parser-lr 0.8.0 → 0.8.1
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 +295 -52
- package/bin/parser-lr.js.map +1 -1
- package/dist/lib/diagnostics/format-diagnostic.d.ts +31 -0
- package/dist/lib/diagnostics/format-diagnostic.d.ts.map +1 -0
- package/dist/lib/diagnostics/format-diagnostic.js +36 -0
- package/dist/lib/diagnostics/format-diagnostic.js.map +1 -0
- package/dist/lib/diagnostics/index.d.ts +5 -0
- package/dist/lib/diagnostics/index.d.ts.map +1 -0
- package/dist/lib/diagnostics/index.js +3 -0
- package/dist/lib/diagnostics/index.js.map +1 -0
- package/dist/lib/diagnostics/source-position.d.ts +24 -0
- package/dist/lib/diagnostics/source-position.d.ts.map +1 -0
- package/dist/lib/diagnostics/source-position.js +39 -0
- package/dist/lib/diagnostics/source-position.js.map +1 -0
- package/dist/lib/grammar/ast-type.d.ts +2 -0
- package/dist/lib/grammar/ast-type.d.ts.map +1 -1
- package/dist/lib/grammar/grammar-from-cst.js +4 -3
- package/dist/lib/grammar/grammar-from-cst.js.map +1 -1
- package/dist/lib/grammar/index.d.ts +1 -1
- package/dist/lib/grammar/index.d.ts.map +1 -1
- package/dist/lib/grammar/production.d.ts +2 -0
- package/dist/lib/grammar/production.d.ts.map +1 -1
- package/dist/lib/grammar/table-validator.d.ts +12 -2
- package/dist/lib/grammar/table-validator.d.ts.map +1 -1
- package/dist/lib/grammar/table-validator.js +49 -24
- package/dist/lib/grammar/table-validator.js.map +1 -1
- package/dist/lib/grammar/transform-rule.d.ts +3 -0
- package/dist/lib/grammar/transform-rule.d.ts.map +1 -1
- package/dist/lib/grammar-entry.d.ts +1 -1
- package/dist/lib/grammar-entry.d.ts.map +1 -1
- package/dist/lib/grammar-entry.js.map +1 -1
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/parse-context.d.ts +7 -0
- package/dist/lib/parse-context.d.ts.map +1 -1
- package/dist/lib/parse-context.js +9 -0
- package/dist/lib/parse-context.js.map +1 -1
- package/dist/lib/parse-table/table/lr-parse-table.d.ts.map +1 -1
- package/dist/lib/parse-table/table/lr-parse-table.js +10 -3
- package/dist/lib/parse-table/table/lr-parse-table.js.map +1 -1
- package/dist/lib/parser-lr.d.ts +15 -0
- package/dist/lib/parser-lr.d.ts.map +1 -1
- package/dist/lib/parser-lr.js +38 -9
- package/dist/lib/parser-lr.js.map +1 -1
- 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.1";
|
|
3785
3785
|
var packageDefinition = {
|
|
3786
3786
|
version: version$1};
|
|
3787
3787
|
|
|
@@ -5920,6 +5920,80 @@ function buildLalrGotoTargets(grammar, analysis, lr1Collection, lr1ToLalr) {
|
|
|
5920
5920
|
Lr1ItemSetBuilder.buildLr1ToLalrMap = buildLr1ToLalrMap;
|
|
5921
5921
|
})(Lr1ItemSetBuilder || (Lr1ItemSetBuilder = {}));
|
|
5922
5922
|
|
|
5923
|
+
/**
|
|
5924
|
+
* Converts a source offset into a 1-based line and column.
|
|
5925
|
+
*
|
|
5926
|
+
* @param source - Full source text.
|
|
5927
|
+
* @param offset - Zero-based UTF-16 code unit offset.
|
|
5928
|
+
* @returns Line and column for the offset, clamped to the source bounds.
|
|
5929
|
+
*/
|
|
5930
|
+
function offsetToPosition(source, offset) {
|
|
5931
|
+
// Clamp out-of-range offsets to the nearest valid index.
|
|
5932
|
+
const clamped = Math.max(0, Math.min(offset, source.length));
|
|
5933
|
+
let line = 1;
|
|
5934
|
+
let column = 1;
|
|
5935
|
+
// Scan characters before the offset, advancing line on each newline.
|
|
5936
|
+
for (let index = 0; index < clamped; index += 1) {
|
|
5937
|
+
if (source[index] === '\n') {
|
|
5938
|
+
line += 1;
|
|
5939
|
+
column = 1;
|
|
5940
|
+
continue;
|
|
5941
|
+
}
|
|
5942
|
+
column += 1;
|
|
5943
|
+
}
|
|
5944
|
+
return { line, column };
|
|
5945
|
+
}
|
|
5946
|
+
/**
|
|
5947
|
+
* Returns the start offset from a location or raw offset value.
|
|
5948
|
+
*
|
|
5949
|
+
* @param location - Optional source span.
|
|
5950
|
+
* @param offset - Optional raw offset when no span is available.
|
|
5951
|
+
*/
|
|
5952
|
+
function resolveDiagnosticOffset(location, offset) {
|
|
5953
|
+
if (location !== null && location !== undefined) {
|
|
5954
|
+
return location.offset;
|
|
5955
|
+
}
|
|
5956
|
+
if (offset === null || offset === undefined) {
|
|
5957
|
+
return null;
|
|
5958
|
+
}
|
|
5959
|
+
return offset;
|
|
5960
|
+
}
|
|
5961
|
+
|
|
5962
|
+
/**
|
|
5963
|
+
* Formats a diagnostic as `path:line:column: severity: message` when possible.
|
|
5964
|
+
*
|
|
5965
|
+
* @param options - Severity, message, and optional source position inputs.
|
|
5966
|
+
* @returns One diagnostic line without a trailing newline.
|
|
5967
|
+
*/
|
|
5968
|
+
function formatDiagnostic(options) {
|
|
5969
|
+
const prefix = formatDiagnosticLocationPrefix(options);
|
|
5970
|
+
const body = `${options.severity}: ${options.message}`;
|
|
5971
|
+
if (prefix === null) {
|
|
5972
|
+
return body;
|
|
5973
|
+
}
|
|
5974
|
+
return `${prefix}: ${body}`;
|
|
5975
|
+
}
|
|
5976
|
+
/**
|
|
5977
|
+
* Builds the `path:line:column` or `path` prefix for a diagnostic.
|
|
5978
|
+
*
|
|
5979
|
+
* @param options - Path and optional source position inputs.
|
|
5980
|
+
* @returns Location prefix, or null when no path is available.
|
|
5981
|
+
*/
|
|
5982
|
+
function formatDiagnosticLocationPrefix(options) {
|
|
5983
|
+
const path = options.path ?? null;
|
|
5984
|
+
if (path === null || path.length === 0) {
|
|
5985
|
+
return null;
|
|
5986
|
+
}
|
|
5987
|
+
const offset = resolveDiagnosticOffset(options.location, options.offset);
|
|
5988
|
+
const source = options.source ?? null;
|
|
5989
|
+
// Prefer path:line:column when both source text and an offset are known.
|
|
5990
|
+
if (source !== null && offset !== null) {
|
|
5991
|
+
const position = offsetToPosition(source, offset);
|
|
5992
|
+
return `${path}:${String(position.line)}:${String(position.column)}`;
|
|
5993
|
+
}
|
|
5994
|
+
return path;
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5923
5997
|
/**
|
|
5924
5998
|
* LR parse table with ACTION and GOTO entries plus any detected conflicts.
|
|
5925
5999
|
*/
|
|
@@ -5984,13 +6058,19 @@ class LrParseTable {
|
|
|
5984
6058
|
function formatParseConflictWarning(conflict) {
|
|
5985
6059
|
const tokenLabel = formatConflictTokenLabel(conflict.symbol);
|
|
5986
6060
|
if (conflict.kind === 'shift-reduce') {
|
|
5987
|
-
return
|
|
6061
|
+
return formatDiagnostic({
|
|
6062
|
+
severity: 'warning',
|
|
6063
|
+
message: `state ${String(conflict.state)}: shift/reduce conflict on ${tokenLabel} resolved as shift`,
|
|
6064
|
+
});
|
|
5988
6065
|
}
|
|
5989
6066
|
const keptProduction = conflict.existing.kind === 'reduce'
|
|
5990
6067
|
? conflict.existing.productionId
|
|
5991
6068
|
: conflict.incoming.productionId;
|
|
5992
|
-
return
|
|
5993
|
-
|
|
6069
|
+
return formatDiagnostic({
|
|
6070
|
+
severity: 'warning',
|
|
6071
|
+
message: `state ${String(conflict.state)}: reduce/reduce conflict on ${tokenLabel} `
|
|
6072
|
+
+ `resolved using rule ${String(keptProduction)}`,
|
|
6073
|
+
});
|
|
5994
6074
|
}
|
|
5995
6075
|
/**
|
|
5996
6076
|
* Returns a readable terminal label for conflict warning text.
|
|
@@ -7535,6 +7615,42 @@ class ParserLr {
|
|
|
7535
7615
|
}
|
|
7536
7616
|
return parseWithTable(this.table, tokens);
|
|
7537
7617
|
}
|
|
7618
|
+
/**
|
|
7619
|
+
* Parses a token stream and retains syntax-error offset details.
|
|
7620
|
+
*
|
|
7621
|
+
* @param tokens - Token stream ending with `$eof`.
|
|
7622
|
+
* @returns Tree and optional syntax-error details.
|
|
7623
|
+
*/
|
|
7624
|
+
parseResult(tokens) {
|
|
7625
|
+
if (this.table === null) {
|
|
7626
|
+
return {
|
|
7627
|
+
tree: null,
|
|
7628
|
+
errorOffset: 0,
|
|
7629
|
+
errorMessage: 'Parse table has no parser entries',
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
const result = parseWithTableResult(this.table, tokens);
|
|
7633
|
+
if (result.cst === null) {
|
|
7634
|
+
return {
|
|
7635
|
+
tree: null,
|
|
7636
|
+
errorOffset: result.errorOffset,
|
|
7637
|
+
errorMessage: result.errorMessage,
|
|
7638
|
+
};
|
|
7639
|
+
}
|
|
7640
|
+
if (this.grammar.transformSchema === null) {
|
|
7641
|
+
return {
|
|
7642
|
+
tree: result.cst,
|
|
7643
|
+
errorOffset: null,
|
|
7644
|
+
errorMessage: null,
|
|
7645
|
+
};
|
|
7646
|
+
}
|
|
7647
|
+
const tree = transformCst(result.cst, this.grammar.transformSchema, this.table);
|
|
7648
|
+
return {
|
|
7649
|
+
tree,
|
|
7650
|
+
errorOffset: tree === null ? result.errorOffset : null,
|
|
7651
|
+
errorMessage: tree === null ? 'AST transform failed' : null,
|
|
7652
|
+
};
|
|
7653
|
+
}
|
|
7538
7654
|
/**
|
|
7539
7655
|
* Parses a token stream into an AST when transform rules are declared.
|
|
7540
7656
|
*
|
|
@@ -7542,14 +7658,7 @@ class ParserLr {
|
|
|
7542
7658
|
* @returns Transformed AST, CST when no transform section exists, or null on failure.
|
|
7543
7659
|
*/
|
|
7544
7660
|
parseAst(tokens) {
|
|
7545
|
-
|
|
7546
|
-
if (cst === null) {
|
|
7547
|
-
return null;
|
|
7548
|
-
}
|
|
7549
|
-
if (this.grammar.transformSchema === null || this.table === null) {
|
|
7550
|
-
return cst;
|
|
7551
|
-
}
|
|
7552
|
-
return transformCst(cst, this.grammar.transformSchema, this.table);
|
|
7661
|
+
return this.parseResult(tokens).tree;
|
|
7553
7662
|
}
|
|
7554
7663
|
/**
|
|
7555
7664
|
* Parses a token stream, applying CST-to-AST transforms when configured.
|
|
@@ -7680,6 +7789,15 @@ class ParseContext {
|
|
|
7680
7789
|
parse(tokens) {
|
|
7681
7790
|
return this.parser.parse(tokens);
|
|
7682
7791
|
}
|
|
7792
|
+
/**
|
|
7793
|
+
* Parses a token stream and retains syntax-error offset details.
|
|
7794
|
+
*
|
|
7795
|
+
* @param tokens - Token stream ending with `$eof`.
|
|
7796
|
+
* @returns Tree and optional syntax-error details.
|
|
7797
|
+
*/
|
|
7798
|
+
parseResult(tokens) {
|
|
7799
|
+
return this.parser.parseResult(tokens);
|
|
7800
|
+
}
|
|
7683
7801
|
/**
|
|
7684
7802
|
* Lexes and parses source text into an AST, applying transforms when present.
|
|
7685
7803
|
*
|
|
@@ -8031,7 +8149,7 @@ function parseOptionalTransformSection(node) {
|
|
|
8031
8149
|
function parseProduction(node) {
|
|
8032
8150
|
const name = identifierText(requireChild(node, 'identifier'));
|
|
8033
8151
|
const expression = parseExpressionNode(findExpressionChild(node));
|
|
8034
|
-
return { name, expression };
|
|
8152
|
+
return { name, expression, location: node.location };
|
|
8035
8153
|
}
|
|
8036
8154
|
/**
|
|
8037
8155
|
* Parses one AST type (`identifier = expression ;`).
|
|
@@ -8041,7 +8159,7 @@ function parseProduction(node) {
|
|
|
8041
8159
|
function parseAstType(node) {
|
|
8042
8160
|
const name = identifierText(requireChild(node, 'identifier'));
|
|
8043
8161
|
const expression = parseExpressionNode(findExpressionChild(node));
|
|
8044
|
-
return { name, expression };
|
|
8162
|
+
return { name, expression, location: node.location };
|
|
8045
8163
|
}
|
|
8046
8164
|
/**
|
|
8047
8165
|
* Parses one transform rule.
|
|
@@ -8053,6 +8171,7 @@ function parseTransformRule(node) {
|
|
|
8053
8171
|
return {
|
|
8054
8172
|
production,
|
|
8055
8173
|
alternatives: collectRepeated(node, 'labeled_transform').map(parseLabeledTransform),
|
|
8174
|
+
location: node.location,
|
|
8056
8175
|
};
|
|
8057
8176
|
}
|
|
8058
8177
|
/**
|
|
@@ -8063,7 +8182,7 @@ function parseTransformRule(node) {
|
|
|
8063
8182
|
function parseLabeledTransform(node) {
|
|
8064
8183
|
const label = identifierText(requireChild(node, 'identifier'));
|
|
8065
8184
|
const expression = parseTransformExpr(requireChild(node, 'transform_expr'));
|
|
8066
|
-
return { label, expression };
|
|
8185
|
+
return { label, expression, location: node.location };
|
|
8067
8186
|
}
|
|
8068
8187
|
/**
|
|
8069
8188
|
* Parses a transform expression.
|
|
@@ -13539,8 +13658,8 @@ function validateGrammarTable(grammar) {
|
|
|
13539
13658
|
for (const rule of grammar.transformSchema.rules) {
|
|
13540
13659
|
validateTransformProductionExists(rule, bnfProductionNames, issues);
|
|
13541
13660
|
for (const alternative of rule.alternatives) {
|
|
13542
|
-
validateTransformExpression(grammar, alternative
|
|
13543
|
-
collectPassCollapseWarnings(grammar, rule, alternative
|
|
13661
|
+
validateTransformExpression(grammar, alternative, issues);
|
|
13662
|
+
collectPassCollapseWarnings(grammar, rule, alternative, bnf.productions, issues);
|
|
13544
13663
|
}
|
|
13545
13664
|
}
|
|
13546
13665
|
}
|
|
@@ -13559,37 +13678,47 @@ function validateGrammarTable(grammar) {
|
|
|
13559
13678
|
*/
|
|
13560
13679
|
function collectDuplicateDefinitionWarnings(grammar, issues) {
|
|
13561
13680
|
// Flag repeated production names in the grammar section.
|
|
13562
|
-
|
|
13681
|
+
reportDuplicateDefinitions(grammar.productions.map((production) => ({
|
|
13682
|
+
name: production.name,
|
|
13683
|
+
location: production.location ?? null,
|
|
13684
|
+
})), 'grammar', 'production', issues);
|
|
13563
13685
|
// Flag repeated ast type names.
|
|
13564
13686
|
if (grammar.astSchema !== null) {
|
|
13565
|
-
|
|
13687
|
+
reportDuplicateDefinitions(grammar.astSchema.types.map((type) => ({
|
|
13688
|
+
name: type.name,
|
|
13689
|
+
location: type.location ?? null,
|
|
13690
|
+
})), 'ast', 'type', issues);
|
|
13566
13691
|
}
|
|
13567
13692
|
// Flag repeated transform rules for the same production.
|
|
13568
13693
|
if (grammar.transformSchema !== null) {
|
|
13569
|
-
|
|
13694
|
+
reportDuplicateDefinitions(grammar.transformSchema.rules.map((rule) => ({
|
|
13695
|
+
name: rule.production,
|
|
13696
|
+
location: rule.location ?? null,
|
|
13697
|
+
})), 'transform', 'rule', issues);
|
|
13570
13698
|
}
|
|
13571
13699
|
}
|
|
13572
13700
|
/**
|
|
13573
13701
|
* Records a warning for each name that appears more than once.
|
|
13574
13702
|
*
|
|
13575
|
-
* @param
|
|
13703
|
+
* @param definitions - Declared names and locations in source order.
|
|
13576
13704
|
* @param section - Grammar section name for the message.
|
|
13577
13705
|
* @param kind - Declaration kind for the message.
|
|
13578
13706
|
* @param issues - Issue list to append to.
|
|
13579
13707
|
*/
|
|
13580
|
-
function
|
|
13708
|
+
function reportDuplicateDefinitions(definitions, section, kind, issues) {
|
|
13581
13709
|
const seen = new Set();
|
|
13582
13710
|
const reported = new Set();
|
|
13583
|
-
for (const
|
|
13584
|
-
if (seen.has(name) && !reported.has(name)) {
|
|
13711
|
+
for (const definition of definitions) {
|
|
13712
|
+
if (seen.has(definition.name) && !reported.has(definition.name)) {
|
|
13585
13713
|
issues.push({
|
|
13586
13714
|
severity: 'warning',
|
|
13587
|
-
message: `duplicate ${section} ${kind} ${JSON.stringify(name)}; `
|
|
13715
|
+
message: `duplicate ${section} ${kind} ${JSON.stringify(definition.name)}; `
|
|
13588
13716
|
+ `the later definition overrides the earlier one`,
|
|
13717
|
+
location: definition.location,
|
|
13589
13718
|
});
|
|
13590
|
-
reported.add(name);
|
|
13719
|
+
reported.add(definition.name);
|
|
13591
13720
|
}
|
|
13592
|
-
seen.add(name);
|
|
13721
|
+
seen.add(definition.name);
|
|
13593
13722
|
}
|
|
13594
13723
|
}
|
|
13595
13724
|
/**
|
|
@@ -13612,6 +13741,7 @@ function validateTransformProductionExists(rule, bnfProductionNames, issues) {
|
|
|
13612
13741
|
issues.push({
|
|
13613
13742
|
severity: 'error',
|
|
13614
13743
|
message: `transform rule for unknown production ${JSON.stringify(rule.production)}`,
|
|
13744
|
+
location: rule.location ?? null,
|
|
13615
13745
|
});
|
|
13616
13746
|
}
|
|
13617
13747
|
}
|
|
@@ -13629,18 +13759,20 @@ function syntheticRepeatPrefix(name) {
|
|
|
13629
13759
|
* Validates one transform expression against the grammar AST schema.
|
|
13630
13760
|
*
|
|
13631
13761
|
* @param grammar - Parsed grammar model.
|
|
13632
|
-
* @param
|
|
13762
|
+
* @param alternative - Transform alternative owning the expression.
|
|
13633
13763
|
* @param issues - Issue list to append to.
|
|
13634
13764
|
*/
|
|
13635
|
-
function validateTransformExpression(grammar,
|
|
13765
|
+
function validateTransformExpression(grammar, alternative, issues) {
|
|
13766
|
+
const expression = alternative.expression;
|
|
13767
|
+
const location = alternative.location ?? null;
|
|
13636
13768
|
switch (expression.kind) {
|
|
13637
13769
|
case 'build':
|
|
13638
|
-
validateAstTarget(grammar, expression.typeName, expression.variant, issues);
|
|
13770
|
+
validateAstTarget(grammar, expression.typeName, expression.variant, location, issues);
|
|
13639
13771
|
break;
|
|
13640
13772
|
case 'foldLeft':
|
|
13641
13773
|
case 'foldRight':
|
|
13642
13774
|
case 'flatten':
|
|
13643
|
-
validateAstTarget(grammar, expression.typeName, expression.variant, issues);
|
|
13775
|
+
validateAstTarget(grammar, expression.typeName, expression.variant, location, issues);
|
|
13644
13776
|
break;
|
|
13645
13777
|
}
|
|
13646
13778
|
}
|
|
@@ -13650,13 +13782,15 @@ function validateTransformExpression(grammar, expression, issues) {
|
|
|
13650
13782
|
* @param grammar - Parsed grammar model.
|
|
13651
13783
|
* @param typeName - AST type name from a transform expression.
|
|
13652
13784
|
* @param variant - AST variant label from a transform expression.
|
|
13785
|
+
* @param location - Source span of the transform alternative.
|
|
13653
13786
|
* @param issues - Issue list to append to.
|
|
13654
13787
|
*/
|
|
13655
|
-
function validateAstTarget(grammar, typeName, variant, issues) {
|
|
13788
|
+
function validateAstTarget(grammar, typeName, variant, location, issues) {
|
|
13656
13789
|
if (grammar.astSchema === null) {
|
|
13657
13790
|
issues.push({
|
|
13658
13791
|
severity: 'error',
|
|
13659
13792
|
message: `transform references ${typeName}.${variant} but the grammar has no ast section`,
|
|
13793
|
+
location,
|
|
13660
13794
|
});
|
|
13661
13795
|
return;
|
|
13662
13796
|
}
|
|
@@ -13665,6 +13799,7 @@ function validateAstTarget(grammar, typeName, variant, issues) {
|
|
|
13665
13799
|
issues.push({
|
|
13666
13800
|
severity: 'error',
|
|
13667
13801
|
message: `transform references undefined ast type ${JSON.stringify(typeName)}`,
|
|
13802
|
+
location,
|
|
13668
13803
|
});
|
|
13669
13804
|
return;
|
|
13670
13805
|
}
|
|
@@ -13673,6 +13808,7 @@ function validateAstTarget(grammar, typeName, variant, issues) {
|
|
|
13673
13808
|
issues.push({
|
|
13674
13809
|
severity: 'error',
|
|
13675
13810
|
message: `transform references ${typeName}.${variant} which is not declared in ast`,
|
|
13811
|
+
location,
|
|
13676
13812
|
});
|
|
13677
13813
|
}
|
|
13678
13814
|
}
|
|
@@ -13703,11 +13839,12 @@ function collectAstVariants(expression) {
|
|
|
13703
13839
|
*
|
|
13704
13840
|
* @param grammar - Parsed grammar model.
|
|
13705
13841
|
* @param rule - Parent production transform rule.
|
|
13706
|
-
* @param
|
|
13842
|
+
* @param alternative - Transform alternative containing the pass expression.
|
|
13707
13843
|
* @param bnfProductions - Desugared BNF productions.
|
|
13708
13844
|
* @param issues - Issue list to append to.
|
|
13709
13845
|
*/
|
|
13710
|
-
function collectPassCollapseWarnings(grammar, rule,
|
|
13846
|
+
function collectPassCollapseWarnings(grammar, rule, alternative, bnfProductions, issues) {
|
|
13847
|
+
const expression = alternative.expression;
|
|
13711
13848
|
if (expression.kind !== 'pass') {
|
|
13712
13849
|
return;
|
|
13713
13850
|
}
|
|
@@ -13730,6 +13867,7 @@ function collectPassCollapseWarnings(grammar, rule, expression, bnfProductions,
|
|
|
13730
13867
|
message: `pass(${expression.reference}) on ${rule.production} binds ${boundSymbol} `
|
|
13731
13868
|
+ `which has no transform rule and can match a single terminal; `
|
|
13732
13869
|
+ `add a transform for ${boundSymbol} or use build`,
|
|
13870
|
+
location: alternative.location ?? null,
|
|
13733
13871
|
});
|
|
13734
13872
|
}
|
|
13735
13873
|
/**
|
|
@@ -13786,14 +13924,19 @@ function isTerminalSymbol(symbol) {
|
|
|
13786
13924
|
return symbol?.kind === 'terminal' || symbol?.kind === 'token';
|
|
13787
13925
|
}
|
|
13788
13926
|
/**
|
|
13789
|
-
* Formats validation issues as stderr
|
|
13927
|
+
* Formats validation issues as stderr diagnostic lines.
|
|
13790
13928
|
*
|
|
13791
13929
|
* @param issues - Validation issues to format.
|
|
13930
|
+
* @param options - Optional grammar path and source text for line numbers.
|
|
13792
13931
|
*/
|
|
13793
|
-
function formatTableValidationIssues(issues) {
|
|
13794
|
-
return issues.map((issue) =>
|
|
13795
|
-
|
|
13796
|
-
:
|
|
13932
|
+
function formatTableValidationIssues(issues, options = {}) {
|
|
13933
|
+
return issues.map((issue) => formatDiagnostic({
|
|
13934
|
+
severity: issue.severity,
|
|
13935
|
+
message: issue.message,
|
|
13936
|
+
path: options.path,
|
|
13937
|
+
source: options.source,
|
|
13938
|
+
location: issue.location,
|
|
13939
|
+
}));
|
|
13797
13940
|
}
|
|
13798
13941
|
|
|
13799
13942
|
/**
|
|
@@ -13829,6 +13972,58 @@ function parseContextFromSources(options) {
|
|
|
13829
13972
|
throw new ParseContextError('required: grammar source or table JSON');
|
|
13830
13973
|
}
|
|
13831
13974
|
|
|
13975
|
+
/**
|
|
13976
|
+
* Rewrites a thrown source error into a positioned CLI diagnostic error.
|
|
13977
|
+
*
|
|
13978
|
+
* @param error - Thrown value from grammar or input reading.
|
|
13979
|
+
* @param path - Source file path.
|
|
13980
|
+
* @param source - Full source text used for line/column mapping.
|
|
13981
|
+
* @returns Positioned ParserLrError when the failure has an offset; otherwise the original value.
|
|
13982
|
+
*/
|
|
13983
|
+
function locateSourceError(error, path, source) {
|
|
13984
|
+
const offset = sourceErrorOffset(error);
|
|
13985
|
+
if (offset === null) {
|
|
13986
|
+
return error;
|
|
13987
|
+
}
|
|
13988
|
+
return new ParserLrError(formatDiagnostic({
|
|
13989
|
+
severity: 'error',
|
|
13990
|
+
message: stripEmbeddedOffset(errorMessage(error)),
|
|
13991
|
+
path,
|
|
13992
|
+
source,
|
|
13993
|
+
offset,
|
|
13994
|
+
}));
|
|
13995
|
+
}
|
|
13996
|
+
/**
|
|
13997
|
+
* Returns a source offset from a known offset-bearing error.
|
|
13998
|
+
*
|
|
13999
|
+
* @param error - Thrown value to inspect.
|
|
14000
|
+
*/
|
|
14001
|
+
function sourceErrorOffset(error) {
|
|
14002
|
+
if (error instanceof ReadGrammarError || error instanceof LexerError) {
|
|
14003
|
+
return error.offset;
|
|
14004
|
+
}
|
|
14005
|
+
return null;
|
|
14006
|
+
}
|
|
14007
|
+
/**
|
|
14008
|
+
* Returns a human-readable message from a thrown value.
|
|
14009
|
+
*
|
|
14010
|
+
* @param error - Thrown value.
|
|
14011
|
+
*/
|
|
14012
|
+
function errorMessage(error) {
|
|
14013
|
+
if (error instanceof Error) {
|
|
14014
|
+
return error.message;
|
|
14015
|
+
}
|
|
14016
|
+
return String(error);
|
|
14017
|
+
}
|
|
14018
|
+
/**
|
|
14019
|
+
* Removes an embedded `at offset N` suffix so the diagnostic formatter owns the position.
|
|
14020
|
+
*
|
|
14021
|
+
* @param message - Original error message.
|
|
14022
|
+
*/
|
|
14023
|
+
function stripEmbeddedOffset(message) {
|
|
14024
|
+
return message.replace(/\s+at offset \d+\s*$/, '');
|
|
14025
|
+
}
|
|
14026
|
+
|
|
13832
14027
|
/**
|
|
13833
14028
|
* Reads a UTF-8 text file from disk.
|
|
13834
14029
|
*
|
|
@@ -13892,11 +14087,21 @@ function registerParseCommand(program) {
|
|
|
13892
14087
|
}
|
|
13893
14088
|
const context = await loadContextFromPaths(options);
|
|
13894
14089
|
logProgress(`lexing ${options.input}`);
|
|
13895
|
-
const tokens = await context
|
|
14090
|
+
const tokens = await lexInputFile(context, options.input);
|
|
13896
14091
|
logProgress('parsing');
|
|
13897
|
-
const
|
|
14092
|
+
const result = context.parseResult(tokens);
|
|
14093
|
+
if (result.tree === null) {
|
|
14094
|
+
const source = await readTextFile(options.input);
|
|
14095
|
+
throw new ParserLrError(formatDiagnostic({
|
|
14096
|
+
severity: 'error',
|
|
14097
|
+
message: result.errorMessage ?? 'Parse failed',
|
|
14098
|
+
path: options.input,
|
|
14099
|
+
source,
|
|
14100
|
+
offset: result.errorOffset,
|
|
14101
|
+
}));
|
|
14102
|
+
}
|
|
13898
14103
|
logProgress(`formatting output (${options.format})`);
|
|
13899
|
-
const output = formatParseOutput(tree, options.format);
|
|
14104
|
+
const output = formatParseOutput(result.tree, options.format);
|
|
13900
14105
|
// Write output to disk or stdout.
|
|
13901
14106
|
if (options.output !== undefined) {
|
|
13902
14107
|
logProgress(`writing ${options.output}`);
|
|
@@ -13908,6 +14113,21 @@ function registerParseCommand(program) {
|
|
|
13908
14113
|
}
|
|
13909
14114
|
});
|
|
13910
14115
|
}
|
|
14116
|
+
/**
|
|
14117
|
+
* Lexes an input file and rewrites offset-bearing lexer failures with line numbers.
|
|
14118
|
+
*
|
|
14119
|
+
* @param context - Loaded parse context.
|
|
14120
|
+
* @param path - Input file path.
|
|
14121
|
+
*/
|
|
14122
|
+
async function lexInputFile(context, path) {
|
|
14123
|
+
try {
|
|
14124
|
+
return await context.lexChunkStreamAsync(readTextChunks(path));
|
|
14125
|
+
}
|
|
14126
|
+
catch (error) {
|
|
14127
|
+
const source = await readTextFile(path);
|
|
14128
|
+
throw locateSourceError(error, path, source);
|
|
14129
|
+
}
|
|
14130
|
+
}
|
|
13911
14131
|
/**
|
|
13912
14132
|
* Reads grammar or table files and builds a parse context.
|
|
13913
14133
|
*
|
|
@@ -13918,8 +14138,13 @@ async function loadContextFromPaths(options) {
|
|
|
13918
14138
|
if (options.grammar !== undefined) {
|
|
13919
14139
|
logProgress(`reading grammar ${options.grammar}`);
|
|
13920
14140
|
const grammarSource = await readTextFile(options.grammar);
|
|
13921
|
-
|
|
13922
|
-
|
|
14141
|
+
try {
|
|
14142
|
+
logProgress('building parse table');
|
|
14143
|
+
return parseContextFromGrammar(grammarSource);
|
|
14144
|
+
}
|
|
14145
|
+
catch (error) {
|
|
14146
|
+
throw locateSourceError(error, options.grammar, grammarSource);
|
|
14147
|
+
}
|
|
13923
14148
|
}
|
|
13924
14149
|
logProgress(`reading table ${options.table}`);
|
|
13925
14150
|
const tableJson = await readTextFile(options.table);
|
|
@@ -13945,9 +14170,9 @@ function registerTableCommands(program) {
|
|
|
13945
14170
|
.action(async (options) => {
|
|
13946
14171
|
logProgress(`reading grammar ${options.grammar}`);
|
|
13947
14172
|
const grammarSource = await readTextFile(options.grammar);
|
|
14173
|
+
const grammar = readGrammarAtPath(options.grammar, grammarSource);
|
|
13948
14174
|
logProgress(`building ${options.algorithm} parse table`);
|
|
13949
|
-
|
|
13950
|
-
writeGrammarValidationMessages(grammar, false);
|
|
14175
|
+
writeGrammarValidationMessages(grammar, options.grammar, grammarSource, false);
|
|
13951
14176
|
const context = parseContextFromSources({
|
|
13952
14177
|
grammarSource,
|
|
13953
14178
|
algorithm: options.algorithm,
|
|
@@ -13976,27 +14201,45 @@ function registerTableCommands(program) {
|
|
|
13976
14201
|
.action(async (options) => {
|
|
13977
14202
|
logProgress(`reading grammar ${options.grammar}`);
|
|
13978
14203
|
const grammarSource = await readTextFile(options.grammar);
|
|
13979
|
-
const grammar =
|
|
13980
|
-
const exitCode = writeGrammarValidationMessages(grammar, options.strict === true);
|
|
14204
|
+
const grammar = readGrammarAtPath(options.grammar, grammarSource);
|
|
14205
|
+
const exitCode = writeGrammarValidationMessages(grammar, options.grammar, grammarSource, options.strict === true);
|
|
13981
14206
|
process.exitCode = exitCode;
|
|
13982
14207
|
});
|
|
13983
14208
|
}
|
|
14209
|
+
/**
|
|
14210
|
+
* Parses a grammar file and rewrites offset-bearing failures with line numbers.
|
|
14211
|
+
*
|
|
14212
|
+
* @param path - Grammar file path.
|
|
14213
|
+
* @param source - Grammar file text.
|
|
14214
|
+
*/
|
|
14215
|
+
function readGrammarAtPath(path, source) {
|
|
14216
|
+
try {
|
|
14217
|
+
return readGrammar(source);
|
|
14218
|
+
}
|
|
14219
|
+
catch (error) {
|
|
14220
|
+
throw locateSourceError(error, path, source);
|
|
14221
|
+
}
|
|
14222
|
+
}
|
|
13984
14223
|
/**
|
|
13985
14224
|
* Writes grammar validation messages to stderr and returns a process exit code.
|
|
13986
14225
|
*
|
|
13987
14226
|
* @param grammar - Parsed grammar model.
|
|
14227
|
+
* @param path - Grammar file path for diagnostics.
|
|
14228
|
+
* @param source - Grammar file text for line/column mapping.
|
|
13988
14229
|
* @param strict - Whether warnings should fail validation.
|
|
13989
14230
|
*/
|
|
13990
|
-
function writeGrammarValidationMessages(grammar, strict) {
|
|
14231
|
+
function writeGrammarValidationMessages(grammar, path, source, strict) {
|
|
13991
14232
|
const issues = validateGrammarTable(grammar);
|
|
13992
14233
|
let hasError = false;
|
|
13993
14234
|
let hasWarning = false;
|
|
13994
|
-
for (const line of formatTableValidationIssues(issues)) {
|
|
14235
|
+
for (const line of formatTableValidationIssues(issues, { path, source })) {
|
|
13995
14236
|
process.stderr.write(`${line}\n`);
|
|
13996
|
-
|
|
14237
|
+
}
|
|
14238
|
+
for (const issue of issues) {
|
|
14239
|
+
if (issue.severity === 'error') {
|
|
13997
14240
|
hasError = true;
|
|
13998
14241
|
}
|
|
13999
|
-
if (
|
|
14242
|
+
if (issue.severity === 'warning') {
|
|
14000
14243
|
hasWarning = true;
|
|
14001
14244
|
}
|
|
14002
14245
|
}
|