diff --git a/dingus/dingus.js b/dingus/dingus.js index d019ac6..7aab450 100644 --- a/dingus/dingus.js +++ b/dingus/dingus.js @@ -88,7 +88,7 @@ function render_state(state, input_editor) { } if (state.output_mode === "errors") { - const error_node = document.createElement("div"); + const error_node = document.createElement("pre"); error_node.classList.add("error-panel"); if (state.errors.length == 0) { if (state.tree) { @@ -97,13 +97,7 @@ function render_state(state, input_editor) { error_node.innerText = "No errors."; } } else { - const ul = document.createElement("ul"); - ul.replaceChildren(...state.errors.map(e => { - const li = document.createElement("li"); - li.innerText = e; - return li; - })); - error_node.appendChild(ul); + error_node.innerText = state.errors.join("\n"); } OUTPUT.replaceChildren(error_node); diff --git a/dingus/srvit.py b/dingus/srvit.py index e4d8a72..31b0c81 100755 --- a/dingus/srvit.py +++ b/dingus/srvit.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -import pathlib -import socket +import os from http.server import test, SimpleHTTPRequestHandler, ThreadingHTTPServer @@ -36,9 +35,22 @@ if __name__ == "__main__": metavar="ADDRESS", help="bind to this address " "(default: all interfaces)", ) + parser.add_argument( + "-d", + "--directory", + default=os.getcwd(), + help="serve this directory " "(default: current directory)", + ) + parser.add_argument( + "-p", + "--protocol", + metavar="VERSION", + default="HTTP/1.0", + help="conform to this HTTP version " "(default: %(default)s)", + ) parser.add_argument( "port", - default=8086, + default=8000, type=int, nargs="?", help="bind to this port " "(default: %(default)s)", @@ -46,8 +58,6 @@ if __name__ == "__main__": args = parser.parse_args() handler_class = MyHTTPRequestHandler - directory = pathlib.Path(__file__).parent - # ensure dual-stack is not disabled; ref #38907 class DualStackServer(ThreadingHTTPServer): @@ -58,12 +68,12 @@ if __name__ == "__main__": return super().server_bind() def finish_request(self, request, client_address): - self.RequestHandlerClass(request, client_address, self, directory=str(directory)) + self.RequestHandlerClass(request, client_address, self, directory=args.directory) test( HandlerClass=handler_class, ServerClass=DualStackServer, port=args.port, bind=args.bind, - protocol="HTTP/1.0", + protocol=args.protocol, ) diff --git a/makefile b/makefile index a6d04c0..3b9c4fb 100644 --- a/makefile +++ b/makefile @@ -27,7 +27,6 @@ clean: .PHONY: dingus dingus: dingus/wheel/lrparsers-$(VERSION)-py3-none-any.whl - python3 ./dingus/srvit.py dingus/wheel/lrparsers-$(VERSION)-py3-none-any.whl: dist/lrparsers-$(VERSION)-py3-none-any.whl cp $< $@ diff --git a/parser/parser.py b/parser/parser.py index 13f41c1..e0c8c3f 100644 --- a/parser/parser.py +++ b/parser/parser.py @@ -552,9 +552,8 @@ class ParseTable: actions: list[dict[str, ParseAction]] gotos: list[dict[str, int]] trivia: set[str] - error_names: dict[str, str] - def format(self) -> str: + def format(self): """Format a parser table so pretty.""" def format_action(actions: dict[str, ParseAction], terminal: str): @@ -643,7 +642,7 @@ class TableBuilder(object): if error is not None: raise error - return ParseTable(actions=self.actions, gotos=self.gotos, trivia=set(), error_names={}) + return ParseTable(actions=self.actions, gotos=self.gotos, trivia=set()) def new_row(self, config_set: ItemSet): """Start a new row, processing the given config set. Call this before @@ -1583,21 +1582,12 @@ class Terminal(Rule): pattern: "str | Re" meta: dict[str, typing.Any] regex: bool - error_name: str | None - def __init__( - self, - pattern: "str|Re", - *, - name: str | None = None, - error_name: str | None = None, - **kwargs, - ): + def __init__(self, pattern: "str|Re", *, name: str | None = None, **kwargs): self.name = name self.pattern = pattern self.meta = kwargs self.regex = isinstance(pattern, Re) - self.error_name = error_name def flatten( self, with_metadata: bool = False @@ -1621,14 +1611,12 @@ class NonTerminal(Rule): fn: typing.Callable[["Grammar"], Rule] name: str transparent: bool - error_name: str | None def __init__( self, fn: typing.Callable[["Grammar"], Rule], name: str | None = None, transparent: bool = False, - error_name: str | None = None, ): """Create a new NonTerminal. @@ -1636,16 +1624,10 @@ class NonTerminal(Rule): right-hand-side of this production; it will be flattened with `flatten`. `name` is the name of the production- if unspecified (or `None`) it will be replaced with the `__name__` of the provided fn. - - error_name is a human-readable name, to be shown in error messages. Use - this to fine-tune error messages. (For example, maybe you want your - nonterminal to be named "expr" but in error messages it should be - be spelled out: "expression".) """ self.fn = fn self.name = name or fn.__name__ self.transparent = transparent - self.error_name = error_name def generate_body(self, grammar) -> list[list[str | Terminal]]: """Generate the body of the non-terminal. @@ -1781,16 +1763,12 @@ def rule(f: typing.Callable, /) -> Rule: ... @typing.overload def rule( - name: str | None = None, - transparent: bool | None = None, - error_name: str | None = None, + name: str | None = None, transparent: bool | None = None ) -> typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]: ... def rule( - name: str | None | typing.Callable = None, - transparent: bool | None = None, - error_name: str | None = None, + name: str | None | typing.Callable = None, transparent: bool | None = None ) -> Rule | typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]: """The decorator that marks a method in a Grammar object as a nonterminal rule. @@ -1805,7 +1783,6 @@ def rule( def wrapper(f: typing.Callable[[typing.Any], Rule]): nonlocal name nonlocal transparent - nonlocal error_name if name is None: name = f.__name__ @@ -1814,7 +1791,7 @@ def rule( if transparent is None: transparent = name.startswith("_") - return NonTerminal(f, name, transparent, error_name) + return NonTerminal(f, name, transparent) return wrapper @@ -2992,17 +2969,6 @@ class Grammar: assert t.name is not None table.trivia.add(t.name) - for nt in self._nonterminals.values(): - if nt.error_name is not None: - table.error_names[nt.name] = nt.error_name - - for t in self._terminals.values(): - if t.name is not None: - if t.error_name is not None: - table.error_names[t.name] = t.error_name - elif isinstance(t.pattern, str): - table.error_names[t.name] = f'"{t.pattern}"' - return table def compile_lexer(self) -> LexerTable: diff --git a/parser/runtime.py b/parser/runtime.py index a74276a..4c5d8db 100644 --- a/parser/runtime.py +++ b/parser/runtime.py @@ -404,9 +404,6 @@ class Parser: def __init__(self, table: parser.ParseTable): self.table = table - def readable(self, token_kind: str) -> str: - return self.table.error_names.get(token_kind, token_kind) - def parse(self, tokens: TokenStream) -> typing.Tuple[Tree | None, list[str]]: """Parse a token stream into a tree, returning both the root of the tree (if any could be found) and a list of errors that were encountered during @@ -530,8 +527,7 @@ class Parser: # See if we can figure out what we were working on here, # for the error message. if production_message is None and len(repair.reductions) > 0: - reduction = repair.reductions[-1] - production_message = f"while parsing {self.readable(reduction)}" + production_message = f"while parsing {repair.reductions[0]}" match repair.repair: case RepairAction.Base: @@ -557,9 +553,7 @@ class Parser: cursor += 1 if token_message is None: - token_message = ( - f"(Did you forget {self.readable(repair.value)}?)" - ) + token_message = f"Expected {repair.value}" case RepairAction.Delete: del input[cursor] @@ -573,10 +567,10 @@ class Parser: # Add the extra information about what we were looking for # here. - if production_message is not None: - error_message = f"{error_message} {production_message}" if token_message is not None: error_message = f"{error_message}. {token_message}" + if production_message is not None: + error_message = f"{error_message} {production_message}" errors.append( ParseError( message=error_message,