Skip to content

Commit beb161b

Browse files
committed
topdown file loader
1 parent 7ff6c08 commit beb161b

File tree

2 files changed

+204
-54
lines changed

2 files changed

+204
-54
lines changed

purescripto/purescript_loader.py

Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,79 @@
1-
from pathlib import Path
2-
from typing import Union, Tuple
3-
from types import CodeType
4-
from pie import LoaderForBetterLife
5-
from importlib import import_module
6-
from purescripto import rts
7-
from purescripto.utilities import import_from_path
8-
from purescripto.workaround import suppress_cpy38_literal_is, workaround
9-
import marshal
10-
import functools
11-
12-
RES = 'res'
13-
"""generated code object is stored in global variable $RES"""
14-
15-
16-
@functools.lru_cache()
17-
def _import_module_to_dict(m: str):
18-
with suppress_cpy38_literal_is():
19-
package, _, module = m.rpartition('.')
20-
entry_path = import_module(package).__file__
21-
loc = entry_path[:-len('__init__.py')] + module + '.py'
22-
return import_from_path(m, loc).__dict__
23-
24-
25-
RTS_TEMPLATE = {
26-
'zfsr32': rts.zfsr32,
27-
'Error': Exception,
28-
'import_module': _import_module_to_dict
29-
}
30-
31-
32-
class LoadPureScriptImplCode(LoaderForBetterLife[CodeType]):
33-
def source_to_prog(self, src: bytes, path: Path) -> CodeType:
34-
with workaround():
35-
mod = import_from_path(self.qualified_name + '$',
36-
str(path.absolute()))
37-
return getattr(mod, RES)
38-
39-
def load_program(self, b: bytes) -> CodeType:
40-
return marshal.loads(b)
41-
42-
def dump_program(self, prog: CodeType) -> bytes:
43-
return marshal.dumps(prog)
44-
45-
def suffix(self) -> Union[str, Tuple[str, ...]]:
46-
return '.src.py'
47-
48-
49-
def LoadPureScript(file: str, name: str):
50-
loader = LoadPureScriptImplCode(file, name)
51-
code = loader.load()
52-
man_made_globals = RTS_TEMPLATE.copy()
53-
exec(code, man_made_globals)
54-
return man_made_globals['exports']
1+
from pathlib import Path
2+
from typing import Union, Tuple
3+
from types import CodeType
4+
from pie import LoaderForBetterLife
5+
from importlib import import_module
6+
from purescripto import rts
7+
from purescripto.utilities import import_from_path
8+
from purescripto.workaround import suppress_cpy38_literal_is, workaround
9+
from py_sexpr.stack_vm.emit import module_code
10+
from purescripto.topdown import load_topdown
11+
import marshal
12+
import functools
13+
14+
RES = "res"
15+
"""generated code object is stored in global variable $RES"""
16+
17+
18+
@functools.lru_cache()
19+
def _import_module_to_dict(m: str):
20+
with suppress_cpy38_literal_is():
21+
package, _, module = m.rpartition(".")
22+
entry_path = import_module(package).__file__
23+
loc = entry_path[: -len("__init__.py")] + module + ".py"
24+
return import_from_path(m, loc).__dict__
25+
26+
27+
RTS_TEMPLATE = {
28+
"zfsr32": rts.zfsr32,
29+
"Error": Exception,
30+
"import_module": _import_module_to_dict,
31+
}
32+
33+
34+
def _META_ENV():
35+
with workaround():
36+
import py_sexpr.terms as terms
37+
38+
def make_pair(a, b):
39+
return a, b
40+
41+
env = {each: getattr(terms, each) for each in terms.__all__}
42+
env[make_pair.__name__] = make_pair
43+
return env
44+
45+
46+
META_ENV = _META_ENV()
47+
48+
49+
class LoadPureScriptImplCode(LoaderForBetterLife[CodeType]):
50+
def source_to_prog(self, src: bytes, path: Path) -> CodeType:
51+
52+
filename = str(path.absolute())
53+
if path.name.endswith(".raw.py"):
54+
meta_code = compile(src, filename, "eval")
55+
sexpr = eval(meta_code, META_ENV)
56+
code = module_code(sexpr, name=self.qualified_name + "$", filename=filename)
57+
else:
58+
assert path.name.endswith(".zip.py")
59+
with path.open(mode="utf8") as f:
60+
code = load_topdown(f, META_ENV)
61+
62+
return code
63+
64+
def load_program(self, b: bytes) -> CodeType:
65+
return marshal.loads(b)
66+
67+
def dump_program(self, prog: CodeType) -> bytes:
68+
return marshal.dumps(prog)
69+
70+
def suffix(self) -> Union[str, Tuple[str, ...]]:
71+
return ".raw.py", ".zip.py"
72+
73+
74+
def LoadPureScript(file: str, name: str):
75+
loader = LoadPureScriptImplCode(file, name)
76+
code = loader.load()
77+
man_made_globals = RTS_TEMPLATE.copy()
78+
exec(code, man_made_globals)
79+
return man_made_globals["exports"]

purescripto/topdown.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from typing import TextIO
2+
3+
4+
def _META_ENV():
5+
import py_sexpr.terms as terms
6+
7+
def make_pair(a, b):
8+
return a, b
9+
10+
env = {each: getattr(terms, each) for each in terms.__all__}
11+
env[make_pair.__name__] = make_pair
12+
return env
13+
14+
15+
META_ENV = _META_ENV()
16+
17+
18+
def load_topdown(file_io: TextIO, env):
19+
readline, read = file_io.readline, file_io.read
20+
n_entry = int(readline()[:-1])
21+
ACTION_ATTR = 0
22+
ACTION_APP = 1
23+
ACTION_SEQ = 2
24+
25+
object_stack = []
26+
actions = []
27+
left_stack = []
28+
push_object = object_stack.append
29+
push_left = left_stack.append
30+
push_action = actions.append
31+
pop_object = object_stack.pop
32+
pop_action = actions.pop
33+
pop_left = left_stack.pop
34+
35+
def read_entry(readline=readline, read=read):
36+
line = readline()
37+
key, length = line[:-1].split()
38+
value = read(int(length))
39+
read(1)
40+
return key, value
41+
42+
entries = dict(read_entry(readline, read) for _ in range(n_entry))
43+
read(1)
44+
45+
def read_float(readline=readline, append=push_object):
46+
return append(float(readline()[:-1]))
47+
48+
def read_int(readline=readline, append=push_object):
49+
return append(int(readline()[:-1]))
50+
51+
# noinspection PyDefaultArgument
52+
def read_bool(
53+
readline=readline, mapping={"true": True, "false": False}, append=push_object,
54+
):
55+
return append(mapping[readline()[:-1]])
56+
57+
def read_nil(append=push_object):
58+
readline()
59+
return append(None)
60+
61+
# noinspection PyDefaultArgument
62+
def read_string(readline=readline, entries=entries, append=push_object):
63+
append(entries[readline()[:-1]])
64+
65+
# noinspection PyDefaultArgument
66+
def read_var(readline=readline, env=env, entries=entries, append=push_object):
67+
return append(env[entries[readline()[:-1]]])
68+
69+
def read_acc(readline=readline, ACTION_ATTR=ACTION_ATTR):
70+
attr_name = readline()[:-1]
71+
return 1, (ACTION_ATTR, attr_name)
72+
73+
# noinspection PyDefaultArgument
74+
def read_cons(env=env, readline=readline, entries=entries, ACTION_APP=ACTION_APP):
75+
length, fn = readline()[:-1].split()
76+
length = int(length)
77+
fn = env[entries[fn]]
78+
return length, (ACTION_APP, fn)
79+
80+
def read_seq(readline=readline, ACTION_SEQ=ACTION_SEQ):
81+
length = int(readline()[:-1])
82+
return length, ACTION_SEQ
83+
84+
terminal_maps = {
85+
"s": read_string,
86+
"i": read_int,
87+
"b": read_bool,
88+
"n": read_nil,
89+
"f": read_float,
90+
"v": read_var,
91+
}
92+
non_term_maps = {"a": read_acc, "c": read_cons, "l": read_seq}
93+
94+
left = 1
95+
96+
while True:
97+
while left:
98+
left -= 1
99+
case = read(1)
100+
term = terminal_maps.get(case)
101+
if term:
102+
term()
103+
continue
104+
push_left(left)
105+
left, action = non_term_maps[case]()
106+
push_action((left, action))
107+
108+
try:
109+
left = pop_left()
110+
except IndexError:
111+
assert not actions and len(object_stack) == 1
112+
return object_stack[0]
113+
narg, action = pop_action()
114+
args = [pop_object() for _ in range(narg)]
115+
args.reverse()
116+
if action is ACTION_SEQ:
117+
push_object(args)
118+
continue
119+
120+
action, op = action
121+
if action is ACTION_APP:
122+
push_object(op(*args))
123+
else:
124+
assert action is ACTION_ATTR and len(args) == 1
125+
push_object(getattr(args[0], op))

0 commit comments

Comments
 (0)