A categorical programming language
修订版 | 7a65a2ad61556360b675678d99db2c2a3599afb8 (tree) |
---|---|
时间 | 2022-02-18 14:32:04 |
作者 | Corbin <cds@corb...> |
Commiter | Corbin |
Split cammy-run into cammy-draw and pieces.
The parser and arrow-builder are now in their own module, so that both
cammy-draw and cammy-repl can use them. cammy-draw is finally around the
proper size, carrying proper responsibilities.
@@ -1,13 +1,10 @@ | ||
1 | +import math | |
1 | 2 | |
2 | -import math, sys | |
3 | - | |
4 | -from rpython.rlib.jit import JitDriver, isconstant, set_param, unroll_safe, we_are_jitted | |
3 | +from rpython.rlib.jit import JitDriver, isconstant, we_are_jitted | |
5 | 4 | from rpython.rlib.rbigint import rbigint |
6 | -from rpython.rlib.rfloat import string_to_float | |
7 | -from rpython.rlib.rstring import StringBuilder, split | |
8 | 5 | |
9 | -from cammylib.hax import patch_ctypes_for_ffi | |
10 | -from cammylib import stb | |
6 | +from cammylib.parser import Atom, Functor | |
7 | + | |
11 | 8 | |
12 | 9 | class TypeFail(Exception): |
13 | 10 | def __init__(self, reason): |
@@ -395,161 +392,15 @@ def buildCompound(name, args): | ||
395 | 392 | else: |
396 | 393 | raise BuildProblem("Invalid compound functor: " + name) |
397 | 394 | |
398 | - | |
399 | -class CammyParser(object): | |
400 | - | |
401 | - def __init__(self, s): | |
402 | - self._i = 0 | |
403 | - self._s = s | |
404 | - | |
405 | - def eatWhitespace(self): | |
406 | - while self._i < len(self._s) and self._s[self._i] in (" ", "\n"): | |
407 | - self._i += 1 | |
408 | - | |
409 | - def canAndDoesEat(self, c): | |
410 | - if self._s[self._i] == c: | |
411 | - self._i += 1 | |
412 | - return True | |
413 | - else: | |
414 | - return False | |
415 | - | |
416 | - def takeName(self): | |
417 | - start = self._i | |
418 | - while self._i < len(self._s) and self._s[self._i] not in (")", "(", " ", "\n"): | |
419 | - self._i += 1 | |
420 | - stop = self._i | |
421 | - return self._s[start:stop] | |
422 | - | |
423 | - def takeExpression(self): | |
424 | - self.eatWhitespace() | |
425 | - if self.canAndDoesEat('('): | |
426 | - return self.startFunctor() | |
427 | - else: | |
428 | - return buildUnary(self.takeName()) | |
429 | - | |
430 | - def startFunctor(self): | |
431 | - head = self.takeName() | |
432 | - self.eatWhitespace() | |
433 | - args = [] | |
434 | - while not self.canAndDoesEat(')'): | |
435 | - args.append(self.takeExpression()) | |
436 | - return buildCompound(head, args) | |
437 | - | |
438 | - | |
439 | -def parse(s): | |
395 | +def buildArrow(sexp): | |
440 | 396 | try: |
441 | - return CammyParser(s).takeExpression() | |
397 | + if isinstance(sexp, Atom): | |
398 | + return buildUnary(sexp.symbol) | |
399 | + elif isinstance(sexp, Functor): | |
400 | + args = [buildArrow(arg) for arg in sexp.arguments] | |
401 | + return buildCompound(sexp.constructor, args) | |
402 | + else: | |
403 | + assert False, "inconceivable" | |
442 | 404 | except BuildProblem as bp: |
443 | - print "Couldn't parse Cammy expression:", bp.message | |
444 | - | |
445 | - | |
446 | -# Borrowed from stub.scm and Typhon's colors.mt | |
447 | - | |
448 | -def scale(bot, top, x): | |
449 | - d = top - bot | |
450 | - return bot + d * x | |
451 | - | |
452 | -def linear2sRGB(u): | |
453 | - try: | |
454 | - return u * 25 / 323 if u <= 0.04045 else math.pow((u * 200 + 11) / 211, 12 / 5) | |
455 | - except OverflowError: | |
456 | - return 1.0 | |
457 | - | |
458 | -def finishChannel(c): | |
459 | - return int(255 * max(0.0, min(1.0, linear2sRGB(c)))) | |
460 | - | |
461 | -# Pixel area: 4 / (w * h) | |
462 | -# Pixel radius: √(area / pi) = 2 / √(w * h * pi) = (2 / √pi) / √(w * h) | |
463 | -# This constant is the first half of that. | |
464 | -TWOSQRTPI = 2.0 / math.sqrt(math.pi) | |
465 | - | |
466 | -class Window(object): | |
467 | - _immutable_ = True | |
468 | - | |
469 | - def __init__(self, corners, width, height): | |
470 | - self._corners = corners[0], corners[1], corners[2], corners[3] | |
471 | - self._w = width | |
472 | - self._h = height | |
473 | - area = abs((corners[0] - corners[2]) * (corners[1] - corners[3])) | |
474 | - self.pixelRadius = TWOSQRTPI * area / math.sqrt(width * height) | |
475 | - | |
476 | - def coordsForPixel(self, i): | |
477 | - w = i % self._w | |
478 | - h = i // self._w | |
479 | - iw = 1.0 / self._w | |
480 | - dw = 0.5 * iw | |
481 | - ih = 1.0 / self._h | |
482 | - dh = 0.5 * ih | |
483 | - c1 = scale(self._corners[0], self._corners[2], dw + iw * w) | |
484 | - c2 = scale(self._corners[1], self._corners[3], dh + ih * h) | |
485 | - return c1, c2 | |
486 | - | |
487 | -sample_driver = JitDriver(name="sample", | |
488 | - greens=["program"], reds=["x", "y"], | |
489 | - is_recursive=True) | |
490 | - | |
491 | -def sample(program, x, y): | |
492 | - sample_driver.jit_merge_point(program=program, x=x, y=y) | |
493 | - rgb = program.run(P(F(x), F(y))) | |
494 | - r = rgb.first().f() | |
495 | - g = rgb.second().first().f() | |
496 | - b = rgb.second().second().f() | |
497 | - return r, g, b | |
498 | - | |
499 | -offsets = [(0.0, 0.0), (1.0, 1.0), (1.0, -1.0), (-1.0, 1.0), (-1.0, -1.0)] | |
500 | - | |
501 | -@unroll_safe | |
502 | -def multisample(program, radius, x, y): | |
503 | - r = g = b = 0.0 | |
504 | - for ox, oy in offsets: | |
505 | - sr, sg, sb = sample(program, x + radius * ox, y + radius * oy) | |
506 | - r += sr | |
507 | - g += sg | |
508 | - b += sb | |
509 | - l = len(offsets) | |
510 | - return finishChannel(r / l), finishChannel(g / l), finishChannel(b / l) | |
511 | - | |
512 | -def drawPixels(size, program, window): | |
513 | - sb = StringBuilder() | |
514 | - i = 0 | |
515 | - while i < size: | |
516 | - c1, c2 = window.coordsForPixel(i) | |
517 | - r, g, b = multisample(program, window.pixelRadius, c1, c2) | |
518 | - sb.append(chr(r) + chr(g) + chr(b)) | |
519 | - i += 1 | |
520 | - return sb.build() | |
521 | - | |
522 | -def drawPNG(program, filename, corners, width, height): | |
523 | - window = Window(corners, width, height) | |
524 | - size = width * height | |
525 | - buf = drawPixels(size, program, window) | |
526 | - channels = 3 | |
527 | - stb.i_write_png(filename, width, height, channels, buf, width * channels) | |
528 | - | |
529 | - | |
530 | -def main(argv): | |
531 | - set_param(None, "trace_limit", 50001) | |
532 | - | |
533 | - prog = argv[1] | |
534 | - window = [string_to_float(s) for s in split(argv[2])] | |
535 | - width = int(argv[3]) | |
536 | - height = int(argv[4]) | |
537 | - out = argv[5] | |
538 | - with open(prog) as handle: | |
539 | - func = parse(handle.read()) | |
540 | - try: | |
541 | - drawPNG(func, out, window, width, height) | |
542 | - except TypeFail as tf: | |
543 | - print "Type failure:", tf.reason | |
405 | + print "Couldn't find Cammy expression:", bp.message | |
544 | 406 | raise |
545 | - return 0 | |
546 | - | |
547 | - | |
548 | -def target(driver, *args): | |
549 | - patch_ctypes_for_ffi() | |
550 | - driver.exe_name = "cammy-run" | |
551 | - return main, None | |
552 | - | |
553 | - | |
554 | -if __name__ == "__main__": | |
555 | - sys.exit(main(sys.argv)) |
@@ -0,0 +1,53 @@ | ||
1 | +from cammylib.sexp import Atom, Functor | |
2 | + | |
3 | + | |
4 | +class CammyParser(object): | |
5 | + | |
6 | + def __init__(self, s): | |
7 | + self._i = 0 | |
8 | + self._s = s | |
9 | + | |
10 | + def position(self): | |
11 | + return self._i | |
12 | + | |
13 | + def eatWhitespace(self): | |
14 | + while self._i < len(self._s) and self._s[self._i] in (" ", "\n"): | |
15 | + self._i += 1 | |
16 | + | |
17 | + def canAndDoesEat(self, c): | |
18 | + if self._s[self._i] == c: | |
19 | + self._i += 1 | |
20 | + return True | |
21 | + else: | |
22 | + return False | |
23 | + | |
24 | + def takeName(self): | |
25 | + start = self._i | |
26 | + while self._i < len(self._s) and self._s[self._i] not in (")", "(", " ", "\n"): | |
27 | + self._i += 1 | |
28 | + stop = self._i | |
29 | + return self._s[start:stop] | |
30 | + | |
31 | + def takeExpression(self): | |
32 | + self.eatWhitespace() | |
33 | + if self.canAndDoesEat('('): | |
34 | + return self.startFunctor() | |
35 | + else: | |
36 | + return Atom(self.takeName()) | |
37 | + | |
38 | + def startFunctor(self): | |
39 | + head = self.takeName() | |
40 | + self.eatWhitespace() | |
41 | + args = [] | |
42 | + while not self.canAndDoesEat(')'): | |
43 | + args.append(self.takeExpression()) | |
44 | + return Functor(head, args) | |
45 | + | |
46 | + | |
47 | +def parse(s): | |
48 | + parser = CammyParser(s) | |
49 | + sexp = parser.takeExpression() | |
50 | + # Parser is now fast-forwarded to the end of the S-expression, so we can | |
51 | + # slice from that point and get the docstring/etc. | |
52 | + trail = s[parser.position():] | |
53 | + return sexp, trail |
@@ -0,0 +1,19 @@ | ||
1 | +class SExp(object): | |
2 | + "An S-expression." | |
3 | + | |
4 | +class Atom(SExp): | |
5 | + "An S-expression atom." | |
6 | + | |
7 | + _immutable_fields_ = "symbol", | |
8 | + | |
9 | + def __init__(self, symbol): | |
10 | + self.symbol = symbol | |
11 | + | |
12 | +class Functor(SExp): | |
13 | + "A list of S-expressions with a distinguished head." | |
14 | + | |
15 | + _immutable_fields_ = "constructor", "arguments[:]", | |
16 | + | |
17 | + def __init__(self, constructor, arguments): | |
18 | + self.constructor = constructor | |
19 | + self.arguments = arguments |
@@ -1,5 +0,0 @@ | ||
1 | -{ nixpkgs ? import <nixpkgs> {} }: | |
2 | -let | |
3 | - makeBuilder = import ./builder.nix { inherit nixpkgs; }; | |
4 | -in | |
5 | - makeBuilder ./main.py "cammy-run" true |
@@ -2,20 +2,35 @@ | ||
2 | 2 | |
3 | 3 | from rpython.rlib.rfile import create_stdio |
4 | 4 | |
5 | +from cammylib.arrows import buildArrow, BuildProblem | |
6 | +from cammylib.parser import parse | |
7 | + | |
5 | 8 | LINE_BUFFER_LENGTH = 1024 |
6 | 9 | |
7 | 10 | def repl(stdin, stdout): |
8 | 11 | while True: |
9 | 12 | stdout.write("> ") |
10 | 13 | line = stdin.readline(LINE_BUFFER_LENGTH) |
11 | - print line | |
14 | + if not line: | |
15 | + # Empty read means EOF. | |
16 | + break | |
17 | + sexp, trail = parse(line) | |
18 | + print "Got:", line | |
19 | + print "S-expression:", sexp | |
20 | + print "Trail:", trail | |
21 | + try: | |
22 | + arrow = buildArrow(sexp) | |
23 | + print "Arrow:", arrow | |
24 | + except BuildProblem as bp: | |
25 | + print "Couldn't build arrow:", bp.message | |
26 | + return 0 | |
12 | 27 | |
13 | 28 | def main(argv): |
14 | 29 | stdin, stdout, stderr = create_stdio() |
15 | 30 | try: |
16 | - repl(stdin, stdout) | |
31 | + return repl(stdin, stdout) | |
17 | 32 | except: |
18 | - return 0 | |
33 | + return 1 | |
19 | 34 | |
20 | 35 | def target(driver, *args): |
21 | 36 | driver.exe_name = "cammy-repl" |
@@ -4,7 +4,7 @@ let | ||
4 | 4 | frame = import ./frame { inherit nixpkgs; }; |
5 | 5 | jelly = (import jelly/Cargo.nix { pkgs = nixpkgs; }).rootCrate.build; |
6 | 6 | movelist = import ./movelist { inherit nixpkgs; }; |
7 | - cammy-run = import ./cammy-rpy { inherit nixpkgs; }; | |
7 | + cammy-draw = import ./cammy-rpy/draw.nix { inherit nixpkgs; }; | |
8 | 8 | cammy-repl = import ./cammy-rpy/repl.nix { inherit nixpkgs; }; |
9 | 9 | eggs = builtins.attrValues (removeAttrs (import ./eggs.nix { |
10 | 10 | inherit pkgs; |
@@ -46,7 +46,7 @@ in pkgs.stdenv.mkDerivation { | ||
46 | 46 | |
47 | 47 | makeWrapper ${movelist}/bin/movelist $out/bin/cammy-movelist |
48 | 48 | |
49 | - makeWrapper ${cammy-run}/bin/cammy-run $out/bin/cammy-draw | |
49 | + makeWrapper ${cammy-draw}/bin/cammy-draw $out/bin/cammy-draw | |
50 | 50 | makeWrapper ${cammy-repl}/bin/cammy-repl $out/bin/cammy-repl |
51 | 51 | ''; |
52 | 52 | } |
@@ -17,6 +17,10 @@ in pkgs.stdenv.mkDerivation { | ||
17 | 17 | egg2nix |
18 | 18 | # maintaining frame/ |
19 | 19 | ocamlformat |
20 | + # maintaining cammy-rpy/ | |
21 | + pythonPackages.pyflakes | |
22 | + # using cammy-repl | |
23 | + rlwrap | |
20 | 24 | # working with sexps |
21 | 25 | ocamlPackages.sexp |
22 | 26 | # benchmarking |
@@ -44,3 +44,7 @@ | ||
44 | 44 | * Interval type? |
45 | 45 | * Cantor's type of bitstrings: N -> 2 |
46 | 46 | * The square type: For a type X, 2 -> X |
47 | +* The double type: For a type X, X + X | |
48 | +* Moore machines: For state type Q and input type S, Q × S -> Q | |
49 | + * Moore transducers: [Q × S, Q] -> [Q × S, Q] | |
50 | +* Mealy machines: For state type Q, input type S, and output type L, Q × S -> Q × L |