parser-lr 0.8.2 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/parser-lr.js +184 -24
- package/bin/parser-lr.js.map +1 -1
- package/dist/lib/grammar/table-validator.d.ts.map +1 -1
- package/dist/lib/grammar/table-validator.js +127 -2
- package/dist/lib/grammar/table-validator.js.map +1 -1
- package/dist/lib/transform/cst-transformer.d.ts +4 -6
- package/dist/lib/transform/cst-transformer.d.ts.map +1 -1
- package/dist/lib/transform/cst-transformer.js +17 -21
- package/dist/lib/transform/cst-transformer.js.map +1 -1
- package/dist/lib/transform/epsilon-node.d.ts +32 -0
- package/dist/lib/transform/epsilon-node.d.ts.map +1 -0
- package/dist/lib/transform/epsilon-node.js +40 -0
- package/dist/lib/transform/epsilon-node.js.map +1 -0
- package/dist/lib/transform/index.d.ts +1 -0
- package/dist/lib/transform/index.d.ts.map +1 -1
- package/dist/lib/transform/index.js +1 -0
- package/dist/lib/transform/index.js.map +1 -1
- package/docs/grammar.md +7 -3
- package/package.json +1 -1
package/bin/parser-lr.js
CHANGED
|
@@ -3781,7 +3781,7 @@ const {
|
|
|
3781
3781
|
Help,
|
|
3782
3782
|
} = commander;
|
|
3783
3783
|
|
|
3784
|
-
var version$1 = "0.8.
|
|
3784
|
+
var version$1 = "0.8.3";
|
|
3785
3785
|
var packageDefinition = {
|
|
3786
3786
|
version: version$1};
|
|
3787
3787
|
|
|
@@ -7063,6 +7063,46 @@ function referenceSlotIndex(production, reference) {
|
|
|
7063
7063
|
return null;
|
|
7064
7064
|
}
|
|
7065
7065
|
|
|
7066
|
+
/**
|
|
7067
|
+
* Returns whether a CST node was reduced by an empty right-hand side.
|
|
7068
|
+
*
|
|
7069
|
+
* @param node - CST node to test.
|
|
7070
|
+
* @param table - Parse table supplying production metadata.
|
|
7071
|
+
*/
|
|
7072
|
+
function isEpsilonNode(node, table) {
|
|
7073
|
+
if (node.isTerminal || node.children.length > 0 || node.productionId === null) {
|
|
7074
|
+
return false;
|
|
7075
|
+
}
|
|
7076
|
+
const production = table.production(node.productionId);
|
|
7077
|
+
return production !== null && production.rhs.length === 0;
|
|
7078
|
+
}
|
|
7079
|
+
/**
|
|
7080
|
+
* Returns whether a CST node is an authored labeled empty alternative.
|
|
7081
|
+
*
|
|
7082
|
+
* @remarks
|
|
7083
|
+
* Labeled epsilons such as `#absent` are first-class grammar alternatives.
|
|
7084
|
+
* They keep identity through default transforms.
|
|
7085
|
+
*
|
|
7086
|
+
* @param node - CST node to test.
|
|
7087
|
+
* @param table - Parse table supplying production metadata.
|
|
7088
|
+
*/
|
|
7089
|
+
function isAuthoredEpsilonNode(node, table) {
|
|
7090
|
+
return isEpsilonNode(node, table) && node.variant !== null;
|
|
7091
|
+
}
|
|
7092
|
+
/**
|
|
7093
|
+
* Returns whether a CST node is synthetic unlabeled epsilon scaffolding.
|
|
7094
|
+
*
|
|
7095
|
+
* @remarks
|
|
7096
|
+
* Unlabeled empty productions come from `{…}` / `[…]` desugaring. They are
|
|
7097
|
+
* not authored alternatives and should disappear from AST construction.
|
|
7098
|
+
*
|
|
7099
|
+
* @param node - CST node to test.
|
|
7100
|
+
* @param table - Parse table supplying production metadata.
|
|
7101
|
+
*/
|
|
7102
|
+
function isSyntheticEpsilonNode(node, table) {
|
|
7103
|
+
return isEpsilonNode(node, table) && node.variant === null;
|
|
7104
|
+
}
|
|
7105
|
+
|
|
7066
7106
|
/**
|
|
7067
7107
|
* Applies CST-to-AST transform rules to a concrete syntax tree.
|
|
7068
7108
|
*/
|
|
@@ -7188,7 +7228,8 @@ class CstTransformer {
|
|
|
7188
7228
|
if (childNode === null) {
|
|
7189
7229
|
return null;
|
|
7190
7230
|
}
|
|
7191
|
-
|
|
7231
|
+
// Drop synthetic unlabeled epsilons; keep authored `#label` empty alts.
|
|
7232
|
+
if (isSyntheticEpsilonNode(childNode, this.table)) {
|
|
7192
7233
|
continue;
|
|
7193
7234
|
}
|
|
7194
7235
|
const child = this.transformNode(childNode);
|
|
@@ -7220,7 +7261,7 @@ class CstTransformer {
|
|
|
7220
7261
|
}
|
|
7221
7262
|
let acc = this.buildAstNode(expression.typeName, expression.variant, [left, operator, right], node.location);
|
|
7222
7263
|
let tail = this.findRepeatTail(node, slots);
|
|
7223
|
-
while (tail !== null && !this.
|
|
7264
|
+
while (tail !== null && !isSyntheticEpsilonNode(tail, this.table)) {
|
|
7224
7265
|
const tailSlots = this.resolveReferences(tail, expression.references);
|
|
7225
7266
|
if (tailSlots === null) {
|
|
7226
7267
|
return null;
|
|
@@ -7244,7 +7285,7 @@ class CstTransformer {
|
|
|
7244
7285
|
applyFoldRight(node, expression) {
|
|
7245
7286
|
const parts = [];
|
|
7246
7287
|
let current = node;
|
|
7247
|
-
while (current !== null && !this.
|
|
7288
|
+
while (current !== null && !isSyntheticEpsilonNode(current, this.table)) {
|
|
7248
7289
|
const leftRef = expression.references[0];
|
|
7249
7290
|
const operatorRef = expression.references[1];
|
|
7250
7291
|
const rightRef = expression.references[2];
|
|
@@ -7286,14 +7327,14 @@ class CstTransformer {
|
|
|
7286
7327
|
applyFlatten(node, expression) {
|
|
7287
7328
|
const items = [];
|
|
7288
7329
|
let current = node;
|
|
7289
|
-
while (current !== null && !this.
|
|
7330
|
+
while (current !== null && !isSyntheticEpsilonNode(current, this.table)) {
|
|
7290
7331
|
const head = this.transformFlattenHead(current, expression.head);
|
|
7291
7332
|
if (head === null) {
|
|
7292
7333
|
return null;
|
|
7293
7334
|
}
|
|
7294
7335
|
items.push(head);
|
|
7295
7336
|
const tail = this.resolveReferenceNode(current, expression.tail);
|
|
7296
|
-
if (tail === null || this.
|
|
7337
|
+
if (tail === null || isSyntheticEpsilonNode(tail, this.table)) {
|
|
7297
7338
|
break;
|
|
7298
7339
|
}
|
|
7299
7340
|
current = tail;
|
|
@@ -7333,10 +7374,19 @@ class CstTransformer {
|
|
|
7333
7374
|
/**
|
|
7334
7375
|
* Transforms a CST node without explicit transform rules.
|
|
7335
7376
|
*
|
|
7377
|
+
* @remarks
|
|
7378
|
+
* Authored labeled epsilon alternatives (`#absent`) keep their identity.
|
|
7379
|
+
* Synthetic unlabeled epsilons from `{…}` / `[…]` desugaring are dropped.
|
|
7380
|
+
*
|
|
7336
7381
|
* @param node - CST node to pass through or unwrap.
|
|
7337
7382
|
*/
|
|
7338
7383
|
defaultTransform(node) {
|
|
7339
|
-
|
|
7384
|
+
// Authored `#label` empty alternatives are first-class nodes.
|
|
7385
|
+
if (isAuthoredEpsilonNode(node, this.table)) {
|
|
7386
|
+
return AstNode.rule(node.symbol, [], node.location, node.variant);
|
|
7387
|
+
}
|
|
7388
|
+
// Synthetic unlabeled epsilons are scaffolding, not AST content.
|
|
7389
|
+
if (isSyntheticEpsilonNode(node, this.table)) {
|
|
7340
7390
|
return null;
|
|
7341
7391
|
}
|
|
7342
7392
|
const children = this.transformChildren(node.children);
|
|
@@ -7458,21 +7508,6 @@ class CstTransformer {
|
|
|
7458
7508
|
}
|
|
7459
7509
|
return null;
|
|
7460
7510
|
}
|
|
7461
|
-
/**
|
|
7462
|
-
* Returns whether a CST node represents an epsilon production.
|
|
7463
|
-
*
|
|
7464
|
-
* @param node - CST node to test.
|
|
7465
|
-
*/
|
|
7466
|
-
isEpsilonNode(node) {
|
|
7467
|
-
if (node.isTerminal) {
|
|
7468
|
-
return false;
|
|
7469
|
-
}
|
|
7470
|
-
if (node.children.length > 0) {
|
|
7471
|
-
return false;
|
|
7472
|
-
}
|
|
7473
|
-
const production = this.productionFor(node);
|
|
7474
|
-
return production !== null && production.rhs.length === 0;
|
|
7475
|
-
}
|
|
7476
7511
|
/**
|
|
7477
7512
|
* Returns parse table production metadata for a CST node.
|
|
7478
7513
|
*
|
|
@@ -13826,7 +13861,7 @@ function validateGrammarTable(grammar) {
|
|
|
13826
13861
|
for (const rule of grammar.transformSchema.rules) {
|
|
13827
13862
|
validateTransformProductionExists(rule, bnfProductionNames, issues);
|
|
13828
13863
|
for (const alternative of rule.alternatives) {
|
|
13829
|
-
validateTransformExpression(grammar, alternative, issues);
|
|
13864
|
+
validateTransformExpression(grammar, rule, alternative, issues);
|
|
13830
13865
|
collectPassCollapseWarnings(grammar, rule, alternative, bnf.productions, issues);
|
|
13831
13866
|
}
|
|
13832
13867
|
}
|
|
@@ -13930,12 +13965,13 @@ function syntheticRepeatPrefix(name) {
|
|
|
13930
13965
|
* @param alternative - Transform alternative owning the expression.
|
|
13931
13966
|
* @param issues - Issue list to append to.
|
|
13932
13967
|
*/
|
|
13933
|
-
function validateTransformExpression(grammar, alternative, issues) {
|
|
13968
|
+
function validateTransformExpression(grammar, rule, alternative, issues) {
|
|
13934
13969
|
const expression = alternative.expression;
|
|
13935
13970
|
const location = alternative.location ?? null;
|
|
13936
13971
|
switch (expression.kind) {
|
|
13937
13972
|
case 'build':
|
|
13938
13973
|
validateAstTarget(grammar, expression.typeName, expression.variant, location, issues);
|
|
13974
|
+
validateBuildArity(grammar, rule, alternative, expression, location, issues);
|
|
13939
13975
|
break;
|
|
13940
13976
|
case 'foldLeft':
|
|
13941
13977
|
case 'foldRight':
|
|
@@ -13944,6 +13980,130 @@ function validateTransformExpression(grammar, alternative, issues) {
|
|
|
13944
13980
|
break;
|
|
13945
13981
|
}
|
|
13946
13982
|
}
|
|
13983
|
+
/**
|
|
13984
|
+
* Records an error when a build argument list conflicts with a zero-factor shape.
|
|
13985
|
+
*
|
|
13986
|
+
* @remarks
|
|
13987
|
+
* Build argument counts are not required to match grammar or AST slot counts in
|
|
13988
|
+
* general: keywords may be dropped, punctuation may be kept, and child
|
|
13989
|
+
* productions may build a shared parent AST variant. The only coherent checks
|
|
13990
|
+
* are zero-factor cases and empty builds targeting a non-empty AST variant.
|
|
13991
|
+
*
|
|
13992
|
+
* @param grammar - Parsed grammar model.
|
|
13993
|
+
* @param rule - Transform rule owning the build expression.
|
|
13994
|
+
* @param alternative - Transform alternative owning the build expression.
|
|
13995
|
+
* @param expression - Build transform expression.
|
|
13996
|
+
* @param location - Source span of the transform alternative.
|
|
13997
|
+
* @param issues - Issue list to append to.
|
|
13998
|
+
*/
|
|
13999
|
+
function validateBuildArity(grammar, rule, alternative, expression, location, issues) {
|
|
14000
|
+
const argumentCount = expression.arguments.length;
|
|
14001
|
+
const production = grammar.production(rule.production);
|
|
14002
|
+
// Reject build arguments on a labeled zero-factor grammar alternative.
|
|
14003
|
+
if (production !== null) {
|
|
14004
|
+
const alternativeExpression = findGrammarAlternativeExpression(production.expression, alternative.label);
|
|
14005
|
+
if (alternativeExpression !== null
|
|
14006
|
+
&& isZeroFactorExpression(alternativeExpression)
|
|
14007
|
+
&& argumentCount > 0) {
|
|
14008
|
+
issues.push({
|
|
14009
|
+
severity: 'error',
|
|
14010
|
+
message: `transform ${expression.typeName}.${expression.variant} has `
|
|
14011
|
+
+ `${String(argumentCount)} argument(s) but ast variant declares 0`,
|
|
14012
|
+
location,
|
|
14013
|
+
});
|
|
14014
|
+
return;
|
|
14015
|
+
}
|
|
14016
|
+
}
|
|
14017
|
+
if (grammar.astSchema === null) {
|
|
14018
|
+
return;
|
|
14019
|
+
}
|
|
14020
|
+
const astType = grammar.astSchema.type(expression.typeName);
|
|
14021
|
+
if (astType === null) {
|
|
14022
|
+
return;
|
|
14023
|
+
}
|
|
14024
|
+
const variantExpression = findAstVariantExpression(astType.expression, expression.variant);
|
|
14025
|
+
if (variantExpression === null) {
|
|
14026
|
+
return;
|
|
14027
|
+
}
|
|
14028
|
+
// Reject build arguments on a zero-factor AST variant.
|
|
14029
|
+
if (isZeroFactorExpression(variantExpression)) {
|
|
14030
|
+
if (argumentCount > 0) {
|
|
14031
|
+
issues.push({
|
|
14032
|
+
severity: 'error',
|
|
14033
|
+
message: `transform ${expression.typeName}.${expression.variant} has `
|
|
14034
|
+
+ `${String(argumentCount)} argument(s) but ast variant declares 0`,
|
|
14035
|
+
location,
|
|
14036
|
+
});
|
|
14037
|
+
}
|
|
14038
|
+
return;
|
|
14039
|
+
}
|
|
14040
|
+
// Reject empty builds that target a non-empty AST variant.
|
|
14041
|
+
if (argumentCount === 0) {
|
|
14042
|
+
issues.push({
|
|
14043
|
+
severity: 'error',
|
|
14044
|
+
message: `transform ${expression.typeName}.${expression.variant} has `
|
|
14045
|
+
+ `0 argument(s) but ast variant requires 1`,
|
|
14046
|
+
location,
|
|
14047
|
+
});
|
|
14048
|
+
}
|
|
14049
|
+
}
|
|
14050
|
+
/**
|
|
14051
|
+
* Returns whether an expression is a labeled zero-factor alternative.
|
|
14052
|
+
*
|
|
14053
|
+
* @param expression - Grammar or AST alternative expression.
|
|
14054
|
+
*/
|
|
14055
|
+
function isZeroFactorExpression(expression) {
|
|
14056
|
+
return expression.kind === 'sequence' && expression.elements.length === 0;
|
|
14057
|
+
}
|
|
14058
|
+
/**
|
|
14059
|
+
* Returns the expression for one labeled grammar alternative.
|
|
14060
|
+
*
|
|
14061
|
+
* @param expression - Production right-hand side expression.
|
|
14062
|
+
* @param label - Transform alternative label.
|
|
14063
|
+
*/
|
|
14064
|
+
function findGrammarAlternativeExpression(expression, label) {
|
|
14065
|
+
if (expression.kind === 'choice') {
|
|
14066
|
+
for (const alternative of expression.alternatives) {
|
|
14067
|
+
if (alternative.label === label) {
|
|
14068
|
+
return alternative.expression;
|
|
14069
|
+
}
|
|
14070
|
+
}
|
|
14071
|
+
if (label === 'main' && expression.alternatives.length === 1) {
|
|
14072
|
+
return expression.alternatives[0]?.expression ?? null;
|
|
14073
|
+
}
|
|
14074
|
+
return null;
|
|
14075
|
+
}
|
|
14076
|
+
if (label === 'main') {
|
|
14077
|
+
return expression;
|
|
14078
|
+
}
|
|
14079
|
+
return null;
|
|
14080
|
+
}
|
|
14081
|
+
/**
|
|
14082
|
+
* Returns the expression for one labeled AST alternative.
|
|
14083
|
+
*
|
|
14084
|
+
* @param expression - AST type right-hand side.
|
|
14085
|
+
* @param variant - Variant label to find.
|
|
14086
|
+
*/
|
|
14087
|
+
function findAstVariantExpression(expression, variant) {
|
|
14088
|
+
if (expression.kind === 'choice') {
|
|
14089
|
+
for (const alternative of expression.alternatives) {
|
|
14090
|
+
if (alternative.label === variant) {
|
|
14091
|
+
return alternative.expression;
|
|
14092
|
+
}
|
|
14093
|
+
}
|
|
14094
|
+
return null;
|
|
14095
|
+
}
|
|
14096
|
+
if (expression.kind === 'sequence' || expression.kind === 'group') {
|
|
14097
|
+
const inner = expression.kind === 'sequence'
|
|
14098
|
+
? expression.elements[0]
|
|
14099
|
+
: expression.element;
|
|
14100
|
+
if (inner === undefined) {
|
|
14101
|
+
return null;
|
|
14102
|
+
}
|
|
14103
|
+
return findAstVariantExpression(inner, variant);
|
|
14104
|
+
}
|
|
14105
|
+
return null;
|
|
14106
|
+
}
|
|
13947
14107
|
/**
|
|
13948
14108
|
* Records an error when a transform references an undefined AST type or variant.
|
|
13949
14109
|
*
|