exports.js 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { adapt_parser, VALUE } from './parser.js';
  2. import { Discard, FirstMatch, Optional, Repeat, Sequence } from './parsers/combinators.js';
  3. import { Fail, Literal, None, StringOf, StringUntil, Symbol } from './parsers/terminals.js';
  4. class ParserWithAction {
  5. #parser;
  6. #action;
  7. constructor(parser, action) {
  8. this.#parser = adapt_parser(parser);
  9. this.#action = action;
  10. }
  11. parse (stream) {
  12. const parsed = this.#parser.parse(stream);
  13. if (parsed.status === VALUE) {
  14. parsed.value = this.#action(parsed.value);
  15. }
  16. return parsed;
  17. }
  18. }
  19. export class GrammarContext {
  20. constructor (parsers) {
  21. // Object of { parser_name: Parser, ... }
  22. this.parsers = parsers;
  23. }
  24. sub (more_parsers) {
  25. return new GrammarContext({...this.parsers, ...more_parsers});
  26. }
  27. /**
  28. * Construct a parsing function for the given grammar.
  29. * @param grammar An object of symbol-names to a DSL for parsing that symbol.
  30. * @param actions An object of symbol-names to a function run to process the symbol after it has been parsed.
  31. * @returns {function(*, *, {must_consume_all_input?: boolean}=): *} A function to run the parser. Throws if parsing fails.
  32. */
  33. define_parser (grammar, actions) {
  34. const symbol_registry = {};
  35. const api = {};
  36. for (const [name, parserCls] of Object.entries(this.parsers)) {
  37. api[name] = (...params) => {
  38. const result = new parserCls();
  39. result._create(...params);
  40. result.set_symbol_registry(symbol_registry);
  41. return result;
  42. };
  43. }
  44. for (const [name, builder] of Object.entries(grammar)) {
  45. if (actions[name]) {
  46. symbol_registry[name] = new ParserWithAction(builder(api), actions[name]);
  47. } else {
  48. symbol_registry[name] = builder(api);
  49. }
  50. }
  51. return (stream, entry_symbol, { must_consume_all_input = true } = {}) => {
  52. const entry_parser = symbol_registry[entry_symbol];
  53. if (!entry_parser) {
  54. throw new Error(`Entry symbol '${entry_symbol}' not found in grammar.`);
  55. }
  56. const result = entry_parser.parse(stream);
  57. if (result.status !== VALUE) {
  58. throw new Error('Failed to parse input against grammar.');
  59. }
  60. // Ensure the entire stream is consumed.
  61. if (must_consume_all_input && !stream.is_eof()) {
  62. throw new Error('Parsing did not consume all input.');
  63. }
  64. return result;
  65. };
  66. }
  67. }
  68. export const standard_parsers = () => {
  69. return {
  70. discard: Discard,
  71. fail: Fail,
  72. firstMatch: FirstMatch,
  73. literal: Literal,
  74. none: None,
  75. optional: Optional,
  76. repeat: Repeat,
  77. sequence: Sequence,
  78. stringOf: StringOf,
  79. stringUntil: StringUntil,
  80. symbol: Symbol,
  81. }
  82. }