Allow for text to follow tokens in pretty-printing
It's weird that it counts against the line length though, like if you were going to break you could ignore it right? At least, for the grammar I'm working here....
This commit is contained in:
parent
d6dd54f4df
commit
276449287d
2 changed files with 42 additions and 7 deletions
|
|
@ -36,6 +36,11 @@ class Text:
|
||||||
end: int
|
end: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class Literal:
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class Group:
|
class Group:
|
||||||
child: "Document"
|
child: "Document"
|
||||||
|
|
@ -55,7 +60,7 @@ class Lazy:
|
||||||
return Lazy(lambda: printer.convert_tree_to_document(tree))
|
return Lazy(lambda: printer.convert_tree_to_document(tree))
|
||||||
|
|
||||||
|
|
||||||
Document = None | Text | NewLine | Cons | Indent | Group | Lazy
|
Document = None | Text | Literal | NewLine | Cons | Indent | Group | Lazy
|
||||||
|
|
||||||
|
|
||||||
class DocumentLayout:
|
class DocumentLayout:
|
||||||
|
|
@ -111,6 +116,9 @@ def layout_document(doc: Document, width: int) -> DocumentLayout:
|
||||||
case Text(start, end):
|
case Text(start, end):
|
||||||
remaining -= end - start
|
remaining -= end - start
|
||||||
|
|
||||||
|
case Literal(text):
|
||||||
|
remaining -= len(text)
|
||||||
|
|
||||||
case NewLine():
|
case NewLine():
|
||||||
if chunk.flat:
|
if chunk.flat:
|
||||||
# These are newlines that have been rendered flat,
|
# These are newlines that have been rendered flat,
|
||||||
|
|
@ -163,6 +171,10 @@ def layout_document(doc: Document, width: int) -> DocumentLayout:
|
||||||
output.append((start, end))
|
output.append((start, end))
|
||||||
column += end - start
|
column += end - start
|
||||||
|
|
||||||
|
case Literal(text):
|
||||||
|
output.append(text)
|
||||||
|
column += len(text)
|
||||||
|
|
||||||
case NewLine():
|
case NewLine():
|
||||||
if chunk.flat:
|
if chunk.flat:
|
||||||
# TODO: Custom newline flat mode. See also the
|
# TODO: Custom newline flat mode. See also the
|
||||||
|
|
@ -226,10 +238,17 @@ def child_to_name(child: runtime.Tree | runtime.TokenValue) -> str:
|
||||||
class Matcher:
|
class Matcher:
|
||||||
table: parser.ParseTable
|
table: parser.ParseTable
|
||||||
indent_amounts: dict[str, int]
|
indent_amounts: dict[str, int]
|
||||||
|
text_follow: dict[str, str]
|
||||||
|
|
||||||
def __init__(self, table: parser.ParseTable, indent_amounts):
|
def __init__(
|
||||||
|
self,
|
||||||
|
table: parser.ParseTable,
|
||||||
|
indent_amounts: dict[str, int],
|
||||||
|
text_follow: dict[str, str],
|
||||||
|
):
|
||||||
self.table = table
|
self.table = table
|
||||||
self.indent_amounts = indent_amounts
|
self.indent_amounts = indent_amounts
|
||||||
|
self.text_follow = text_follow
|
||||||
|
|
||||||
def match(self, printer: "Printer", items: list[runtime.Tree | runtime.TokenValue]) -> Document:
|
def match(self, printer: "Printer", items: list[runtime.Tree | runtime.TokenValue]) -> Document:
|
||||||
stack: list[tuple[int, Document]] = [(0, None)]
|
stack: list[tuple[int, Document]] = [(0, None)]
|
||||||
|
|
@ -272,7 +291,7 @@ class Matcher:
|
||||||
child = cons(NewLine(), child)
|
child = cons(NewLine(), child)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pass # ???
|
pass # Reducing a transparent rule probably.
|
||||||
|
|
||||||
goto = self.table.gotos[stack[-1][0]].get(name)
|
goto = self.table.gotos[stack[-1][0]].get(name)
|
||||||
assert goto is not None
|
assert goto is not None
|
||||||
|
|
@ -280,10 +299,18 @@ class Matcher:
|
||||||
|
|
||||||
case parser.Shift():
|
case parser.Shift():
|
||||||
value = current_token[1]
|
value = current_token[1]
|
||||||
|
|
||||||
|
follow = None
|
||||||
if isinstance(value, runtime.Tree):
|
if isinstance(value, runtime.Tree):
|
||||||
child = Lazy.from_tree(value, printer)
|
child = Lazy.from_tree(value, printer)
|
||||||
|
if value.name:
|
||||||
|
follow = self.text_follow.get(value.name)
|
||||||
else:
|
else:
|
||||||
child = Text(value.start, value.end)
|
child = Text(value.start, value.end)
|
||||||
|
follow = self.text_follow.get(value.kind)
|
||||||
|
|
||||||
|
if follow is not None:
|
||||||
|
child = cons(child, Literal(follow))
|
||||||
|
|
||||||
stack.append((action.state, child))
|
stack.append((action.state, child))
|
||||||
input_index += 1
|
input_index += 1
|
||||||
|
|
@ -296,6 +323,7 @@ class Printer:
|
||||||
# TODO: Pre-generate the matcher tables for a grammar, to make it
|
# TODO: Pre-generate the matcher tables for a grammar, to make it
|
||||||
# possible to do codegen in other languages.
|
# possible to do codegen in other languages.
|
||||||
grammar: parser.Grammar
|
grammar: parser.Grammar
|
||||||
|
_text_follow: dict[str, str]
|
||||||
_matchers: dict[str, Matcher]
|
_matchers: dict[str, Matcher]
|
||||||
_nonterminals: dict[str, parser.NonTerminal]
|
_nonterminals: dict[str, parser.NonTerminal]
|
||||||
|
|
||||||
|
|
@ -304,6 +332,13 @@ class Printer:
|
||||||
self._nonterminals = {nt.name: nt for nt in grammar.non_terminals()}
|
self._nonterminals = {nt.name: nt for nt in grammar.non_terminals()}
|
||||||
self._matchers = {}
|
self._matchers = {}
|
||||||
|
|
||||||
|
text_follow = {}
|
||||||
|
for terminal in self.grammar.terminals():
|
||||||
|
follow = terminal.meta.get("format_follow")
|
||||||
|
if isinstance(follow, str):
|
||||||
|
text_follow[terminal.name] = follow
|
||||||
|
self._text_follow = text_follow
|
||||||
|
|
||||||
def lookup_nonterminal(self, name: str) -> parser.NonTerminal:
|
def lookup_nonterminal(self, name: str) -> parser.NonTerminal:
|
||||||
return self._nonterminals[name]
|
return self._nonterminals[name]
|
||||||
|
|
||||||
|
|
@ -385,7 +420,7 @@ class Printer:
|
||||||
gen = self.grammar._generator(rule.name, generated_grammar)
|
gen = self.grammar._generator(rule.name, generated_grammar)
|
||||||
parse_table = gen.gen_table()
|
parse_table = gen.gen_table()
|
||||||
|
|
||||||
return Matcher(parse_table, indent_amounts)
|
return Matcher(parse_table, indent_amounts, self._text_follow)
|
||||||
|
|
||||||
def rule_to_matcher(self, rule: parser.NonTerminal) -> Matcher:
|
def rule_to_matcher(self, rule: parser.NonTerminal) -> Matcher:
|
||||||
result = self._matchers.get(rule.name)
|
result = self._matchers.get(rule.name)
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class JsonGrammar(Grammar):
|
||||||
LCURLY = Terminal("{")
|
LCURLY = Terminal("{")
|
||||||
RCURLY = Terminal("}")
|
RCURLY = Terminal("}")
|
||||||
COMMA = Terminal(",")
|
COMMA = Terminal(",")
|
||||||
COLON = Terminal(":")
|
COLON = Terminal(":", format_follow=" ")
|
||||||
LSQUARE = Terminal("[")
|
LSQUARE = Terminal("[")
|
||||||
RSQUARE = Terminal("]")
|
RSQUARE = Terminal("]")
|
||||||
TRUE = Terminal("true")
|
TRUE = Terminal("true")
|
||||||
|
|
@ -164,8 +164,8 @@ def test_layout_basic():
|
||||||
result
|
result
|
||||||
== """
|
== """
|
||||||
{
|
{
|
||||||
"a":true,
|
"a": true,
|
||||||
"b":[
|
"b": [
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
3
|
3
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue