Feature File Grammar Changes

Adobe has not—at least in recent memory—maintained a document with a formal grammar for feature files. However, when AFDKO feature file parser was ported to ANTLR 4 in 2021 we tried to make the new source files easy to read, in part so that other implementers can refer to them when there are changes. This document shows the differences between the three source files as they were in 2021 and how they are after variable value support has been added.

The tokenizer

The grammar for tokens is contained in FeatLexerBase.g4, which is part of the addfeatures in AFDKO (what used to be makeotfexe. The substantive changes are:

  • A number of new keywords
  • Separate tokens for the axis units (u, d, and n
  • (, ), +, and : are now keywords
  • Quoted strings are handled differently
  • LNAME is the token for the restricted (compared with glyph names) grammar of location names
  • CATCHTAG (which parsed non-quoted tags with special characters) is removed
 /* Copyright 2021 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved.
  * This software is licensed as OpenSource, under the Apache License, Version 2.0.
  * This license is available at: http://opensource.org/licenses/Apache-2.0.
  */
 
 // -------------------------- Feature file tokens ---------------------------
 
 lexer grammar FeatLexerBase;
 
 COMMENT                 : '#' ~[\r\n]* -> skip ;
 WHITESPACE              : [ \t\r\n]+ -> skip ;
 
 INCLUDE                 : 'include' -> pushMode(Include) ;
 FEATURE                 : 'feature' ;
 TABLE                   : 'table' ;
 SCRIPT                  : 'script' ;
 LANGUAGE                : 'language' ;
 LANGSYS                 : 'languagesystem' ;
 SUBTABLE                : 'subtable';
 LOOKUP                  : 'lookup' ;
 LOOKUPFLAG              : 'lookupflag' ;
 NOTDEF                  : '.notdef' ;
 
 RIGHT_TO_LEFT           : 'RightToLeft' ;
 IGNORE_BASE_GLYPHS      : 'IgnoreBaseGlyphs' ;
 IGNORE_LIGATURES        : 'IgnoreLigatures' ;
 IGNORE_MARKS            : 'IgnoreMarks' ;
 USE_MARK_FILTERING_SET  : 'UseMarkFilteringSet' ;
 MARK_ATTACHMENT_TYPE    : 'MarkAttachmentType' ;
 
 ANON                    : 'anon' ;
 ANON_v                  : 'anonymous' ;
 
 EXCLUDE_DFLT            : 'excludeDFLT' ;
 INCLUDE_DFLT            : 'includeDFLT' ;
 EXCLUDE_dflt            : 'exclude_dflt' ;
 INCLUDE_dflt            : 'include_dflt' ;
 
 USE_EXTENSION           : 'useExtension' ;
 BEGINVALUE              : '<' ;
 ENDVALUE                : '>' ;
 ENUMERATE               : 'enumerate' ;
 ENUMERATE_v             : 'enum' ;
 EXCEPT                  : 'except' ;
 IGNORE                  : 'ignore' ;
 SUBSTITUTE              : 'substitute' ;
 SUBSTITUTE_v            : 'sub' ;
 REVERSE                 : 'reversesub' ;
 REVERSE_v               : 'rsub' ;
 BY                      : 'by' ;
 FROM                    : 'from' ;
 POSITION                : 'position' ;
 POSITION_v              : 'pos';
 PARAMETERS              : 'parameters' ;
 FEATURE_NAMES           : 'featureNames' ;
 CV_PARAMETERS           : 'cvParameters' ;
 CV_UI_LABEL             : 'FeatUILabelNameID' ;
 CV_TOOLTIP              : 'FeatUITooltipTextNameID' ;
 CV_SAMPLE_TEXT          : 'SampleTextNameID' ;
 CV_PARAM_LABEL          : 'ParamUILabelNameID' ;
 CV_CHARACTER            : 'Character' ;
 SIZEMENUNAME            : 'sizemenuname' ;
 CONTOURPOINT            : 'contourpoint' ;
 ANCHOR                  : 'anchor' ;
 ANCHOR_DEF              : 'anchorDef' ;
 VALUE_RECORD_DEF        : 'valueRecordDef' ;
+LOCATION_DEF            : 'locationDef' ;
 MARK                    : 'mark';
 MARK_CLASS              : 'markClass' ;
 CURSIVE                 : 'cursive' ;
 MARKBASE                : 'base' ;
 MARKLIG                 : 'ligature' ;
 MARKLIG_v               : 'lig' ;
 LIG_COMPONENT           : 'ligComponent' ;
 
 KNULL                   : 'NULL' ;
 
 BASE                    : 'BASE' ;
 HA_BTL                  : 'HorizAxis.BaseTagList' ;
 VA_BTL                  : 'VertAxis.BaseTagList' ;
 HA_BSL                  : 'HorizAxis.BaseScriptList' ;
 VA_BSL                  : 'VertAxis.BaseScriptList' ;
 
 GDEF                    : 'GDEF' ;
 GLYPH_CLASS_DEF         : 'GlyphClassDef' ;
 ATTACH                  : 'Attach' ;
 LIG_CARET_BY_POS        : 'LigatureCaretByPos' ;
 LIG_CARET_BY_IDX        : 'LigatureCaretByIndex' ;
 
 HEAD                    : 'head' ;
 FONT_REVISION           : 'FontRevision' ;
 
 HHEA                    : 'hhea' ;
 ASCENDER                : 'Ascender' ;
 DESCENDER               : 'Descender' ;
 LINE_GAP                : 'LineGap' ;
 
 CARET_OFFSET            : 'CaretOffset' ;
+CARET_SLOPE_RISE        : 'CaretSlopeRise' ;
+CARET_SLOPE_RUN         : 'CaretSlopeRun' ;
 
 NAME                    : 'name' ;
 NAMEID                  : 'nameid' ;
 
 OS_2                    : 'OS/2' ;
 FS_TYPE                 : 'FSType' ;
 FS_TYPE_v               : 'fsType' ;
 OS2_LOWER_OP_SIZE       : 'LowerOpSize' ;
 OS2_UPPER_OP_SIZE       : 'UpperOpSize' ;
 PANOSE                  : 'Panose' ;
 TYPO_ASCENDER           : 'TypoAscender' ;
 TYPO_DESCENDER          : 'TypoDescender' ;
 TYPO_LINE_GAP           : 'TypoLineGap' ;
 WIN_ASCENT              : 'winAscent' ;
 WIN_DESCENT             : 'winDescent' ;
 X_HEIGHT                : 'XHeight' ;
 CAP_HEIGHT              : 'CapHeight' ;
+SUBSCRIPT_X_SIZE        : 'SubscriptXSize' ;
+SUBSCRIPT_X_OFFSET      : 'SubscriptXOffset' ;
+SUBSCRIPT_Y_SIZE        : 'SubscriptYSize' ;
+SUBSCRIPT_Y_OFFSET      : 'SubscriptYOffset' ;
+SUPERSCRIPT_X_SIZE      : 'SuperscriptXSize' ;
+SUPERSCRIPT_X_OFFSET    : 'SuperscriptXOffset' ;
+SUPERSCRIPT_Y_SIZE      : 'SuperscriptYSize' ;
+SUPERSCRIPT_Y_OFFSET    : 'SuperscriptYOffset' ;
+STRIKEOUT_SIZE          : 'StrikeoutSize' ;
+STRIKEOUT_POSITION      : 'StrikeoutPosition' ;
 WEIGHT_CLASS            : 'WeightClass' ;
 WIDTH_CLASS             : 'WidthClass' ;
 VENDOR                  : 'Vendor' ;
 UNICODE_RANGE           : 'UnicodeRange' ;
 CODE_PAGE_RANGE         : 'CodePageRange' ;
 FAMILY_CLASS            : 'FamilyClass' ;
 
 STAT                    : 'STAT' ;
 ELIDED_FALLBACK_NAME    : 'ElidedFallbackName' ;
 ELIDED_FALLBACK_NAME_ID : 'ElidedFallbackNameID' ;
 DESIGN_AXIS             : 'DesignAxis' ;
 AXIS_VALUE              : 'AxisValue';
 FLAG                    : 'flag' ;
 LOCATION                : 'location';
 AXIS_EAVN               : 'ElidableAxisValueName';
 AXIS_OSFA               : 'OlderSiblingFontAttribute';
 
 VHEA                    : 'vhea' ;
 VERT_TYPO_ASCENDER      : 'VertTypoAscender' ;
 VERT_TYPO_DESCENDER     : 'VertTypoDescender' ;
 VERT_TYPO_LINE_GAP      : 'VertTypoLineGap' ;
 
 VMTX                    : 'vmtx' ;
 VERT_ORIGIN_Y           : 'VertOriginY' ;
 VERT_ADVANCE_Y          : 'VertAdvanceY' ;
 
 LCBRACE                 : '{' ;
 RCBRACE                 : '}' ;
 LBRACKET                : '[' ;
 RBRACKET                : ']' ;
+LPAREN                  : '(' ;
+RPAREN                  : ')' ;
 HYPHEN                  : '-' ;
+PLUS                    : '+' ;
 SEMI                    : ';' ;
 EQUALS                  : '=' ;
 MARKER                  : '\'' ;
 COMMA                   : ',' ;
-QUOTE                   : '"' -> pushMode(String) ;
+COLON                   : ':' ;
+STRVAL                  : '"' ( '\\"' | ~["] )* '"' ;
 
 fragment GNST           : 'A' .. 'Z' | 'a' .. 'z' | '_' ;
 fragment LCHR           : GNST | '0' .. '9' | '.' ;
 fragment GCCHR          : LCHR | '-' ;
+LNAME                   : '@' GNST LCHR* ;
 GCLASS                  : '@' GNST GCCHR* ;
 
+AXISUNIT                : 'u' | 'd' | 'n' ;
 CID                     : '\\' ( '0' .. '9' )+ ;
 fragment GNCHR          : GCCHR | '+' | '*' | ':' | '~' | '^' | '|' ;
 ESCGNAME                : '\\' GNST GNCHR* ;
 NAMELABEL               : GNST LCHR* ;
 EXTNAME                 : GNST GNCHR* ;
 POINTNUM                : '-'? ( '0' .. '9' )+ '.' ( '0' .. '9' )+ ;
 NUMEXT                  : '0x' ( '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' )+ ;
 NUMOCT                  : '0' ( '0' .. '7' )+ ;
 NUM                     : '-'? ( '1' .. '9' ( '0' .. '9' )* | '0' ) ;
-fragment TSTART         :  '!' | '$' | '%' | '&' | '*' | '+' | '.' | ':' | '?' |
-                           'A' .. 'Z' | '^' .. 'z' | '|' | '~' ;
-fragment TCHR           : TSTART | '0' .. '9' | '-' ;
-CATCHTAG                : TSTART TCHR? ;
 
 mode Include;
 
 I_WHITESPACE            : [ \t\r\n]+ -> skip ;
 I_RPAREN                : '(' -> mode(Ifile) ;
 
 mode Ifile;
 
 IFILE                   : ~')'+ ;
 I_LPAREN                : ')' -> popMode ;
-
-mode String;
-
-STRVAL                  : ~'"'* ;
-EQUOTE                  : '"' -> popMode ;

Anonymous block handling

FeatLexerBase.g4 is imported by FeatLexer.g4, which contains more complex handling for anonymous blocks. The changes to this file have to do with the changes in tag handling in the base token grammar.

 /* Copyright 2024 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved.
  * This software is licensed as OpenSource, under the Apache License, Version 2.0.
  * This license is available at: http://opensource.org/licenses/Apache-2.0.
  */
 
 lexer grammar FeatLexer;
 import FeatLexerBase;
 
 @members {
  std::string anon_tag;
 
  /* All the TSTART/TCHR characters are grouped together, so just
   * look for the string and if its there verify that the characters
   * on either side are from the appropriate set (in case there are
   * "extra" characters).
   */
 
  bool verify_anon(const std::string &line) {
      auto p = line.find(anon_tag);
      if ( p == std::string::npos )
          return false;
      --p;
      if ( ! ( line[p] == ' ' || line[p] == '\t' || line[p] == '}' ) )
          return false;
      p += anon_tag.size() + 1;
      if ( ! ( line[p] == ' ' || line[p] == '\t' || line[p] == ';' ) )
          return false;
      return true;
  }
 }
 
 ANON                        : 'anon' -> pushMode(Anon) ;
 ANON_v                      : 'anonymous' -> pushMode(Anon) ;
 
 mode Anon;
 
 A_WHITESPACE                : [ \t\r\n]+ -> skip ;
-A_LABEL                     : TSTART TCHR* { anon_tag = getText(); } ;
+A_LABEL                     : (NAMELABEL | EXTNAME | STRVAL | MARK) { anon_tag = getText(); } ;
 A_LBRACE                    : '{' -> mode(AnonContent) ;
 
 mode AnonContent;
 
-A_CLOSE                     : '\r'? '\n}' [ \t]* TSTART TCHR* [ \t]* ';' { verify_anon(getText()) }? -> popMode ;
+A_CLOSE                     : '\r'? '\n}' [ \t]* (NAMELABEL | EXTNAME | STRVAL | MARK) [ \t]* ';' { verify_anon(getText()) }? -> popMode ;
 A_LINE                      : '\r'? '\n' ~[\r\n]* ;

The parser

FeatParser.g4 specifies the grammar. The changes here correspond straightforwardly to the changes in the specification. Note that tags with unusual characters must now have quotes around them.

 /* Copyright 2024 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved.
  * This software is licensed as OpenSource, under the Apache License, Version 2.0.
  * This license is available at: http://opensource.org/licenses/Apache-2.0.
  */
 
 // ------------------------- Feature file grammar ---------------------------
 
 parser grammar FeatParser;
 
 options { tokenVocab = FeatLexer; }
 
 file:
     ( topLevelStatement
     | featureBlock
     | tableBlock
     | anonBlock
     | lookupBlockTopLevel
     )* EOF
 ;
 
 topLevelStatement:
     ( include
     | glyphClassAssign
     | langsysAssign
     | mark_statement
     | anchorDef
     | valueRecordDef
+    | locationDef
     )
     SEMI
 ;
 
 include:
     INCLUDE I_RPAREN IFILE I_LPAREN
 ;
 
 glyphClassAssign:
-    GCLASS EQUALS glyphClass
+    gclass EQUALS glyphClass
 ;
 
 langsysAssign:
     LANGSYS script=tag lang=tag
 ;
 
 mark_statement:
-    MARK_CLASS ( glyph | glyphClass ) anchor GCLASS
+    MARK_CLASS ( glyph | glyphClass ) anchor gclass
 ;
 
 anchorDef:
-    ANCHOR_DEF xval=NUM yval=NUM ( CONTOURPOINT cp=NUM )? name=label
+    ANCHOR_DEF anchorLiteral name=label
 ;
 
 valueRecordDef:
     VALUE_RECORD_DEF valueLiteral label
 ;
 
+locationDef:
+    LOCATION_DEF locationLiteral LNAME
+;
+
 featureBlock:
     FEATURE starttag=tag USE_EXTENSION? LCBRACE
     featureStatement+
     RCBRACE endtag=tag SEMI
 ;
 
 tableBlock:
     TABLE
     ( table_BASE
     | table_GDEF
     | table_head
     | table_hhea
     | table_vhea
     | table_name
     | table_OS_2
     | table_STAT
     | table_vmtx
     )
 ;
 
 anonBlock:
     anontok A_LABEL A_LBRACE A_LINE* A_CLOSE
 ;
 
 lookupBlockTopLevel:
     LOOKUP startlabel=label USE_EXTENSION? LCBRACE
     statement+
     RCBRACE endlabel=label SEMI
 ;
 
 featureStatement:
       statement
     | lookupBlockOrUse
     | cvParameterBlock
 ;
 
 lookupBlockOrUse:
     LOOKUP startlabel=label ( USE_EXTENSION? LCBRACE
     statement+
     RCBRACE endlabel=label )? SEMI
 ;
 
 cvParameterBlock:
     CV_PARAMETERS LCBRACE
     cvParameterStatement*
     RCBRACE SEMI
 ;
 
 cvParameterStatement:
     ( cvParameter
     | include
     ) SEMI
 ;
 
 cvParameter:
       ( CV_UI_LABEL | CV_TOOLTIP | CV_SAMPLE_TEXT | CV_PARAM_LABEL ) LCBRACE
       nameEntryStatement+
       RCBRACE
     | CV_CHARACTER genNum
 ;
 
 statement:
     ( featureUse
     | scriptAssign
     | langAssign
     | lookupflagAssign
     | glyphClassAssign
     | ignoreSubOrPos
     | substitute
     | mark_statement
     | position
     | parameters
     | sizemenuname
     | featureNames
     | subtable
     | include
     ) SEMI
 ;
 
 featureUse:
     FEATURE tag
 ;
 
 scriptAssign:
     SCRIPT tag
 ;
 
 langAssign:
     LANGUAGE tag ( EXCLUDE_DFLT | INCLUDE_DFLT | EXCLUDE_dflt | INCLUDE_dflt )?
 ;
 
 lookupflagAssign:
     LOOKUPFLAG ( NUM | lookupflagElement+ )
 ;
 
 lookupflagElement:
       RIGHT_TO_LEFT
     | IGNORE_BASE_GLYPHS
     | IGNORE_LIGATURES
     | IGNORE_MARKS
     | ( MARK_ATTACHMENT_TYPE glyphClass )
     | ( USE_MARK_FILTERING_SET glyphClass )
 ;
 
 ignoreSubOrPos:
     IGNORE ( subtok | revtok | postok ) lookupPattern ( COMMA lookupPattern )*
 ;
 
 substitute:
     ( EXCEPT lookupPattern ( COMMA lookupPattern )* )?
     (   revtok startpat=lookupPattern ( BY ( KNULL | endpat=lookupPattern ) )?
       | subtok startpat=lookupPattern ( ( BY | FROM ) ( KNULL | endpat=lookupPattern ) )? )
 ;
 
 position:
     enumtok? postok startpat=pattern?
     (
         ( valueRecord valuePattern* )
       | ( ( LOOKUP label )+ lookupPatternElement* )
       | ( CURSIVE cursiveElement endpat=pattern? )
       | ( MARKBASE midpat=pattern baseToMarkElement+ endpat=pattern? )
       | ( markligtok midpat=pattern ligatureMarkElement+ endpat=pattern? )
       | ( MARK midpat=pattern baseToMarkElement+ endpat=pattern? )
     )
 ;
 
 valuePattern:
     patternElement valueRecord?
 ;
 
 valueRecord:
     BEGINVALUE valuename=label ENDVALUE | valueLiteral
 ;
 
 valueLiteral:
-    ( BEGINVALUE NUM NUM NUM NUM ENDVALUE ) | NUM
+      singleValueLiteral
+    | ( BEGINVALUE singleValueLiteral singleValueLiteral
+                   singleValueLiteral singleValueLiteral ENDVALUE )
+    | ( LPAREN locationMultiValueLiteral+ RPAREN )
+;
+
+singleValueLiteral:
+      NUM | parenLocationValue
+;
+
+parenLocationValue:
+    LPAREN locationValueLiteral+ RPAREN
+;
+
+locationValueLiteral:
+    (locationSpecifier COLON)? NUM
+;
+
+locationMultiValueLiteral:
+    (locationSpecifier COLON)? BEGINVALUE NUM NUM NUM NUM ENDVALUE
+;
+
+locationSpecifier:
+    locationLiteral | LNAME
+;
+
+locationLiteral:
+    axisLocationLiteral ( COMMA axisLocationLiteral )*
+;
+
+axisLocationLiteral:
+    tag EQUALS fixedNum ( HYPHEN | PLUS )? AXISUNIT
 ;
 
 cursiveElement:
     patternElement anchor anchor
 ;
 
 baseToMarkElement:
-    anchor MARK GCLASS MARKER?
+    anchor MARK gclass MARKER?
 ;
 
 ligatureMarkElement:
-    anchor ( MARK GCLASS )? LIG_COMPONENT? MARKER?
+    anchor ( MARK gclass )? LIG_COMPONENT? MARKER?
 ;
 
 parameters:
     PARAMETERS fixedNum+
 ;
 
 sizemenuname:
-    SIZEMENUNAME ( genNum ( genNum genNum )? )? QUOTE STRVAL EQUOTE
+    SIZEMENUNAME ( genNum ( genNum genNum )? )? STRVAL
 ;
 
 featureNames:
     FEATURE_NAMES LCBRACE
     nameEntryStatement+
     RCBRACE
 ;
 
 subtable:
     SUBTABLE
 ;
 
 table_BASE:
     BASE LCBRACE
     baseStatement+
     RCBRACE BASE SEMI
 ;
 
 baseStatement:
     ( axisTags
     | axisScripts
     | include
     ) SEMI
 ;
 
 axisTags:
     ( HA_BTL | VA_BTL ) tag+
 ;
 
 axisScripts:
     ( HA_BSL | VA_BSL ) baseScript ( COMMA baseScript )*
 ;
 
 baseScript:
-    script=tag db=tag NUM+
+    script=tag db=tag singleValueLiteral+
 ;
 
 table_GDEF:
     GDEF LCBRACE
     gdefStatement+
     RCBRACE GDEF SEMI
 ;
 
 gdefStatement:
     ( gdefGlyphClass
     | gdefAttach
     | gdefLigCaretPos
     | gdefLigCaretIndex
     | include
     ) SEMI
 ;
 
 gdefGlyphClass:
     GLYPH_CLASS_DEF glyphClassOptional COMMA
                     glyphClassOptional COMMA
                     glyphClassOptional COMMA
                     glyphClassOptional
 ;
 
 gdefAttach:
     ATTACH lookupPattern NUM+
 ;
 
 gdefLigCaretPos:
-    LIG_CARET_BY_POS lookupPattern NUM+
+    LIG_CARET_BY_POS lookupPattern singleValueLiteral+
 ;
 
 gdefLigCaretIndex:
     LIG_CARET_BY_IDX lookupPattern NUM+
 ;
 
 table_head:
     HEAD LCBRACE
     headStatement+
     RCBRACE HEAD SEMI
 ;
 
 headStatement:
     ( head
     | include
     ) SEMI
 ;
 
 head:
     FONT_REVISION POINTNUM
 ;
 
 table_hhea:
     HHEA LCBRACE
     hheaStatement*
     RCBRACE HHEA SEMI
 ;
 
 hheaStatement:
     ( hhea
     | include
     ) SEMI
 ;
 
 hhea:
-    ( CARET_OFFSET | ASCENDER | DESCENDER | LINE_GAP ) NUM
+      ( ASCENDER | DESCENDER | LINE_GAP ) NUM
+    | ( CARET_OFFSET | CARET_SLOPE_RISE | CARET_SLOPE_RUN ) singleValueLiteral
 ;
 
 table_vhea:
     VHEA LCBRACE
     vheaStatement*
     RCBRACE VHEA SEMI
 ;
 
 vheaStatement:
     ( vhea
     | include
     ) SEMI
 ;
 
 vhea:
-    ( VERT_TYPO_ASCENDER | VERT_TYPO_DESCENDER | VERT_TYPO_LINE_GAP ) NUM
+    ( VERT_TYPO_ASCENDER | VERT_TYPO_DESCENDER | VERT_TYPO_LINE_GAP
+    | CARET_OFFSET | CARET_SLOPE_RISE | CARET_SLOPE_RUN ) singleValueLiteral
 ;
 
 table_name:
     NAME LCBRACE
     nameStatement+
     RCBRACE NAME SEMI
 ;
 
 nameStatement:
     ( nameID
     | include
     ) SEMI
 ;
 
 nameID:
-    NAMEID id=genNum ( plat=genNum ( spec=genNum lang=genNum )? )? QUOTE STRVAL EQUOTE
+    NAMEID id=genNum ( plat=genNum ( spec=genNum lang=genNum )? )? STRVAL
 ;
 
 table_OS_2:
     OS_2 LCBRACE
     os_2Statement+
     RCBRACE OS_2 SEMI
 ;
 
 os_2Statement:
     ( os_2
     | include
     ) SEMI
 ;
 
 os_2:
       ( TYPO_ASCENDER | TYPO_DESCENDER | TYPO_LINE_GAP
-      | WIN_ASCENT | WIN_DESCENT | X_HEIGHT | CAP_HEIGHT ) num=NUM
+      | WIN_ASCENT | WIN_DESCENT | X_HEIGHT | CAP_HEIGHT
+      | SUBSCRIPT_X_SIZE | SUBSCRIPT_X_OFFSET
+      | SUBSCRIPT_Y_SIZE | SUBSCRIPT_Y_OFFSET
+      | SUPERSCRIPT_X_SIZE | SUPERSCRIPT_X_OFFSET
+      | SUPERSCRIPT_Y_SIZE | SUPERSCRIPT_Y_OFFSET
+      | STRIKEOUT_SIZE | STRIKEOUT_POSITION ) num=singleValueLiteral
     |
       ( FS_TYPE | FS_TYPE_v | WEIGHT_CLASS | WIDTH_CLASS
       | OS2_LOWER_OP_SIZE | OS2_UPPER_OP_SIZE ) unum=NUM
     | FAMILY_CLASS gnum=genNum
-    | VENDOR QUOTE STRVAL EQUOTE
+    | VENDOR STRVAL
     | PANOSE NUM NUM NUM NUM NUM NUM NUM NUM NUM NUM
     | ( UNICODE_RANGE | CODE_PAGE_RANGE ) NUM+
 ;
 
-
 table_STAT:
     STAT LCBRACE
     statStatement+
     RCBRACE STAT SEMI
 ;
 
 statStatement:
     ( designAxis
     | axisValue
     | elidedFallbackName
     | elidedFallbackNameID
     | include
     ) SEMI
 ;
 
 designAxis:
     DESIGN_AXIS tag NUM LCBRACE
     nameEntryStatement+
     RCBRACE
 ;
 
 axisValue:
     AXIS_VALUE LCBRACE
     axisValueStatement+
     RCBRACE
 ;
 
 axisValueStatement:
     ( nameEntry
     | axisValueLocation
     | axisValueFlags
     | include
     ) SEMI
 ;
 
 axisValueLocation:
     LOCATION tag fixedNum ( fixedNum fixedNum? )?
 ;
 
 axisValueFlags:
     FLAG ( AXIS_OSFA | AXIS_EAVN )+
 ;
 
 elidedFallbackName:
     ELIDED_FALLBACK_NAME LCBRACE
     nameEntryStatement+
     RCBRACE
 ;
 
 nameEntryStatement:
     ( nameEntry
     | include
     ) SEMI
 ;
 
 elidedFallbackNameID:
     ELIDED_FALLBACK_NAME_ID genNum
 ;
 
 nameEntry:
-    NAME ( genNum ( genNum genNum )? )? QUOTE STRVAL EQUOTE
+    NAME ( genNum ( genNum genNum )? )? STRVAL
 ;
 
 table_vmtx:
     VMTX LCBRACE
     vmtxStatement+
     RCBRACE VMTX SEMI
 ;
 
 vmtxStatement:
     ( vmtx
     | include
     ) SEMI
 ;
 
 vmtx:
-    ( VERT_ORIGIN_Y | VERT_ADVANCE_Y ) glyph NUM
+    ( VERT_ORIGIN_Y | VERT_ADVANCE_Y ) glyph singleValueLiteral
 ;
 
 anchor:
     BEGINVALUE ANCHOR
-    (   ( xval=NUM yval=NUM ( CONTOURPOINT cp=NUM )? )
+    (   anchorLiteral
       | KNULL
       | name=label
     ) ENDVALUE
 ;
 
+anchorLiteral:
+    anchorLiteralXY ( CONTOURPOINT cp=NUM )?
+;
+
+anchorLiteralXY:
+      (xval=singleValueLiteral yval=singleValueLiteral)
+    | (LPAREN anchorMultiValueLiteral+ RPAREN)
+;
+
+anchorMultiValueLiteral:
+    (locationSpecifier COLON)? BEGINVALUE NUM NUM ENDVALUE
+;
+
 lookupPattern:
     lookupPatternElement+
 ;
 
 lookupPatternElement:
     patternElement ( LOOKUP label )*
 ;
 
 pattern:
     patternElement+
 ;
 
 patternElement:
     ( glyphClass | glyph ) MARKER?
 ;
 
 glyphClassOptional:
     glyphClass?
 ;
 
 glyphClass:
-    GCLASS | gcLiteral
+    gclass | gcLiteral
 ;
 
 gcLiteral:
     LBRACKET gcLiteralElement+ RBRACKET
 ;
 
 gcLiteralElement:
       startg=glyph ( HYPHEN endg=glyph )?
-    | GCLASS
+    | gclass
+;
+
+gclass:
+    LNAME | GCLASS
 ;
 
 glyph:
       glyphName
     | CID
 ;
 
 glyphName:
-    ESCGNAME | NAMELABEL | EXTNAME | NOTDEF
+    ESCGNAME | NAMELABEL | EXTNAME | AXISUNIT | NOTDEF
 ;
 
 label:
-    NAMELABEL | MARK
+    NAMELABEL | MARK | AXISUNIT
 ;
 
 tag:
-    NAMELABEL | EXTNAME | CATCHTAG | MARK     // MARK included for "feature mark"
+    NAMELABEL | EXTNAME | STRVAL | MARK     // MARK included for "feature mark"
 ;
 
 fixedNum:
     POINTNUM | NUM
 ;
 
 genNum:
     NUM | NUMOCT | NUMEXT
 ;
 
 // These are for an include directive in a block with statements
 
 featureFile:
     featureStatement* EOF
 ;
 
 statementFile:
     statement* EOF
 ;
 
 cvStatementFile:
     cvParameterStatement* EOF
 ;
 
 baseFile:
     baseStatement* EOF
 ;
 
 headFile:
     headStatement* EOF
 ;
 
 hheaFile:
     hheaStatement* EOF
 ;
 
 vheaFile:
     vheaStatement* EOF
 ;
 
 gdefFile:
     gdefStatement* EOF
 ;
 
 nameFile:
     nameStatement* EOF
 ;
 
 vmtxFile:
     vmtxStatement* EOF
 ;
 
 os_2File:
     os_2Statement* EOF
 ;
 
 statFile:
     statStatement* EOF
 ;
 
 axisValueFile:
     axisValueStatement* EOF
 ;
 
 nameEntryFile:
     nameEntryStatement* EOF
 ;
 
 /* These tokens are defined this way because they slightly improves
  * Antlr 4's default error reporting.  If we wind up overloading the
  * class with the token literals at the C++ level I will devolve these
  * back into the Lexer grammar.
  */
 subtok:
     SUBSTITUTE | SUBSTITUTE_v
 ;
 
 revtok:
     REVERSE | REVERSE_v
 ;
 
 anontok:
     ANON | ANON_v
 ;
 
 enumtok:
     ENUMERATE | ENUMERATE_v
 ;
 
 postok:
     POSITION | POSITION_v
 ;
 
 markligtok:
     MARKLIG | MARKLIG_v
 ;