ANTLR - 如何从维度扩展单位

ANTLR - How to extact units from a dimension

本文关键字:扩展 单位 ANTLR      更新时间:2023-10-16

我正在使用ANTLR4和 https://github.com/antlr/grammars-v4/tree/master/css3 的CSS语法。语法定义了以下内容(为简洁起见,略有删减(

dimension
: ( Plus | Minus )? Dimension
;
fragment FontRelative
: Number E M
| Number E X
| Number C H
| Number R E M
;
fragment AbsLength
: Number P X
| Number C M
| Number M M
| Number I N
| Number P T
| Number P C
| Number Q
;
fragment Angle
: Number D E G
| Number R A D
| Number G R A D
| Number T U R N
;
fragment Length
: AbsLength
| FontRelative
;
Dimension
: Length
| Angle
;

匹配工作正常,但我没有看到提取单位的明显方法。解析器创建一个具有 3 个TerminalNode成员的DimensionContext-DimensionPlusMinus。我希望能够在解析期间提取单元,而无需进行额外的字符串解析。

我知道一个问题是长度和角度是碎片。我更改了语法不使用片段

Unit
: 'em'
| 'ex'
| 'ch'
| 'rem'
| 'vw'
| 'vh'
| 'vmin'
| 'vmax'
| 'px'
| 'cm'
| 'mm'
| 'in'
| 'pt'
| 'q'
| 'deg'
| 'rad'
| 'grad'
| 'turn'
| 'ms'
| 's'
| 'hz'
| 'khz'
;

Dimension : Number Unit;

事情仍然解析,但我没有得到更多关于单位是什么的上下文 -Dimension仍然是一个单一的TerminalNode.有没有办法在不拉开完整令牌字符串的情况下处理这个问题?

您需要在词法分析器中尽可能少地执行操作:

NUMBER
: Dash? Dot Digit+ { atNumber(); }
| Dash? Digit+ ( Dot Digit* )? { atNumber(); }
;
UNIT
: { aftNumber() }? 
( 'px'  | 'cm'  | 'mm'   | 'in'
| 'pt'  | 'pc'  | 'em'   | 'ex'
| 'deg' | 'rad' | 'grad' | '%'
| 'ms'  | 's'   | 'hz'   | 'khz' 
)
;

诀窍是将NUMBERUNIT作为单独的令牌生成,但仅限于所需的排序。NUMBER规则中的操作只是设置了一个标志,UNIT谓词确保UNIT只能遵循NUMBER

protected void atNumber() {
_number = true;
}
protected boolean aftNumber() {
if (_number && Character.isWhitespace(_input.LA(1))) return false;
if (!_number) return false;
_number = false;
return true;
}

解析器规则很简单,但保留了所需的详细信息:

number
: NUMBER UNIT?
;

使用树漫步,将NUMBER解析为双精度和枚举(或等效项(以提供语义UNIT特征:

public enum Unit {
CM("cm", true, true),   // 1cm = 96px/2.54
MM("mm", true, true),   
IN("in", true, true),   // 1in = 2.54cm = 96px
PX("px", true, true),   // 1px = 1/96th
PT("pt", true, true),   // 1pt = 1/72th
EM("em", false, true),   // element font size
REM("rem", false, true), // root element font size
EX("ex", true, true),   // element font x-height
CAP("cap", true, true), // element font nominal capital letters height
PER("%", false, true),
DEG("deg", true, false),
RAD("rad", true, false),
GRAD("grad", true, false),
MS("ms", true, false),
S("s", true, false),
HZ("hz", true, false),
KHZ("khz", true, false),
NONE(Strings.EMPTY, true, false), // 'no unit specified'
INVALID(Strings.UNKNOWN, true, false);
public final String symbol;
public final boolean abs;
public final boolean len;
private Unit(String symbol, boolean abs, boolean len) {
this.symbol = symbol;
this.abs = abs;
this.len = len;
}
public boolean isAbsolute() { return abs; }
public boolean isLengthUnit() { return len; }
// call from the visitor to resolve from `UNIT` to Unit
public static Unit find(TerminalNode node) {
if (node == null) return NONE;
for (Unit unit : values()) {
if (unit.symbol.equalsIgnoreCase(node.getText())) return unit;
}
return INVALID;
}
@Override
public String toString() {
return symbol;  
}
}