Skip to content

Commit e807954

Browse files
committed
Implement symbolic labels, optimize worker with select
1 parent 2dd75bb commit e807954

File tree

5 files changed

+281
-267
lines changed

5 files changed

+281
-267
lines changed

python/src/mlogv32/preprocessor/app.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@
2525
from .filters import FILTERS, ram_var
2626
from .models import BuildConfig
2727
from .parser import (
28-
DirectiveError,
29-
Statement,
28+
MlogError,
3029
check_unsaved_variables,
30+
count_statements,
3131
iter_labels,
3232
parse_mlog,
33+
replace_symbolic_labels,
3334
)
3435
from .types import ConfigArgs, ConfigsYaml, Labels
3536

@@ -101,11 +102,11 @@ def build(
101102
size: Annotated[int | None, Option("-s", "--size")] = None,
102103
output: Annotated[Path | None, Option("-o", "--output")] = None,
103104
bin_path: Annotated[Path | None, Option("--bin")] = None,
104-
include_all: Annotated[bool, Option("--all")] = False,
105-
include_cpu: Annotated[bool, Option("--cpu")] = False,
106-
include_peripherals: Annotated[bool, Option("--peripherals")] = False,
107-
include_memory: Annotated[bool, Option("--memory")] = False,
108-
include_debugger: Annotated[bool, Option("--debugger")] = False,
105+
include_all: Annotated[bool, Option("-A", "--all")] = False,
106+
include_cpu: Annotated[bool, Option("-C", "--cpu")] = False,
107+
include_peripherals: Annotated[bool, Option("-P", "--peripherals")] = False,
108+
include_memory: Annotated[bool, Option("-M", "--memory")] = False,
109+
include_debugger: Annotated[bool, Option("-D", "--debugger")] = False,
109110
include_keyboard: Annotated[bool, Option("--keyboard/--no-keyboard")] = True,
110111
):
111112
"""Generate a CPU schematic.
@@ -260,20 +261,24 @@ def _render_template(
260261

261262
worker_ast = parse_mlog(worker_code)
262263

263-
check_unsaved_variables(worker_ast)
264+
try:
265+
check_unsaved_variables(worker_ast)
266+
worker_labels = dict(iter_labels(worker_ast))
267+
worker_code = replace_symbolic_labels(worker_code, worker_ast, worker_labels)
268+
except MlogError as e:
269+
e.add_note(f"{worker_output}:{e.token.line}")
270+
raise
271+
272+
# hack
273+
worker_output.write_text(worker_code, "utf-8")
274+
264275
print(
265276
f"""\
266277
Worker:
267-
Instructions: {len(list(n for n in worker_ast if isinstance(n, Statement)))} / 1000
278+
Instructions: {count_statements(worker_ast)} / 1000
268279
Bytes: {len(worker_code.encode())} / {1024 * 100}"""
269280
)
270281

271-
try:
272-
worker_labels = dict(iter_labels(worker_ast))
273-
except DirectiveError as e:
274-
e.add_note(f"{worker_output}:{e.token.line}")
275-
raise
276-
277282
# preprocess controller
278283

279284
controller_code, _, _ = _render_template(
@@ -291,7 +296,7 @@ def _render_template(
291296
print(
292297
f"""\
293298
Controller:
294-
Instructions: {len(list(n for n in controller_ast if isinstance(n, Statement)))} / 1000
299+
Instructions: {count_statements(controller_ast)} / 1000
295300
Bytes: {len(controller_code.encode())} / {1024 * 100}"""
296301
)
297302

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
__all__ = [
2-
"DirectiveError",
32
"Directive",
3+
"DirectiveError",
44
"Label",
5+
"MlogError",
56
"Statement",
67
"check_unsaved_variables",
8+
"count_statements",
79
"iter_labels",
810
"parse_mlog",
11+
"replace_symbolic_labels",
912
]
1013

1114
from .mlog import (
1215
Directive,
1316
DirectiveError,
1417
Label,
18+
MlogError,
1519
Statement,
1620
check_unsaved_variables,
21+
count_statements,
1722
iter_labels,
1823
parse_mlog,
24+
replace_symbolic_labels,
1925
)

python/src/mlogv32/preprocessor/parser/mlog.lark

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ start: (line? _NEWLINE)*
44
| statement
55
| directive
66

7-
statement: TOKEN (TOKEN | LABEL | STRING)*
7+
statement: TOKEN (TOKEN | LABEL_REF | LABEL | STRING)*
88

99
directive: _DIRECTIVE /assert_counter|(start|end)_(fetch|assert_length)|(push|pop)_saved/ TOKEN*
1010

1111
STRING.2: /"[^\n]*"/
1212

13-
LABEL.1: /[^\n #\t;]+:/
13+
LABEL.1: TOKEN ":"
14+
15+
LABEL_REF.1: "%" TOKEN "%"
1416

1517
TOKEN: /[^\n #\t;]+/
1618

python/src/mlogv32/preprocessor/parser/mlog.py

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from dataclasses import dataclass
22
from importlib import resources
3-
from typing import TYPE_CHECKING, Callable, Iterable, Iterator
3+
from typing import TYPE_CHECKING, Callable, Iterator
44

55
from lark import Lark, Token, Transformer
66

@@ -34,6 +34,8 @@ class Directive:
3434

3535
type ASTNode = Label | Statement | Directive
3636

37+
type AST = list[ASTNode]
38+
3739

3840
@dataclass
3941
@v_args(inline=True)
@@ -65,12 +67,16 @@ def parse_mlog(text: str):
6567
return MlogTransformer().transform(tree)
6668

6769

68-
class DirectiveError(AssertionError):
70+
class MlogError(AssertionError):
6971
def __init__(self, message: str, token: Token, *args: object) -> None:
7072
super().__init__(message, *args)
7173
self.token = token
7274

7375

76+
class DirectiveError(MlogError):
77+
pass
78+
79+
7480
def expect_int(
7581
n: Token,
7682
predicate: Callable[[int], bool] | None = None,
@@ -85,7 +91,7 @@ def expect_int(
8591
raise DirectiveError(f"Invalid {description}: {result}", n)
8692

8793

88-
def iter_labels(ast: Iterable[ASTNode]) -> Iterator[tuple[str, int]]:
94+
def iter_labels(ast: AST) -> Iterator[tuple[str, int]]:
8995
counter = 0
9096
length_assertion = None
9197
for node in ast:
@@ -139,7 +145,7 @@ def iter_labels(ast: Iterable[ASTNode]) -> Iterator[tuple[str, int]]:
139145
)
140146

141147

142-
def check_unsaved_variables(ast: Iterable[ASTNode]):
148+
def check_unsaved_variables(ast: AST):
143149
saved_variables = {"@counter"}
144150
warned_variables = set[str]()
145151
state = "init"
@@ -194,3 +200,64 @@ def check_unsaved_variables(ast: Iterable[ASTNode]):
194200
warned_variables.add(var)
195201

196202
print(f"Saved variable count: {len(saved_variables - {'@counter'})}")
203+
204+
205+
def count_statements(ast: AST):
206+
n = 0
207+
for node in ast:
208+
if isinstance(node, Statement):
209+
n += 1
210+
return n
211+
212+
213+
def replace_symbolic_labels(
214+
text: str,
215+
ast: AST,
216+
labels: dict[str, int],
217+
*,
218+
check: bool = True,
219+
) -> str:
220+
parts = list[str]()
221+
222+
start_pos = 0
223+
224+
for token in _iter_label_refs(ast):
225+
label = token[1:-1]
226+
if label not in labels:
227+
raise MlogError(f"Unknown symbolic label: {label}", token)
228+
229+
if token.start_pos is None or token.end_pos is None:
230+
raise MlogError(f"Internal error: invalid token: {token}", token)
231+
232+
parts += [
233+
text[start_pos : token.start_pos],
234+
str(labels[label]),
235+
]
236+
237+
start_pos = token.end_pos
238+
239+
parts += text[start_pos:]
240+
241+
new_text = "".join(parts)
242+
243+
# sanity checks
244+
if check:
245+
new_ast = parse_mlog(new_text)
246+
if count_statements(ast) != count_statements(new_ast):
247+
raise AssertionError(
248+
"Source transformation failed: mismatched statement count"
249+
)
250+
if labels != dict(iter_labels(new_ast)):
251+
raise AssertionError("Source transformation failed: mismatched labels")
252+
253+
return new_text
254+
255+
256+
def _iter_label_refs(ast: AST) -> Iterator[Token]:
257+
for node in ast:
258+
if not isinstance(node, Statement):
259+
continue
260+
for token in node.args:
261+
if token.type != "LABEL_REF":
262+
continue
263+
yield token

0 commit comments

Comments
 (0)