Compare commits
4 commits
063584fb7e
...
13f1353134
| Author | SHA1 | Date | |
|---|---|---|---|
| 13f1353134 | |||
| c44083b610 | |||
| 2828d13e3a | |||
| 7206298cd1 |
5 changed files with 66 additions and 29 deletions
|
|
@ -88,7 +88,7 @@ function render_state(state, input_editor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.output_mode === "errors") {
|
if (state.output_mode === "errors") {
|
||||||
const error_node = document.createElement("pre");
|
const error_node = document.createElement("div");
|
||||||
error_node.classList.add("error-panel");
|
error_node.classList.add("error-panel");
|
||||||
if (state.errors.length == 0) {
|
if (state.errors.length == 0) {
|
||||||
if (state.tree) {
|
if (state.tree) {
|
||||||
|
|
@ -97,7 +97,13 @@ function render_state(state, input_editor) {
|
||||||
error_node.innerText = "No errors.";
|
error_node.innerText = "No errors.";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error_node.innerText = state.errors.join("\n");
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
OUTPUT.replaceChildren(error_node);
|
OUTPUT.replaceChildren(error_node);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import pathlib
|
||||||
|
import socket
|
||||||
|
|
||||||
from http.server import test, SimpleHTTPRequestHandler, ThreadingHTTPServer
|
from http.server import test, SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
|
||||||
|
|
@ -35,22 +36,9 @@ if __name__ == "__main__":
|
||||||
metavar="ADDRESS",
|
metavar="ADDRESS",
|
||||||
help="bind to this address " "(default: all interfaces)",
|
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(
|
parser.add_argument(
|
||||||
"port",
|
"port",
|
||||||
default=8000,
|
default=8086,
|
||||||
type=int,
|
type=int,
|
||||||
nargs="?",
|
nargs="?",
|
||||||
help="bind to this port " "(default: %(default)s)",
|
help="bind to this port " "(default: %(default)s)",
|
||||||
|
|
@ -58,6 +46,8 @@ if __name__ == "__main__":
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
handler_class = MyHTTPRequestHandler
|
handler_class = MyHTTPRequestHandler
|
||||||
|
|
||||||
|
directory = pathlib.Path(__file__).parent
|
||||||
|
|
||||||
# ensure dual-stack is not disabled; ref #38907
|
# ensure dual-stack is not disabled; ref #38907
|
||||||
class DualStackServer(ThreadingHTTPServer):
|
class DualStackServer(ThreadingHTTPServer):
|
||||||
|
|
||||||
|
|
@ -68,12 +58,12 @@ if __name__ == "__main__":
|
||||||
return super().server_bind()
|
return super().server_bind()
|
||||||
|
|
||||||
def finish_request(self, request, client_address):
|
def finish_request(self, request, client_address):
|
||||||
self.RequestHandlerClass(request, client_address, self, directory=args.directory)
|
self.RequestHandlerClass(request, client_address, self, directory=str(directory))
|
||||||
|
|
||||||
test(
|
test(
|
||||||
HandlerClass=handler_class,
|
HandlerClass=handler_class,
|
||||||
ServerClass=DualStackServer,
|
ServerClass=DualStackServer,
|
||||||
port=args.port,
|
port=args.port,
|
||||||
bind=args.bind,
|
bind=args.bind,
|
||||||
protocol=args.protocol,
|
protocol="HTTP/1.0",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
1
makefile
1
makefile
|
|
@ -27,6 +27,7 @@ clean:
|
||||||
|
|
||||||
.PHONY: dingus
|
.PHONY: dingus
|
||||||
dingus: dingus/wheel/lrparsers-$(VERSION)-py3-none-any.whl
|
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
|
dingus/wheel/lrparsers-$(VERSION)-py3-none-any.whl: dist/lrparsers-$(VERSION)-py3-none-any.whl
|
||||||
cp $< $@
|
cp $< $@
|
||||||
|
|
|
||||||
|
|
@ -552,8 +552,9 @@ class ParseTable:
|
||||||
actions: list[dict[str, ParseAction]]
|
actions: list[dict[str, ParseAction]]
|
||||||
gotos: list[dict[str, int]]
|
gotos: list[dict[str, int]]
|
||||||
trivia: set[str]
|
trivia: set[str]
|
||||||
|
error_names: dict[str, str]
|
||||||
|
|
||||||
def format(self):
|
def format(self) -> str:
|
||||||
"""Format a parser table so pretty."""
|
"""Format a parser table so pretty."""
|
||||||
|
|
||||||
def format_action(actions: dict[str, ParseAction], terminal: str):
|
def format_action(actions: dict[str, ParseAction], terminal: str):
|
||||||
|
|
@ -642,7 +643,7 @@ class TableBuilder(object):
|
||||||
if error is not None:
|
if error is not None:
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
return ParseTable(actions=self.actions, gotos=self.gotos, trivia=set())
|
return ParseTable(actions=self.actions, gotos=self.gotos, trivia=set(), error_names={})
|
||||||
|
|
||||||
def new_row(self, config_set: ItemSet):
|
def new_row(self, config_set: ItemSet):
|
||||||
"""Start a new row, processing the given config set. Call this before
|
"""Start a new row, processing the given config set. Call this before
|
||||||
|
|
@ -1582,12 +1583,21 @@ class Terminal(Rule):
|
||||||
pattern: "str | Re"
|
pattern: "str | Re"
|
||||||
meta: dict[str, typing.Any]
|
meta: dict[str, typing.Any]
|
||||||
regex: bool
|
regex: bool
|
||||||
|
error_name: str | None
|
||||||
|
|
||||||
def __init__(self, pattern: "str|Re", *, name: str | None = None, **kwargs):
|
def __init__(
|
||||||
|
self,
|
||||||
|
pattern: "str|Re",
|
||||||
|
*,
|
||||||
|
name: str | None = None,
|
||||||
|
error_name: str | None = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.pattern = pattern
|
self.pattern = pattern
|
||||||
self.meta = kwargs
|
self.meta = kwargs
|
||||||
self.regex = isinstance(pattern, Re)
|
self.regex = isinstance(pattern, Re)
|
||||||
|
self.error_name = error_name
|
||||||
|
|
||||||
def flatten(
|
def flatten(
|
||||||
self, with_metadata: bool = False
|
self, with_metadata: bool = False
|
||||||
|
|
@ -1611,12 +1621,14 @@ class NonTerminal(Rule):
|
||||||
fn: typing.Callable[["Grammar"], Rule]
|
fn: typing.Callable[["Grammar"], Rule]
|
||||||
name: str
|
name: str
|
||||||
transparent: bool
|
transparent: bool
|
||||||
|
error_name: str | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
fn: typing.Callable[["Grammar"], Rule],
|
fn: typing.Callable[["Grammar"], Rule],
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
transparent: bool = False,
|
transparent: bool = False,
|
||||||
|
error_name: str | None = None,
|
||||||
):
|
):
|
||||||
"""Create a new NonTerminal.
|
"""Create a new NonTerminal.
|
||||||
|
|
||||||
|
|
@ -1624,10 +1636,16 @@ class NonTerminal(Rule):
|
||||||
right-hand-side of this production; it will be flattened with `flatten`.
|
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
|
`name` is the name of the production- if unspecified (or `None`) it will
|
||||||
be replaced with the `__name__` of the provided fn.
|
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.fn = fn
|
||||||
self.name = name or fn.__name__
|
self.name = name or fn.__name__
|
||||||
self.transparent = transparent
|
self.transparent = transparent
|
||||||
|
self.error_name = error_name
|
||||||
|
|
||||||
def generate_body(self, grammar) -> list[list[str | Terminal]]:
|
def generate_body(self, grammar) -> list[list[str | Terminal]]:
|
||||||
"""Generate the body of the non-terminal.
|
"""Generate the body of the non-terminal.
|
||||||
|
|
@ -1763,12 +1781,16 @@ def rule(f: typing.Callable, /) -> Rule: ...
|
||||||
|
|
||||||
@typing.overload
|
@typing.overload
|
||||||
def rule(
|
def rule(
|
||||||
name: str | None = None, transparent: bool | None = None
|
name: str | None = None,
|
||||||
|
transparent: bool | None = None,
|
||||||
|
error_name: str | None = None,
|
||||||
) -> typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]: ...
|
) -> typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]: ...
|
||||||
|
|
||||||
|
|
||||||
def rule(
|
def rule(
|
||||||
name: str | None | typing.Callable = None, transparent: bool | None = None
|
name: str | None | typing.Callable = None,
|
||||||
|
transparent: bool | None = None,
|
||||||
|
error_name: str | None = None,
|
||||||
) -> Rule | typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]:
|
) -> Rule | typing.Callable[[typing.Callable[[typing.Any], Rule]], Rule]:
|
||||||
"""The decorator that marks a method in a Grammar object as a nonterminal
|
"""The decorator that marks a method in a Grammar object as a nonterminal
|
||||||
rule.
|
rule.
|
||||||
|
|
@ -1783,6 +1805,7 @@ def rule(
|
||||||
def wrapper(f: typing.Callable[[typing.Any], Rule]):
|
def wrapper(f: typing.Callable[[typing.Any], Rule]):
|
||||||
nonlocal name
|
nonlocal name
|
||||||
nonlocal transparent
|
nonlocal transparent
|
||||||
|
nonlocal error_name
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
name = f.__name__
|
name = f.__name__
|
||||||
|
|
@ -1791,7 +1814,7 @@ def rule(
|
||||||
if transparent is None:
|
if transparent is None:
|
||||||
transparent = name.startswith("_")
|
transparent = name.startswith("_")
|
||||||
|
|
||||||
return NonTerminal(f, name, transparent)
|
return NonTerminal(f, name, transparent, error_name)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
@ -2969,6 +2992,17 @@ class Grammar:
|
||||||
assert t.name is not None
|
assert t.name is not None
|
||||||
table.trivia.add(t.name)
|
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
|
return table
|
||||||
|
|
||||||
def compile_lexer(self) -> LexerTable:
|
def compile_lexer(self) -> LexerTable:
|
||||||
|
|
|
||||||
|
|
@ -404,6 +404,9 @@ class Parser:
|
||||||
def __init__(self, table: parser.ParseTable):
|
def __init__(self, table: parser.ParseTable):
|
||||||
self.table = table
|
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]]:
|
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
|
"""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
|
(if any could be found) and a list of errors that were encountered during
|
||||||
|
|
@ -527,7 +530,8 @@ class Parser:
|
||||||
# See if we can figure out what we were working on here,
|
# See if we can figure out what we were working on here,
|
||||||
# for the error message.
|
# for the error message.
|
||||||
if production_message is None and len(repair.reductions) > 0:
|
if production_message is None and len(repair.reductions) > 0:
|
||||||
production_message = f"while parsing {repair.reductions[0]}"
|
reduction = repair.reductions[-1]
|
||||||
|
production_message = f"while parsing {self.readable(reduction)}"
|
||||||
|
|
||||||
match repair.repair:
|
match repair.repair:
|
||||||
case RepairAction.Base:
|
case RepairAction.Base:
|
||||||
|
|
@ -553,7 +557,9 @@ class Parser:
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
|
||||||
if token_message is None:
|
if token_message is None:
|
||||||
token_message = f"Expected {repair.value}"
|
token_message = (
|
||||||
|
f"(Did you forget {self.readable(repair.value)}?)"
|
||||||
|
)
|
||||||
|
|
||||||
case RepairAction.Delete:
|
case RepairAction.Delete:
|
||||||
del input[cursor]
|
del input[cursor]
|
||||||
|
|
@ -567,10 +573,10 @@ class Parser:
|
||||||
|
|
||||||
# Add the extra information about what we were looking for
|
# Add the extra information about what we were looking for
|
||||||
# here.
|
# here.
|
||||||
if token_message is not None:
|
|
||||||
error_message = f"{error_message}. {token_message}"
|
|
||||||
if production_message is not None:
|
if production_message is not None:
|
||||||
error_message = f"{error_message} {production_message}"
|
error_message = f"{error_message} {production_message}"
|
||||||
|
if token_message is not None:
|
||||||
|
error_message = f"{error_message}. {token_message}"
|
||||||
errors.append(
|
errors.append(
|
||||||
ParseError(
|
ParseError(
|
||||||
message=error_message,
|
message=error_message,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue