A categorical programming language
修订版 | a493d594f55f546bed94bbed9f5a08547ff4ba02 (tree) |
---|---|
时间 | 2023-01-03 09:20:01 |
作者 | Corbin <cds@corb...> |
Commiter | Corbin |
Really get hacking with graph reduction in RPython.
@@ -2,17 +2,15 @@ entrypoint: exe: enableJIT: | ||
2 | 2 | { nixpkgs ? import <nixpkgs> {}, jelly ? null, movelist ? null }: |
3 | 3 | let |
4 | 4 | inherit (nixpkgs.pkgs) |
5 | - fetchFromGitLab stdenv | |
5 | + fetchhg stdenv | |
6 | 6 | pkg-config libffi stb libcaca |
7 | 7 | python2 python2Packages; |
8 | 8 | # https://foss.heptapod.net/pypy/pypy/ |
9 | - pypySrc = fetchFromGitLab { | |
10 | - domain = "foss.heptapod.net"; | |
11 | - owner = "pypy"; | |
12 | - repo = "pypy"; | |
13 | - # release candidate from branch release-pypy3.8-v7.x | |
14 | - rev = "90fd9ed34d52181de59cbfff863719472b05418e"; | |
15 | - sha256 = "03cshgvh8qcsyac4q4vf0sbvcm1m2ikgwycwip4cc7sw9pzpw6a3"; | |
9 | + pypySrc = fetchhg { | |
10 | + url = "https://foss.heptapod.net/pypy/pypy"; | |
11 | + # tag release-pypy3.8-v7.3.11 | |
12 | + rev = "release-pypy3.8-v7.3.11"; | |
13 | + sha256 = "sha256-yHIY4cuCpfB0Nl1Odutc9w2QUPhWK+rxaSGfbBV+ZUg="; | |
16 | 14 | }; |
17 | 15 | in |
18 | 16 | let |
@@ -0,0 +1,111 @@ | ||
1 | +# Basic graph reduction. | |
2 | +# Heavily inspired by https://theory.stanford.edu/~blynn/lambda/cl.html | |
3 | +# and https://www.youtube.com/watch?v=GawiQQCn3bk | |
4 | + | |
5 | +from cammylib.parser import makeParser | |
6 | + | |
7 | +class InvalidGraph(Exception): pass | |
8 | + | |
9 | +class Node(object): | |
10 | + _immutable_ = True | |
11 | + def asStr(self): return self.symbol | |
12 | + | |
13 | +class Unit(Node): | |
14 | + _immutable_ = True | |
15 | + def asStr(self): return "*" | |
16 | +unit = Unit() | |
17 | + | |
18 | +class App(Node): | |
19 | + _immutable_ = True | |
20 | + | |
21 | + def __init__(self, f, x): | |
22 | + self.f = f | |
23 | + self.x = x | |
24 | + | |
25 | + def asStr(self): return "(@ %s %s)" % (self.f.asStr(), self.x.asStr()) | |
26 | + | |
27 | +class WordBox(Node): | |
28 | + _immutable_ = True | |
29 | + def __init__(self, w): self.w = w | |
30 | + def asStr(self): return "(w# %d)" % self.w | |
31 | + | |
32 | +lits = { | |
33 | + "Z": WordBox(0), | |
34 | + "U": unit, | |
35 | +} | |
36 | +prims = {name: type(name, (Node,), {"_immutable_": True, "symbol": name})() for name in ( | |
37 | + "I", "B", "C", "K", "S", "N", "1+", "FZ", "FO", | |
38 | +)} | |
39 | + | |
40 | +def buildAtom(symbol): | |
41 | + if symbol in lits: return lits[symbol] | |
42 | + elif symbol in prims: return prims[symbol] | |
43 | + else: raise InvalidGraph("can only build lits and prims") | |
44 | +def buildApp(head, args): | |
45 | + if head != "@": raise InvalidGraph("can only build apps") | |
46 | + if len(args) != 2: raise InvalidGraph("can only do binary app") | |
47 | + return App(args[0], args[1]) | |
48 | +GraphParser = makeParser("GraphParser", buildAtom, buildApp) | |
49 | + | |
50 | +def app(f, x): | |
51 | + if f is prims["I"]: return x | |
52 | + elif isinstance(f, WordBox): return f | |
53 | + else: return App(f, x) | |
54 | +def comp(g, f): | |
55 | + if f is prims["I"]: return g | |
56 | + elif g is prims["I"]: return f | |
57 | + return app(app(prims["B"], g), f) | |
58 | + | |
59 | +def reduce(g): | |
60 | + # Heap pointer, value stack | |
61 | + hp = g | |
62 | + s = [] | |
63 | + | |
64 | + while isinstance(hp, App): | |
65 | + print "---" | |
66 | + print "Current hp:", hp.asStr() | |
67 | + print "Current stack:" | |
68 | + for v in s: print v.asStr() | |
69 | + if hp.f is prims["I"] and s: hp = hp.x | |
70 | + elif hp.f is prims["B"] and len(s) >= 2: | |
71 | + g = hp.x | |
72 | + f = s.pop() | |
73 | + x = s.pop() | |
74 | + hp = app(g, app(f, x)) | |
75 | + elif hp.f is prims["1+"]: | |
76 | + n = reduce(hp.x) | |
77 | + if isinstance(n, WordBox): hp = WordBox(n.w + 1) | |
78 | + else: hp = app(hp.f, n) | |
79 | + elif hp.f is prims["N"] and len(s) >= 2: | |
80 | + x = hp.x | |
81 | + f = s.pop() | |
82 | + n = reduce(s.pop()) | |
83 | + if isinstance(n, WordBox): | |
84 | + # Exponentiate by squaring | |
85 | + i = n.w | |
86 | + v = prims["I"] | |
87 | + while i: | |
88 | + if i & 1: v = comp(v, f) | |
89 | + f = comp(f, f) | |
90 | + i >>= 1 | |
91 | + hp = app(v, x) | |
92 | + else: | |
93 | + # not fully evaluated | |
94 | + s.append(n) | |
95 | + s.append(f) | |
96 | + else: s.append(hp.x); hp = hp.f | |
97 | + | |
98 | + # Rebuild spine if not fully applied | |
99 | + while s: hp = app(hp, s.pop()) | |
100 | + return hp | |
101 | + | |
102 | +def doGraph(path): | |
103 | + print "Reading graph from", path | |
104 | + with open(path, "rb") as handle: parser = GraphParser(handle.read()) | |
105 | + g = parser.takeExpression() | |
106 | + print "Got graph", g.asStr() | |
107 | + applied = app(g, unit) | |
108 | + print "Applying graph to unit to get", applied.asStr() | |
109 | + reduced = reduce(applied) | |
110 | + print "Reduced to", reduced.asStr() | |
111 | + return 0 |
@@ -5,59 +5,70 @@ class ParseError(Exception): | ||
5 | 5 | def __init__(self, message): |
6 | 6 | self.message = message |
7 | 7 | |
8 | -class CammyParser(object): | |
9 | - def __init__(self, s): | |
10 | - self._i = 0 | |
11 | - self._s = s | |
12 | - | |
13 | - def position(self): | |
14 | - return self._i | |
15 | - | |
16 | - def hasMore(self): | |
17 | - return self._i < len(self._s) | |
18 | - | |
19 | - def eatWhitespace(self): | |
20 | - while self.hasMore() and self._s[self._i] in (" ", "\n"): | |
21 | - self._i += 1 | |
22 | - | |
23 | - def canAndDoesEat(self, c): | |
24 | - if self._s[self._i] == c: | |
25 | - self._i += 1 | |
26 | - return True | |
27 | - else: | |
28 | - return False | |
29 | - | |
30 | - def takeName(self): | |
31 | - start = self._i | |
32 | - while self.hasMore() and self._s[self._i] not in (")", "(", " ", "\n"): | |
33 | - self._i += 1 | |
34 | - stop = self._i | |
35 | - return self._s[start:stop] | |
36 | - | |
37 | - def takeExpression(self): | |
38 | - self.eatWhitespace() | |
39 | - if self.canAndDoesEat('('): | |
40 | - return self.startFunctor() | |
41 | - else: | |
42 | - symbol = self.takeName() | |
43 | - if symbol.startswith("@"): | |
44 | - try: | |
45 | - return sexp.Hole(int(symbol[1:])) | |
46 | - except ValueError: | |
47 | - return sexp.Atom(symbol) | |
48 | - return sexp.Atom(symbol) | |
8 | +def makeParser(parserName, buildAtom, buildList): | |
9 | + class Parser(object): | |
10 | + def __init__(self, s): | |
11 | + self._i = 0 | |
12 | + self._s = s | |
13 | + | |
14 | + def position(self): | |
15 | + return self._i | |
16 | + | |
17 | + def hasMore(self): | |
18 | + return self._i < len(self._s) | |
19 | + | |
20 | + def eatWhitespace(self): | |
21 | + while self.hasMore() and self._s[self._i] in (" ", "\n"): | |
22 | + self._i += 1 | |
49 | 23 | |
50 | - def startFunctor(self): | |
51 | - start = self._i | |
52 | - head = self.takeName() | |
53 | - self.eatWhitespace() | |
54 | - args = [] | |
55 | - while not self.canAndDoesEat(')'): | |
56 | - args.append(self.takeExpression()) | |
24 | + def canAndDoesEat(self, c): | |
25 | + if self._s[self._i] == c: | |
26 | + self._i += 1 | |
27 | + return True | |
28 | + else: | |
29 | + return False | |
30 | + | |
31 | + def takeName(self): | |
32 | + start = self._i | |
33 | + while self.hasMore() and self._s[self._i] not in (")", "(", " ", "\n"): | |
34 | + self._i += 1 | |
35 | + stop = self._i | |
36 | + return self._s[start:stop] | |
37 | + | |
38 | + def takeExpression(self): | |
57 | 39 | self.eatWhitespace() |
58 | - if not self.hasMore(): | |
59 | - raise ParseError("Unterminated functor starting at %d" % start) | |
60 | - return sexp.Functor(head, args) | |
40 | + if self.canAndDoesEat('('): | |
41 | + return self.startFunctor() | |
42 | + else: | |
43 | + symbol = self.takeName() | |
44 | + return buildAtom(symbol) | |
45 | + | |
46 | + def startFunctor(self): | |
47 | + start = self._i | |
48 | + head = self.takeName() | |
49 | + self.eatWhitespace() | |
50 | + args = [] | |
51 | + while not self.canAndDoesEat(')'): | |
52 | + args.append(self.takeExpression()) | |
53 | + self.eatWhitespace() | |
54 | + if not self.hasMore(): | |
55 | + raise ParseError("Unterminated functor starting at %d" % start) | |
56 | + return buildList(head, args) | |
57 | + | |
58 | + Parser.__name__ = parserName | |
59 | + return Parser | |
60 | + | |
61 | +def buildCammyAtom(symbol): | |
62 | + if symbol.startswith("@"): | |
63 | + try: | |
64 | + return sexp.Hole(int(symbol[1:])) | |
65 | + except ValueError: | |
66 | + return sexp.Atom(symbol) | |
67 | + return sexp.Atom(symbol) | |
68 | + | |
69 | +def buildCammyTemplate(head, args): return sexp.Functor(head, args) | |
70 | + | |
71 | +CammyParser = makeParser("CammyParser", buildCammyAtom, buildCammyTemplate) | |
61 | 72 | |
62 | 73 | |
63 | 74 | def parse(s): |
@@ -5,6 +5,7 @@ from cammylib.hax import patch_ctypes_for_ffi | ||
5 | 5 | |
6 | 6 | from animate import doAnimate |
7 | 7 | from draw import doDraw |
8 | +from cammylib.graph import doGraph | |
8 | 9 | from repl import doREPL |
9 | 10 | |
10 | 11 | def help(exe, problem): |
@@ -13,6 +14,7 @@ def help(exe, problem): | ||
13 | 14 | print "Usage:" |
14 | 15 | print " animate PROG WIDTH HEIGHT Animate in the terminal" |
15 | 16 | print " draw PROG WIDTH HEIGHT OUT Draw a picture" |
17 | + print " graph PROG Uuuuhhhh" | |
16 | 18 | print " repl Start an interactive session" |
17 | 19 | print "Set the hive path by exporting CAMMY_HIVE" |
18 | 20 |
@@ -21,19 +23,26 @@ def main(argv): | ||
21 | 23 | if len(argv) < 2: |
22 | 24 | help(exe, "no command specified") |
23 | 25 | return 1 |
24 | - hivepath = os.environ["CAMMY_HIVE"] | |
25 | 26 | command = argv[1] |
26 | 27 | # XXX unrolling_iterable |
27 | 28 | if command == "animate": |
28 | 29 | if len(argv) != 5: |
29 | 30 | help(exe, "animate takes three arguments") |
30 | 31 | return 1 |
32 | + hivepath = os.environ["CAMMY_HIVE"] | |
31 | 33 | return doAnimate(hivepath, argv[2], int(argv[3]), int(argv[4])) |
32 | 34 | elif command == "draw": |
33 | 35 | if len(argv) != 6: |
34 | 36 | help(exe, "draw takes four arguments") |
37 | + return 1 | |
35 | 38 | return doDraw(argv[2], argv[3], argv[4], argv[5]) |
39 | + elif command == "graph": | |
40 | + if len(argv) != 3: | |
41 | + help(exe, "graph takes one argument") | |
42 | + return 1 | |
43 | + return doGraph(argv[2]) | |
36 | 44 | elif command == "repl": |
45 | + hivepath = os.environ["CAMMY_HIVE"] | |
37 | 46 | return doREPL(hivepath) |
38 | 47 | else: |
39 | 48 | help(exe, "unknown command: " + command) |
@@ -20,6 +20,7 @@ | ||
20 | 20 | ((== expr 'id) (== tree 'I)) |
21 | 21 | ((== expr `(comp ,f ,g)) (== tree `(@ (@ B ,tg) ,tf)) |
22 | 22 | (combinators° f tf) (combinators° g tg)) |
23 | + ((== expr 'ignore) (== tree 'U)) | |
23 | 24 | ; \x l -> l (f x) (g x) |
24 | 25 | ((== expr `(pair ,f ,g)) (== tree `(@ (@ S (@ (@ B C) (@ (@ B (@ C I)) ,tf))) ,tg)) |
25 | 26 | (combinators° f tf) (combinators° g tg)) |