@@ -35,7 +35,7 @@ Parse issues (such as unclosed tab stops or invalid regexes) are simply treated
3535as plain text.
3636
3737NOTE: PEG.js is not designed for efficiency. With appropriate benchmarks, it should
38- be a significant gain to hand write a parser.
38+ be a significant gain to hand write a parser (and remove the PEG.js dependency) .
3939
4040*/
4141
@@ -55,11 +55,11 @@ be a significant gain to hand write a parser.
5555 }
5656}
5757
58- // Grab anything that isn't \ or $, then try to build a special node out of it, and (at the top level) if that fails then just accept it as text
59- topLevelContent = content :(text / escapedTopLevel / tabStop / choice / variable / any )* { return coalesce (content ); }
58+ // Grab anything that isn't \ or $, then try to build a special node out of it, and (at the top level) if that fails then just accept the first character as text and continue
59+ topLevelContent = c :(text / escapedTopLevel / tabStop / choice / variable / . )* { return coalesce (c ); }
6060
61- // Placeholder content. The same as top level, except we need to fail on '}' so that it can end the tab stop (the ` any` rule would eat it if we used it here)
62- tabStopContent = content :(tabStopText / escapedTabStop / tabStop / choice / variable / notCloseBrace )* { return coalesce (content ); }
61+ // Placeholder content. The same as top level, except we need to fail on '}' so that it can end the tab stop (the any matcher would eat it if we used it here)
62+ tabStopContent = c :(tabStopText / escapedTabStop / tabStop / choice / variable / [^}] )* { return coalesce (c ); }
6363
6464// The forms of a tab stop. They all start with '$', so we pull that out here.
6565tabStop = '$' t :(tabStopSimple / tabStopWithoutPlaceholder / tabStopWithPlaceholder / tabStopWithTransform ) { return t; }
@@ -97,7 +97,7 @@ regexString = r:([^/\\] / '\\' c:. { return '\\' + c } )* { return r.join(""); }
9797
9898// The form of a substitution for a transformation. It is a mix of plain text + modifiers + backreferences to the find capture groups
9999// It cannot access tab stop values.
100- replace = (replaceText / format / replaceModifier / escapedReplace ) *
100+ replace = r : (replaceText / format / replaceModifier / escapedReplace / [^}/]) * { return coalesce (r); }
101101
102102// A reference to a capture group of the find regex of a transformation. Can conditionally
103103// resolve based on if the match occurred, and have arbitrary modifiers applied to it.
@@ -126,28 +126,29 @@ formatWithIfElse = '{' n:integer ':?' ifContent:replace ':' elseContent:replace
126126formatWithElse = '{' n :integer ':' '-' ? elseContent :replace { return { backreference: n, elseContent }; }
127127
128128// Used in `format`s to transform a string using a JS function
129- modifier = '/' modifier :var { return modifier; }
129+ modifier = '/' modifier :name { return modifier; }
130130
131131// Regex flags. Validation is performed when the regex itself is also known.
132132flags = f :[a-z]* { return f .join (" " ); }
133133
134134// A tab stop that offers a choice between several fixed values. These values are plain text only.
135135// This feature is not implemented, but the syntax is parsed to reserve it for future use.
136136// It will currently just default to a regular tab stop with the first value as it's placeholder.
137- choice = '${' n :integer '|' a :choiceText b :(',' c :choiceText { return c; } )* '|}' { return { index: n, choices: [a, ... b] }; }
137+ // Empty choices are still parsed, as we may wish to assign meaning to it in future.
138+ choice = '${' n :integer '|' c :(a :choiceText b :(',' c :choiceText { return c; } )* { return [a, ... b] })? '|}' { return { index: n, choices: c || [] }; }
138139
139140// Syntactically looks like a named tab stop. Variables are resolved in JS and may be
140141// further processed with a transformation. Unrecognised variables are transformed into
141142// tab stops with the variable name as a placeholder.
142143variable = '$' v :(variableSimple / variablePlain / variableWithPlaceholder / variableWithTransform ) { return v; }
143144
144- variableSimple = v :var { return { variable: v }; }
145+ variableSimple = v :name { return { variable: v }; }
145146
146- variablePlain = '{' v :var '}' { return { variable: v }; }
147+ variablePlain = '{' v :name '}' { return { variable: v }; }
147148
148- variableWithPlaceholder = '{' v :var ':' content :tabStopContent '}' { return { variable: v, content }; }
149+ variableWithPlaceholder = '{' v :name ':' content :tabStopContent '}' { return { variable: v, content }; }
149150
150- variableWithTransform = '{' v :var t :transformation '}' { return { variable: v, transformation: t }; }
151+ variableWithTransform = '{' v :name t :transformation '}' { return { variable: v, transformation: t }; }
151152
152153// Top level text. Anything that cannot be the start of something special. False negatives are handled later by the `any` rule
153154text = t :([^$\\ }])+ { return t .join (" " ) }
@@ -174,16 +175,10 @@ escapedChoice = '\\' c:[$\\,|] { return c; }
174175escapedReplace = '\\ ' c :[$\\ /] { return c; }
175176
176177// We handle 'modifiers' separately to escapes. These indicate a change in state when building the replacement (e.g., capitalisation)
177- replaceModifier = '\\ ' m :[uUlLeEnr ] { return { modifier: m }; }
178+ replaceModifier = '\\ ' m :[ElLuUnr ] { return { modifier: m }; }
178179
179180// Match nonnegative integers like those used for tab stop ordering
180181integer = digits :[0-9]+ { return parseInt (digits .join (" " ), 10 ); }
181182
182183// Match variable names like TM_SELECTED_TEXT
183- var = a :[a-zA-Z_] b :[a-zA-Z_0-9]* { return a + b .join (" " ); }
184-
185- // Match any single character. Useful to resolve any parse errors where something that looked like it would be special had malformed syntax.
186- any = a :. { return a; }
187-
188- // Match anything that isn't a '}'. Useful for parse errors inside placeholder text, as `}` should be used to end the tab stop region
189- notCloseBrace = a :[^}] { return a; }
184+ name = a :[a-zA-Z_] b :[a-zA-Z_0-9]* { return a + b .join (" " ); }
0 commit comments