diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..85ad5cb --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.mt linguist-language=Monte diff --git a/.travis.yml b/.travis.yml index 4b7af59..766fed3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +# Switch to faster container-based builders. Forbids use of sudo, setuid, +# seteid, etc. but we don't need them. +sudo: false language: python python: - '2.7' @@ -5,6 +8,7 @@ python: matrix: allow_failures: + - python: '2.7' - python: pypy fast_finish: true @@ -16,6 +20,5 @@ notifications: irc: channels: - "chat.freenode.net#monte" - on_success: always + on_success: change on_failure: always - diff --git a/LICENSE b/LICENSE index 1ad326e..23edea8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,10 @@ # Copyright (c) 2008-2014 Allen Short +Corbin Simpson Mike Cooper E. Dunham Combex, Inc. +Google, Inc. Soli Deo Gloria. diff --git a/README.md b/README.md index 354e74e..fa4ecee 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,4 @@ if you have a problem with the docs, telling us what question you had and where you expected to find the answer. Or come say hi on IRC, in #monte on irc.freenode.net! -![Travis Status](https://api.travis-ci.org/monte-language/monte.svg) +[![Travis Status](https://api.travis-ci.org/monte-language/monte.svg)](https://travis-ci.org/monte-language/monte/) diff --git a/bin/monte b/bin/monte index 4a0fb34..2dfe38c 100755 --- a/bin/monte +++ b/bin/monte @@ -7,8 +7,15 @@ while os.path.dirname(path) != path: break path = os.path.dirname(path) -from monte.runtime.load import eval as monte_eval, monteImport -# from monte.repl import startRepl +try: + from monte.runtime.load import eval as monte_eval, monteImport + # from monte.repl import startRepl +except ImportError: + import_error_message = """Failed to import a required Python module. + Is everything in requirements.txt installed and available? + """ + print >> sys.stderr, import_error_message + exit(-1) # startRepl() from monte.runtime.scope import bootScope, createSafeScope @@ -16,4 +23,13 @@ safeScope = createSafeScope(bootScope) scriptScope = safeScope.copy() scriptScope["import"] = monteImport(safeScope) if len(sys.argv) > 1: - monte_eval(open(sys.argv[1]).read(), scriptScope) + if sys.argv[1] == "-c": + # Compile-only mode! + source = open(sys.argv[2]).read() + from monte.parser import parse + from monte.expander import expand + from monte.ast import dump + ast = expand(parse(source)) + sys.stdout.write(dump(ast)) + else: + monte_eval(open(sys.argv[1]).read(), scriptScope) diff --git a/contrib/Quotes b/contrib/Quotes new file mode 100644 index 0000000..1017c11 --- /dev/null +++ b/contrib/Quotes @@ -0,0 +1,44 @@ + I mean, to be frank, at some point this is all Prolog reinvention. + But whatever. + sure + the problem is that the good parts of prolog are all locked away in... prolog implementations +*time passes* + simpson: i guess we have about half a prolog now + dash: Which is appropriate. That's what's supposed to happen, right? We are now complex enough to have an ill-specified and slightly buggy reimplementation of Prolog in our system. + yeah! we're ahead of all those other suckers who got common lisp instead +% +--> monteBot (~monte@c-24-21-131-247.hsd1.or.comcast.net) has joined #monte + monteBot: speak + Hi there! + Okay, the latency's good now. + monteBot: kill + dash: It's not yet equipped for that. + simpson: i'm patient. +% + well, okay. what's our threat model? + Developers want to deploy HTTPS sites. + Origin of threat: Developers +% + simpson: clicking randomly through stackexchange, i think i know how other programmers feel during our design conversations: http://gaming.stackexchange.com/q/214975/2354 + "... i know _some_ of those words?" + dash: can confirm +% +< froztbyte> simpson: a pet hate of mine in REPLs is when something doesn't allow forard/reverse word jumps, back-kill-word, etc +< froztbyte> I don't know how much effort those are to implement without readline +< dash> froztbyte: easy +< froztbyte> but I guess since the parser already does some of that work, parts of it might be reused? +< dash> froztbyte: make emacs your repl frontend +< froztbyte> dash: snrk +< dash> froztbyte: it's been done +< dash> also, there's rlwrap +< froztbyte> oh, I can believe that +< mythmon> froztbyte: without readline? not easy. but then, why not just use readline? +< froztbyte> readline, curses, and Tk +< froztbyte> are some of the ingredients commonly found on a unix/linux system which I've actually very successfully managed to not learn things about +< dash> who'd want to +< froztbyte> heh +< simpson> dash: Oh my. I did not know about rlwrap. +< simpson> Holy fuck now e/rune doesn't suck! + * simpson *ahem* +< simpson> I mean, yes. Very nifty. Quite. Capital. +% diff --git a/contrib/monte.el b/contrib/monte.el new file mode 100644 index 0000000..dae3ff6 --- /dev/null +++ b/contrib/monte.el @@ -0,0 +1,73 @@ +;; monte.el -- support for editing Monte code -*- lexical-binding: t -*- + +(defvar monte-mode-map + (let ((map (make-sparse-keymap))) + ;; (define-key map [remap forward-sentence] 'monte-forward-block) + ;; (define-key map [remap backward-sentence] 'monte-backward-block) + ;; (define-key map "\177" 'monde-dedent-line-backspace) + ;; (define-key map (kbd "") 'monte-dedent-line) + map) + "Keymap for monte-mode.") + +(defun monte-get-previous-line-indent () + (save-excursion + (forward-line -1) + (while (string-match "^ +$" (thing-at-point 'line)) + (forward-line -1)) + (current-indentation))) + +(defun monte-indent-line () + (interactive) + (message "%s %s %s" this-command last-command (- (current-indentation) 4)) + (let ((previous-indent (monte-get-previous-line-indent)) + (is-cycling (eq this-command last-command))) + (if is-cycling + (if (eq (current-indentation) 0) + (indent-to (+ previous-indent 4)) + (let ((place (- (current-indentation) 4))) + (beginning-of-line) + (delete-horizontal-space) + (indent-to place))) + (indent-to (+ previous-indent 4))))) + +(defvar monte-font-lock-keywords + `(,(rx symbol-start + (or "as" "bind" "break" "catch" "continue" "def" "else" "escape" + "exit" "extends" "export" "finally" "fn" "for" "guards" "if" + "implements" "in" "interface" "match" "meta" "method" "module" + "object" "pass" "pragma" "return" "switch" "to" "try" "var" + "via" "when" "while") + symbol-end) + (,(rx symbol-start "def" (1+ space) (group (1+ (or word ?_))) (0+ space) ?\() + (1 font-lock-function-name-face)) + (,(rx symbol-start "object" (1+ space) (group (1+ (or word ?_)))) + (1 font-lock-function-name-face)) + (,(rx symbol-start (or "def" "var") (1+ space) (group (1+ (or word ?_))) (0+ space) ?: ?=) + (1 font-lock-variable-name-face)) + )) + +(defvar monte-mode-syntax-table + (let ((table (make-syntax-table))) + (mapc (lambda (c) (modify-syntax-entry c "." table)) "$%+-.:;<=>?@^|") + (modify-syntax-entry ?+ "." table) + (modify-syntax-entry ?* ". 23b" monte-mode-syntax-table) + (modify-syntax-entry ?/ ". 14b" monte-mode-syntax-table) + (modify-syntax-entry ?# "<" table) + (modify-syntax-entry ?\n ">" table) + (modify-syntax-entry ?' "\"" table) + (modify-syntax-entry ?` "\"" table) + (modify-syntax-entry ?\\ "\\" table) + table) + "Monte syntax table.") + +;;;###autoload +(define-derived-mode monte-mode prog-mode "Monte" + "Major mode for editing Montefiles. + +\\{monte-mode-map}" + (set (make-local-variable 'indent-tabs-mode) nil) + (set (make-local-variable 'comment-start) "# ") + (set (make-local-variable 'comment-start-skip) "#+\\s-*") + (set (make-local-variable 'font-lock-defaults) '(monte-font-lock-keywords nil nil nil nil)) + (set (make-local-variable 'indent-line-function) 'monte-indent-line) + (setq-local electric-indent-inhibit t)) diff --git a/docs/source/_static/railroad-diagrams.css b/docs/source/_static/railroad-diagrams.css new file mode 100644 index 0000000..4b22793 --- /dev/null +++ b/docs/source/_static/railroad-diagrams.css @@ -0,0 +1,26 @@ +svg.railroad-diagram { + background-color: hsl(30,20%,95%); +} +svg.railroad-diagram path { + stroke-width: 3; + stroke: black; + fill: rgba(0,0,0,0); +} +svg.railroad-diagram text { + font: bold 14px monospace; + text-anchor: middle; +} +svg.railroad-diagram text.label { + text-anchor: start; +} +svg.railroad-diagram text.comment { + font: italic 12px monospace; +} +svg.railroad-diagram g.non-terminal text { + /*font-style: italic;*/ +} +svg.railroad-diagram rect { + stroke-width: 3; + stroke: black; + fill: hsl(120,100%,90%); +} diff --git a/docs/source/_static/rr_assign.svg b/docs/source/_static/rr_assign.svg new file mode 100644 index 0000000..3724cd7 --- /dev/null +++ b/docs/source/_static/rr_assign.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + +def + + + + + + +pattern + + + + + + + + + + + + + + + + + +exit + + + + + + +order + + + + + + + + + + + + + + + + + + +:= + + + + + + +assign + + + + + + + + + + + + + + + +var + + + + + + +bind + + + + + + +pattern + + + + + + +:= + + + + + + +assign + + + + + + + + + + + +lval + + + + + + +:= + + + + + + +assign + + + + + + + +@op=...XXX + + + + + + +VERB_ASSIGN XXX + + \ No newline at end of file diff --git a/docs/source/_static/rr_bind.svg b/docs/source/_static/rr_bind.svg new file mode 100644 index 0000000..3bc2dcc --- /dev/null +++ b/docs/source/_static/rr_bind.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + +bind + + + + + + +noun + + + + + + + + + + + + + + + + + +: + + + + + + +guard + + + + + + + +objectExpr@@ + + \ No newline at end of file diff --git a/docs/source/_static/rr_block.svg b/docs/source/_static/rr_block.svg new file mode 100644 index 0000000..6297c3a --- /dev/null +++ b/docs/source/_static/rr_block.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + +{ + + + + + + + + + + + + + + + + + + + + + +blockExpr + + + + + + +expr + + + + + + + +; + + + + + + + +} + + \ No newline at end of file diff --git a/docs/source/_static/rr_blockExpr.svg b/docs/source/_static/rr_blockExpr.svg new file mode 100644 index 0000000..b36e419 --- /dev/null +++ b/docs/source/_static/rr_blockExpr.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + +if + + + + + + +escape + + + + + + +for + + + + + + +fn + + + + + + +switch + + + + + + +try + + + + + + +while + + + + + + +when + + + + + + +bind + + + + + + +object + + + + + + +def + + + + + + +interface + + + + + + +meta + + + + + + +pass + + \ No newline at end of file diff --git a/docs/source/_static/rr_call.svg b/docs/source/_static/rr_call.svg new file mode 100644 index 0000000..7a84c91 --- /dev/null +++ b/docs/source/_static/rr_call.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + +calls + + + + + + + + + + + + + + + + + +curry + + + \ No newline at end of file diff --git a/docs/source/_static/rr_calls.svg b/docs/source/_static/rr_calls.svg new file mode 100644 index 0000000..ab46bba --- /dev/null +++ b/docs/source/_static/rr_calls.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + +prim + + + + + + + + + + +calls + + + + + + + + + + + + + + + + + + + + +. + + + + + + +<- + + + + + + + + + +.String. + + + + + + +IDENTIFIER + + + + + + + + + + +( + + + + + + + + + + + + + + + + + +expr + + + + + + +, + + + + + + + +) + + + + + + + +getExpr + + \ No newline at end of file diff --git a/docs/source/_static/rr_comp.svg b/docs/source/_static/rr_comp.svg new file mode 100644 index 0000000..b30acfd --- /dev/null +++ b/docs/source/_static/rr_comp.svg @@ -0,0 +1,90 @@ + + + + + + + + + +order + + + + + + + + + + + + + + + + + + + + +=~ + + + + + + +!~ + + + + + + +== + + + + + + +!= + + + + + + +&! + + + + + + +^ + + + + + + +& + + + + + + +| + + + + + + +comp + + + \ No newline at end of file diff --git a/docs/source/_static/rr_comprehension.svg b/docs/source/_static/rr_comprehension.svg new file mode 100644 index 0000000..fa6590e --- /dev/null +++ b/docs/source/_static/rr_comprehension.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + +pattern + + + + + + +in + + + + + + +iter + + + + + + +expr + + + + + + + + + + + +pattern + + + + + + +=> + + + + + + +pattern + + + + + + +in + + + + + + +iter + + + + + + +expr + + + + + + +=> + + + + + + +expr + + + \ No newline at end of file diff --git a/docs/source/_static/rr_curry.svg b/docs/source/_static/rr_curry.svg new file mode 100644 index 0000000..9876474 --- /dev/null +++ b/docs/source/_static/rr_curry.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + +. + + + + + + +<- + + + + + + + + + +.String. + + + + + + +IDENTIFIER + + \ No newline at end of file diff --git a/docs/source/_static/rr_def.svg b/docs/source/_static/rr_def.svg new file mode 100644 index 0000000..ac1662d --- /dev/null +++ b/docs/source/_static/rr_def.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + +def + + + + + + + + + + + + + + + + + + + + +bind + + + + + + +noun + + + + + + + + + + + + + + + + + +: + + + + + + +guard + + + + + + + + +noun + + + + + + + + + +objectFunction@@ + + + + + + +assign + + + + + + + +assign + + \ No newline at end of file diff --git a/docs/source/_static/rr_escape.svg b/docs/source/_static/rr_escape.svg new file mode 100644 index 0000000..6c3f8a0 --- /dev/null +++ b/docs/source/_static/rr_escape.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + +escape + + + + + + +pattern + + + + + + +block + + + + + + + + + + + + + + + + + +catch + + + + + + +pattern + + + + + + +block + + + \ No newline at end of file diff --git a/docs/source/_static/rr_exports.svg b/docs/source/_static/rr_exports.svg new file mode 100644 index 0000000..1eca3d4 --- /dev/null +++ b/docs/source/_static/rr_exports.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + +export + + + + + + +( + + + + + + + + + + + + + + + + + +noun + + + + + + + + + + + +) + + \ No newline at end of file diff --git a/docs/source/_static/rr_expr.svg b/docs/source/_static/rr_expr.svg new file mode 100644 index 0000000..7ddf0e5 --- /dev/null +++ b/docs/source/_static/rr_expr.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + +continue + + + + + + +break + + + + + + +return + + + + + + + + + + + + + +( + + + + + + +) + + + + + + + +; + + + + + + +blockExpr + + + + + + + +assign + + \ No newline at end of file diff --git a/docs/source/_static/rr_fn.svg b/docs/source/_static/rr_fn.svg new file mode 100644 index 0000000..0ed7b35 --- /dev/null +++ b/docs/source/_static/rr_fn.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + +fn + + + + + + + + + + + + + + + + + +pattern + + + + + + +, + + + + + + + +block + + \ No newline at end of file diff --git a/docs/source/_static/rr_for.svg b/docs/source/_static/rr_for.svg new file mode 100644 index 0000000..f5a612e --- /dev/null +++ b/docs/source/_static/rr_for.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + +for + + + + + + +pattern + + + + + + + + + + + + + + + + + +=> + + + + + + +pattern + + + + + + + +in + + + + + + +comp + + + + + + +block + + + + + + + + + + + + + + + + + +catch + + + + + + +pattern + + + + + + +block + + + \ No newline at end of file diff --git a/docs/source/_static/rr_getExpr.svg b/docs/source/_static/rr_getExpr.svg new file mode 100644 index 0000000..1617d0d --- /dev/null +++ b/docs/source/_static/rr_getExpr.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + +calls + + + + + + + + + +[ + + + + + + + + + + + + + + + + + +expr + + + + + + +, + + + + + + + +] + + \ No newline at end of file diff --git a/docs/source/_static/rr_guard.svg b/docs/source/_static/rr_guard.svg new file mode 100644 index 0000000..73795be --- /dev/null +++ b/docs/source/_static/rr_guard.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + +IDENTIFIER + + + + + + + + + + + + + + + + + +[ + + + + + + + + + + +expr + + + + + + +, + + + + + + + +] + + + + + + + + + + + + +( + + + + + + +expr + + + + + + +) + + + \ No newline at end of file diff --git a/docs/source/_static/rr_if.svg b/docs/source/_static/rr_if.svg new file mode 100644 index 0000000..df250c6 --- /dev/null +++ b/docs/source/_static/rr_if.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + +if + + + + + + +( + + + + + + +expr + + + + + + +) + + + + + + +block + + + + + + + + + + + + + + + + + +else + + + + + + + + + + + + + +if + + + + + + +blockExpr@@ + + + + + + + +block + + + \ No newline at end of file diff --git a/docs/source/_static/rr_imports.svg b/docs/source/_static/rr_imports.svg new file mode 100644 index 0000000..ac89d06 --- /dev/null +++ b/docs/source/_static/rr_imports.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + +pattern + + + + + + + \ No newline at end of file diff --git a/docs/source/_static/rr_infix.svg b/docs/source/_static/rr_infix.svg new file mode 100644 index 0000000..dfdb5ea --- /dev/null +++ b/docs/source/_static/rr_infix.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + +comp + + + + + + + + + + + + + + + + + + + + +|| + + + + + + +&& + + + + + + +infix + + + \ No newline at end of file diff --git a/docs/source/_static/rr_interface.svg b/docs/source/_static/rr_interface.svg new file mode 100644 index 0000000..52b5864 --- /dev/null +++ b/docs/source/_static/rr_interface.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + +interface + + + + + + +namePattern + + + + + + + + + + + + + + + + + +guards + + + + + + +pattern + + + + + + + + + + + + + + + + + + +extends + + + + + + + + + + +order + + + + + + +, + + + + + + + + +implements_@@ + + + + + + +msgs@@ + + \ No newline at end of file diff --git a/docs/source/_static/rr_iter.svg b/docs/source/_static/rr_iter.svg new file mode 100644 index 0000000..60b57c1 --- /dev/null +++ b/docs/source/_static/rr_iter.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + +order + + + + + + + + + + + + + + + + + +if + + + + + + +comp + + + \ No newline at end of file diff --git a/docs/source/_static/rr_lval.svg b/docs/source/_static/rr_lval.svg new file mode 100644 index 0000000..43c8639 --- /dev/null +++ b/docs/source/_static/rr_lval.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + +noun + + + + + + +getExpr + + \ No newline at end of file diff --git a/docs/source/_static/rr_mapItem.svg b/docs/source/_static/rr_mapItem.svg new file mode 100644 index 0000000..18400e2 --- /dev/null +++ b/docs/source/_static/rr_mapItem.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + +=> + + + + + + + + + + + + + +& + + + + + + +noun + + + + + + + + + + + +&& + + + + + + +noun + + + + + + + +noun + + + + + + + + + + + +expr + + + + + + +=> + + + + + + +expr + + + \ No newline at end of file diff --git a/docs/source/_static/rr_mapPatternItem.svg b/docs/source/_static/rr_mapPatternItem.svg new file mode 100644 index 0000000..8417733 --- /dev/null +++ b/docs/source/_static/rr_mapPatternItem.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + +=> + + + + + + +namePattern + + + + + + + + + + + + + + + + + + +( + + + + + + +expr + + + + + + +) + + + + + + + +.String. + + + + + + +.int. + + + + + + +.float64. + + + + + + +.char. + + + + + + +=> + + + + + + +pattern + + + + + + + + + + + + + + + + + + +:= + + + + + + +order + + + \ No newline at end of file diff --git a/docs/source/_static/rr_meta.svg b/docs/source/_static/rr_meta.svg new file mode 100644 index 0000000..6c2a31c --- /dev/null +++ b/docs/source/_static/rr_meta.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + +meta + + + + + + +. + + + + + + + + + + + + + +context + + + + + + +( + + + + + + +) + + + + + + + + + + + +getState + + + + + + +( + + + + + + +) + + + \ No newline at end of file diff --git a/docs/source/_static/rr_module.svg b/docs/source/_static/rr_module.svg new file mode 100644 index 0000000..2cc75fd --- /dev/null +++ b/docs/source/_static/rr_module.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + +module + + + + + + +imports + + + + + + + + + + + + + +exports + + + + + + + +block + + \ No newline at end of file diff --git a/docs/source/_static/rr_namePattern.svg b/docs/source/_static/rr_namePattern.svg new file mode 100644 index 0000000..4aadf84 --- /dev/null +++ b/docs/source/_static/rr_namePattern.svg @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + +:: + + + + + + +.String. + + + + + + + +IDENTIFIER + + + + + + + + + + + + + + + + + +: + + + + + + +guard + + + + + + + + + + + + +var + + + + + + +noun + + + + + + + + + + + + + + + + + +: + + + + + + +guard + + + + + + + + + + + + +& + + + + + + +noun + + + + + + + + + + + + + + + + + +: + + + + + + +guard + + + + + + + + + + + + +&& + + + + + + +noun + + + + + + + + + + + +bind + + + + + + +noun + + + + + + + + + + + + + + + + + +: + + + + + + +guard + + + + \ No newline at end of file diff --git a/docs/source/_static/rr_noun.svg b/docs/source/_static/rr_noun.svg new file mode 100644 index 0000000..a38de94 --- /dev/null +++ b/docs/source/_static/rr_noun.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + +IDENTIFIER + + + + + + + + + + +:: + + + + + + +.String. + + + \ No newline at end of file diff --git a/docs/source/_static/rr_object.svg b/docs/source/_static/rr_object.svg new file mode 100644 index 0000000..105be7b --- /dev/null +++ b/docs/source/_static/rr_object.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + +object + + + + + + + + + + + + + +bind + + + + + + +noun + + + + + + + +_ + + + + + + +noun + + + + + + + + + + + + + + + + + +: + + + + + + +guard + + + + + + + +objectExpr@@ + + \ No newline at end of file diff --git a/docs/source/_static/rr_order.svg b/docs/source/_static/rr_order.svg new file mode 100644 index 0000000..7edf156 --- /dev/null +++ b/docs/source/_static/rr_order.svg @@ -0,0 +1,146 @@ + + + + + + + + + +prefix + + + + + + + + + + + + + + + + + + + + +** + + + + + + +* + + + + + + +/ + + + + + + +// + + + + + + +% + + + + + + ++ + + + + + + +- + + + + + + +<< + + + + + + +>> + + + + + + +.. + + + + + + +..! + + + + + + +> + + + + + + +< + + + + + + +>= + + + + + + +<= + + + + + + +<=> + + + + + + +order + + + \ No newline at end of file diff --git a/docs/source/_static/rr_pass.svg b/docs/source/_static/rr_pass.svg new file mode 100644 index 0000000..209b79f --- /dev/null +++ b/docs/source/_static/rr_pass.svg @@ -0,0 +1,12 @@ + + + + + + + + + +pass + + \ No newline at end of file diff --git a/docs/source/_static/rr_pattern.svg b/docs/source/_static/rr_pattern.svg new file mode 100644 index 0000000..8d1c618 --- /dev/null +++ b/docs/source/_static/rr_pattern.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + +namePattern + + + + + + +quasiLiteral + + + + + + + + + + + + + +== + + + + + + +!= + + + + + + +prim + + + + + + + + + + + +_ + + + + + + +: + + + + + + +guard + + + + + + + + + + + +via + + + + + + +( + + + + + + +expr + + + + + + +) + + + + + + +pattern + + + + + + + + + + + +[ + + + + + + + + + + +mapPatternItem + + + + + + +, + + + + + + + +] + + + + + + + + + + + + + + + + + + +? + + + + + + +( + + + + + + +expr + + + + + + +) + + + \ No newline at end of file diff --git a/docs/source/_static/rr_prefix.svg b/docs/source/_static/rr_prefix.svg new file mode 100644 index 0000000..90be730 --- /dev/null +++ b/docs/source/_static/rr_prefix.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + +- + + + + + + +prim + + + + + + + + + + + + + + +~ + + + + + + +! + + + + + + +call + + + + + + + + + + + +& + + + + + + +noun + + + + + + + + + + + +&& + + + + + + +noun + + + + + + + + + + + +call + + + + + + + + + + + + + + + + + +: + + + + + + +guard + + + + \ No newline at end of file diff --git a/docs/source/_static/rr_prim.svg b/docs/source/_static/rr_prim.svg new file mode 100644 index 0000000..7e65be6 --- /dev/null +++ b/docs/source/_static/rr_prim.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + +.String. + + + + + + +.int. + + + + + + +.float64. + + + + + + +.char. + + + + + + +quasiliteral + + + + + + +IDENTIFIER + + + + + + + + + + +:: + + + + + + +.String. + + + + + + + + + + + +( + + + + + + +expr + + + + + + +) + + + + + + + + + + + +{ + + + + + + + + + + + + + + + + + +expr + + + + + + +; + + + + + + + +} + + + + + + + + + + + +[ + + + + + + + + + + + + + + + + + +expr + + + + + + +, + + + + + + + + + + + + + + + +expr + + + + + + +=> + + + + + + +expr + + + + + + + +, + + + + + + + + + + + +for + + + + + + +comprehension + + + + + + + +] + + + \ No newline at end of file diff --git a/docs/source/_static/rr_quasiliteral.svg b/docs/source/_static/rr_quasiliteral.svg new file mode 100644 index 0000000..0e90158 --- /dev/null +++ b/docs/source/_static/rr_quasiliteral.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + +IDENTIFIER + + + + + + +` + + + + + + + + + + + + + + + + + + + + + +... + + + + + + +$IDENT + + + + + + + + + + +${ + + + + + + +expr + + + + + + +} + + + + + + + +@IDENT + + + + + + + + + + +@{ + + + + + + +expr + + + + + + +} + + + + + + + + + + + + + +` + + \ No newline at end of file diff --git a/docs/source/_static/rr_switch.svg b/docs/source/_static/rr_switch.svg new file mode 100644 index 0000000..7aed18a --- /dev/null +++ b/docs/source/_static/rr_switch.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + +switch + + + + + + +( + + + + + + +expr + + + + + + +) + + + + + + +{ + + + + + + + + + + + + + + +match + + + + + + +pattern + + + + + + +block + + + + + + + + + + + + +} + + \ No newline at end of file diff --git a/docs/source/_static/rr_try.svg b/docs/source/_static/rr_try.svg new file mode 100644 index 0000000..c253fee --- /dev/null +++ b/docs/source/_static/rr_try.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + +try + + + + + + +block + + + + + + + + + + + + + + + + + + + + + +catch + + + + + + +pattern + + + + + + +block + + + + + + + + + + + + + + + + + + + + + + + +finally + + + + + + +block + + + \ No newline at end of file diff --git a/docs/source/_static/rr_when.svg b/docs/source/_static/rr_when.svg new file mode 100644 index 0000000..90ff02b --- /dev/null +++ b/docs/source/_static/rr_when.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + +when + + + + + + +( + + + + + + + + + + +expr + + + + + + +, + + + + + + + +) + + + + + + + + + + + + + + + + + + + + + +catch + + + + + + +pattern + + + + + + +block + + + + + + + + + + + + + + + + + + + + + + + +finally + + + + + + +block + + + \ No newline at end of file diff --git a/docs/source/_static/rr_while.svg b/docs/source/_static/rr_while.svg new file mode 100644 index 0000000..b28e7d3 --- /dev/null +++ b/docs/source/_static/rr_while.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + +while + + + + + + +( + + + + + + +expr + + + + + + +) + + + + + + +block + + + + + + + + + + + + + + + + + +catch + + + + + + +pattern + + + + + + +block + + + \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index fc9697a..62d47f4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -87,6 +87,9 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' +# And the default language for this repository is... +highlight_language = 'monte' + # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] diff --git a/docs/source/custom-guards.rst b/docs/source/custom-guards.rst new file mode 100644 index 0000000..cbea557 --- /dev/null +++ b/docs/source/custom-guards.rst @@ -0,0 +1,50 @@ +====================== +Creating Custom Guards +====================== + +Like many other subsystems in Monte, guards can be made from any ordinary +object which implements the correct methods. + +The Basics +========== + +The main method for a guard is ``coerce/2``, which takes an object to examine, +called the **specimen**, and an ejector. If the specimen conforms to the +guard, then the guard returns the conformed value; otherwise, the ejector is +used to abort the computation. + +:: + + object Any: + to coerce(specimen, _): + return specimen + + + object Void: + to coerce(_, _): + return null + +Here are two example guards, ``Any`` and ``Void``. ``Any`` passes all +specimens through as-is, and ``Void`` ignores the specimen entirely, always +returning ``null``. + +Here's an actual test. The ``Empty`` guard checks its specimen, which is a +:doc:`container`, for emptiness and ejects on failure:: + + object Empty: + to coerce(specimen, ej): + if (specimen.size() != 0): + throw.eject(ej, `$specimen was not empty`) + +The ejector does not need to have a meaningful object (nor even a string) as +its payload, but the payload may be used for diagnostic purposes by the +runtime. For example, a debugger might display them to a developer, or a +debugging feature of the runtime might record them to a log. + +Guards and Variable Slots +========================= + +.. note:: + There should be a section about ``makeSlot`` and whether it will be part + of the slot API. Also something about whether ejections can happen in + varslots. diff --git a/docs/source/design.rst b/docs/source/design.rst new file mode 100644 index 0000000..6815fae --- /dev/null +++ b/docs/source/design.rst @@ -0,0 +1,42 @@ +=============== +Design of Monte +=============== + +This is a gentle overview of the design features of Monte and how it fits into +the larger genealogy of programming languages. + +Types +===== + +Monte features strong dynamic types. By "strong" we mean that Monte's types +resist automatic coercion; by "dynamic" we mean that objects are not +necessarily specialized to any specific type. + +As an example of strong typing in Monte, consider the following statement:: + + def x := 42 + true + +This statement will result in an error, because ``true`` is a boolean value +and cannot be automatically transformed into an integer, float, or other value +which integers will accept for addition. + +Functional Features +=================== + +Monte has support for the various language features required for programming +in the so-called "functional" style. Monte supports closing over values (by +reference and by binding), and Monte also supports creating new function +objects at runtime. This combination of features enables functional +programming patterns. + +Monte also has several features similar to those found in languages in the +Lisp and ML families which are often conflated with the functional style, like +strict lexical scoping, immutable builtin value types, and currying for +message passing. + +Object-Based Features +===================== + +Monte is descended from the Smalltalk family of languages, and as such, +is an **object-based** language. "Object-oriented" styles of programming are +largely available in Monte. diff --git a/docs/source/faq.rst b/docs/source/faq.rst index f166f17..a4c7d9c 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -1,71 +1,87 @@ +================== Answered Questions ================== -Why the name? -------------- +Community +~~~~~~~~~ -It's like Monty Python, but with E. +What is the origin of Monte's name? +----------------------------------- -What can you know about an object? ----------------------------------- +The Monte language has its roots in the E and Python languages. We took +"Monty" from "Monty Python", and put an "e" in there. Thus, "Monte". -Any object that you can access meets one of three criteria: +Why is Monte called a "dynamic language"? +----------------------------------------- -* You created it, -* You were born with it, or -* You received it as a result of passing messages to something that met either - of the first two criteria. +Monte is dynamic in three ways. -Additionally, you can use guards and auditors to ensure properties of an -object. +Dynamic Typing + Monte is **unityped**, in formal type theory. For the informal engineer, + Monte is "untyped" or "dynamically typed"; the type of a value might not + be known at runtime, and "types are open". +Dynamic Binding + Monte's polymorphism is late-binding. It is possible to pass a message to + an object that will never able to handle that message. +Dynamic Compiling + Monte can compile and run Monte code at runtime, as part of its core + language. -Note that using ``when`` on a promise for a far reference still results in a -far reference. +Object Capabilities +~~~~~~~~~~~~~~~~~~~ +How do I know which capabilities I have? +---------------------------------------- -Parallelism? ---------------- +Any object that you can access meets one of three criteria: -Monte doesn't really say anything about parallelism per se. We *should* -though. If we're going to be agoric, we should say something about CPUs, even -if it's just that people should spin up more vats and make more code use -farrefs. +* You created it, +* You were born with it, or +* You received it as a result of passing messages to something that met either + of the first two criteria. -Concurrency? ------------- +An object has the capabilities of all objects that it can access with these +three rules. -The one concurrency pattern in Monte is that, if you absolutely have to -perform near-ref operations on a far-ref, you must make a when-expression. -This allows the compiler to transform it into far-ref operations. +.. note:: + This answer still isn't satisfying. Neither is this question, really. +Monte in Theory +~~~~~~~~~~~~~~~ -How do you send a message to an object? ------------------------------------------- +How do I perform parallel computations? +--------------------------------------- -In E (and Monte), there are two ways to send a message to an object. +Currently, we haven't specified it. Run multiple processes to get node-level +parallelism. -1) Use the method call, foo.baz() -2) Use eventual send, foo <- baz() +.. note:: + Monte doesn't really say anything about parallelism per se. We *should* + though. If we're going to be agoric, we should say something about CPUs, + even if it's just that people should spin up more vats and make more code + use farrefs. +How do I perform concurrent operations? +--------------------------------------- -Are all messages eligible for both methods of sending? ---------------------------------------------------------- +Spawn more vats. All vats are concurrently turning. -A call (#1) is immediate and returns the value of whatever in foo handles that -message, probably a method. +Are all messages eligible for both methods of sending? +------------------------------------------------------ -An eventual send (#2) returns a promise for the result (in particular, foo does -not receive the messages until the end of the current turn (event loop -iteration), and eventual messages are delivered in order.) Calls are only -allowed for near, resolved objects. Sends can be made to far or unresolved -objects (promises) +In short, yes. The details follow. -All messages are eligible for both kinds of sending, but not all objects can -receive messages in both ways. +Nearly all Monte objects are unable to distinguish passed messages based on +how they were delivered. A few extremely special runtime objects can make the +distinction, but they are the only exception. User-defined objects cannot tell +whether they received a message via call or send. References? ----------- +.. note:: + Messy. + There are three words about references: near/far, settled/unsettled, resolved/unresolved @@ -73,183 +89,206 @@ near/far, settled/unsettled, resolved/unresolved http://www.erights.org/elib/concurrency/refmech.html A near reference is to an object in the same vat, whereas a far reference is -to an object elsewhere. +to an object elsewhere. References are settled if they won't change to a different reference state. They can be compared with == and used as hashtable keys. Settled/unsettled is more or less the same as resolved/unresolved, although -edge cases in previous implementations have required the distinction. +edge cases in previous implementations have required the distinction. A reference is either a promise or resolved. A resolved reference is either near, far, or broken. Near references can have synchronous calls made on them. Promises, far references, and broken references will raise an exception if -synchronous calls are made. +synchronous calls are made. -Functions? ----------- +Does Monte have functions? +-------------------------- -Since everything in Monte is an object, you're always calling methods rather -than functions. +No. Since everything in Monte is an object, you're always calling methods +rather than functions. -A function is actually an object with a single run() method. In other words, -``def f() { ... }`` always desugars to ``object f { to run() { ... } }``. - +Monte does have a convention that objects with a single method with the verb +``run`` are functions. There is no difference, to Monte, between this +function:: -Everything's a method? ----------------------- + def f(): + pass -Well, everything's a message pass, rather. +And this object:: + object f: + to run(): + pass Does this mean we should never make synchronous calls? ------------------------------------------------------ +.. note:: + Ugh. This could probably be its own page! + No. There are many kind of objects on which synchronous calls work, because they are near references. For example, all literals are near: ``def lue := -(6).mul(7)``. +(6).mul(7)``. When in doubt, remember that there is a ``near`` guard which can be used to confirm that an object is in the same vat as you and thus available for -synchronous calls. +synchronous calls. +What are ejectors? +------------------ -What's Monte's comment syntax? ---------------------------------- - -.. code-block:: monte - - # This comment goes to the end of the line - /** This comment is multi-line. - Yes, it starts with a two stars - and ends with only one. - These should only be used for docstrings. */ - - -What does "dynamic" mean, when used to describe Monte? ---------------------------------------------------------- - -Dynamic typing, dynamic binding, dynamic compiling. +An ejector is an object that aborts the current computation and returns to +where it was created. They are created by ``escape`` expressions. +An ejector can be passed as deeply as one wants, but cannot be used outside of +the ``escape`` that created it. This is called the **delimited** property of +ejectors. -What are ejectors? ------------------- +Ejectors cannot be used multiple times. The first time an ejector is used, the +``escape`` block aborts computation, resulting in the value of the ejector. +Subsequent clever uses of the ejector will fail. This is called the **single +use** property. -Ejectors can be thought of as named breaks. You can break out of any -expression with them. For example, early returns are implemented with them. -An ejector has indefinite scope, so you can pass it to methods etc and -invoking it unwinds the stack. +Monte implements the ``return``, ``break``, and ``continue`` expressions with +ejectors. -Return, Break, and Continue are all implemented as ejectors. If you're -familiar with call/current continuation shenanigans, it's like that, but -delimited so that it can't be called outside the scope that created it. +To be fully technical, ejectors are "single-use delimited continuations". -Blocking operators? -------------------- +Monte in Practice +~~~~~~~~~~~~~~~~~ -Not available in Monte. Because of the no-stale-stack-frames policy, Monte -has neither generators nor threads nor C#-style async/await. +How do I force an object to be a certain type? +---------------------------------------------- -At an 'await' or 'yield' point, you don't know what subset of the code has -already been read. Since Monte is intended to be useful in an environment with -an unbounded amount of code, and 'await' and 'yield' force you to assume that -all of the code has been read, they cannot be available in Monte. +Use a guard that coerces objects to be of that type. Guards for all of the +primitive types in Monte are already builtin; see the documentation on +:doc:`guards` for more details. +How do I pass a message to an object? +------------------------------------- -What's a stale stack frame? ---------------------------- +There are two ways to pass a message. First, the **immediate call**:: -A stale stack frame is one that isn't currently running. + def result := obj.message(argument) -Since state is mutable, your code's behavior is always affected by the stack -frames above it. If you violate strict stack ordering (as generators do), you -violate the assumptions that people make when reading and writing such code. +And, second, the **eventual send**:: -Vats? ------ + def promisedResult := obj<-message(argument) -http://erights.org/elib/concurrency/vat.html might help +How do I perform a conditional expression? What is Monte's ternary operator? +---------------------------------------------------------------------------- -A vat's an object that sits on the border of the runtime and is responsible -for containing, guarding, and passing messages to the objects inside of it. +Monte does not have a ternary operator. However, in exchange, the ``if`` +expression can be used where any other expression might be placed. As an +example, consider a function that tests whether an argument is even:: -"A Vat is vaguely like a traditional OS process -- it bundles together a -single thread of control and an address space of synchronously accessible data" + def even(i :Int) :Str: + if (i % 2 == 0): + return "yes" + else: + return "no" +Monte lacks the ternary operator, but permits using regular conditional +expressions in its place. We can refactor this example to pull the ``return`` +outside of the ``if``:: + def even(i :Int) :Str: + return if (i % 2 == 0) {"yes"} else {"no"} -Farrefs? --------- +Don't forget that Monte requires ``if`` expressions to evaluate their +condition to a ``Bool``. -Farrefs are references to far objects, namely objects in different vats. Messages -to far objects can only be sent asynchronously. +How do I write comments? +------------------------ +This is a single-line comment:: -Promises? ---------- + # Lines starting with a # are single-line comments. + # They only last until the end of the line. -ES6 promises were derived from E's. -The crucial part is, when promises are resolved they become forwarders to -their values. +And this is a multi-line comment:: + /** This comment is multi-line. + Yes, it starts with two stars, + but ends with only one. + These should only be used for docstrings. */ -Selfless objects? ------------------ +What's the difference between the ``m`` and ``M`` objects? +---------------------------------------------------------- -Some objects can always be near, even if they were initially far, if they can -be serialized in a way that allows them to be reconstituted in another vat. -This quality is known as being selfless, and objects with it include ints, -floats, strings, and objects that you define correctly. +``M`` is a helper object that provides several runtime services. It can pass +messages on behalf of other objects and quote strings. -Selfless objects are "passed by construction", meaning that instructions for -creating a near version are passed over the wire. +``m`` is a quasiparser which parses Monte source code. It is part of the +runtime Monte compiler. +Differences With Python +~~~~~~~~~~~~~~~~~~~~~~~ -Psuedomonadic joining on promises ---------------------------------- +Where did ``self`` go? +---------------------- -Monte has a mechanic which can be called pseudomonadic joining on promises. +Newcomers to Monte are often surprised to learn that Monte lacks a ``this`` or +``self`` keyword. In fact, Monte does have ways to refer to the current object, +but there's a deeper conceptual difference between Monte and other object-based +languages. -This means that a promise becomes the value for the promise: +Monte does not have a ``this`` or ``self`` keyword because Monte objects can +refer to their "member" or "private" names without qualification. This is a +consequence of how Monte objects are built. Consider this object maker:: -.. code-block:: + def makeMyObject(): + return object myObject: + pass - def p := foo<-bar(); def p2 := p<-baz() +Let's modify it slightly. We want to give this object a "private" value secret +which cannot be accessed directly, and a method ``getSecret/0`` which will +return it. We put "private" in quotation marks to emphasize that Monte does not +have private names. Instead, all names are private in Monte; if one cannot see +a name, then one cannot access it. -Because when-exprs evaluate to a promise as well, you can have something like +:: -.. code-block:: + def makeMyObject(secret): + return object myObject: + to getSecret(): + return secret - def p := foo<-bar(); def p2 := when (p) -> { p.doStuff() }; p2<-baz() +And that's it. No declarations of object contents or special references to ``this`` +or ``self``. -Will the iterable control when the computations are performed? ------------------------------------------------------------------ +We can also simulate "member" names for objects. As before, we can achieve +this effect without ``this``. -That's way outside the scope of an iteration protocol +:: -Let's talk about the _lazy_ iteration protocol -------------------------------------------------- + def makeMyObject(): + var counter :Int := 0 + return object myObject: + to getCounter(): + return counter += 1 - We can just do like everybody else and have explicit laziness, can't we? -Or do we want language-level extra-lazy stuff? +Here, ``counter`` is not visible outside of ``makeMyObject()``, which means +that no other object can directly modify it. Each time we call +``makeMyObject()``, we get a new object called ``myObject`` with a new counter. -.. code-block:: monte +.. note:: + Remember, Monte is an expression language. ``counter += 1`` returns the + value of ``counter``. That's why ``return counter += 1`` works. - def workItems := [lazyNext(someIter) for _ in 0..!cores] - # or to be less handwavey - def workItems := [someIter.lazyNext() for _ in 0..!cores] +What's the "no stale stack frame" policy? +----------------------------------------- -lazyNext() is like .next() but it either returns - 1) a near value if it's immediately available - 2) a promise if it's not - 3) a broken promise if you've iterated off the end -Even this isn't right, but the idea is that you could use something like -twisted's coiterate to serially compute some items in a iterable, a few at a -time and as they were made available, the promises in workItems would get -resolved +A stale stack frame is one that isn't currently running; it is neither the +current stack frame nor below the current stack frame. -What are M and m? ------------------ +The "no stale stack frame" policy is a policy in Monte's design: Monte forbids +suspending computation mid-frame. There are no coroutines or undelimited +continuations in Monte. Monte also does not have an "async/await" syntax, +since there is no way to implement this syntax without stale stack frames. -M is a singleton providing runtime services including passing messages to -farrefs. m is the quasiparser for monte source code. +The policy is justified by readability concerns. Since Monte permits mutable +state, one author's code's behavior could be affected by another author's code +running further up the frame stack. Stale frames make comprehension of code +much harder as a result. diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst new file mode 100644 index 0000000..2543d93 --- /dev/null +++ b/docs/source/glossary.rst @@ -0,0 +1,18 @@ +======== +Glossary +======== + +.. glossary:: + + retractable + A guard that is not :term:`unretractable`. + + unretractable + An unretractable guard, informally, cannot be fooled by impostor + objects that only pretend to be guarded, and it also will not change + its mind about an object on two different coercions. + + Formally, an :dfn:`unretractable` guard Un is a guard such that for + all Monte objects, if any given object is successfully coerced by Un, + then it will always be successfully coerced by Un, regardless of the + internal state of Un or the object. diff --git a/docs/source/guards.rst b/docs/source/guards.rst new file mode 100644 index 0000000..61d0f6f --- /dev/null +++ b/docs/source/guards.rst @@ -0,0 +1,67 @@ +.. _guards: + +====== +Guards +====== + +.. note:: + This section sucks less. It still has a harsh opening though. Maybe + something could be said about typical guard usage, or some more source + code examples could be written? + +:: + + def someName :SomeGuard exit ej := someExpr + +A guard is a syntactic element which ensures that an object has a certain +property. Guards are used to (in)formally prove that sections of code behave +correctly. A guard examines a value and returns a (possibly different) value +which satisfies its property, or ejects or otherwise aborts the computation. + +We call the process of a guard examining an object **coercion**. The object +being examined and coerced is called the **specimen**. + +Builtin Guards +============== + +Monte comes equipped with several very useful guards. + +Type-checking +------------- + +Several builtin guards are used for asserting that a value is of a given type: + +* ``Void`` for ``null``, the only value of its type +* ``Bool`` for the Boolean values ``true`` and ``false`` +* ``Char`` for Unicode code points +* ``Double`` for IEEE 754 floating-point numbers +* ``Int`` for integers +* ``List`` for lists +* ``Map`` for maps +* ``Set`` for sets + +These guards have useful features for more precisely asserting that the +guarded values are within certain ranges. The ``Char``, ``Double``, and +``Int`` guards support subranges of values via comparison expressions:: + + def x :('a'..'z' | 'A'..'Z') := 'c' + def y :(Double >= 4.2) := 7.0 + def z :(Int < 5) := 3 + +Additionally, the ``List`` and ``Set`` guards can be specialized on +*subguards*, which are just regular guards that check each value in the set or +list:: + + def ints :List[Int] := [1, 2, 4, 6, 8] + def setOfUppercaseChars :Set['A'..'Z'] := ['A', 'C', 'E', 'D', 'E', 'C', 'A', 'D', 'E'].asSet() + +Other Builtin Guards +-------------------- + +Some other builtin guards are worth mentioning: + +* ``Any`` is a guard that accepts anything. +* ``NullOk`` accepts ``null``. Specializing it creates a guard that accepts + ``null`` or whatever the subguard accepts. +* ``Same`` must be specialized, returning a guard which only accepts values + that are ``==`` to the value on which it was specialized. diff --git a/docs/source/index.rst b/docs/source/index.rst index ec4b409..33efe5d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,12 +17,25 @@ For users: symbols monte-for-wizards modules + design + iteration + guards + custom-guards + patterns + tubes + miranda + quasiparsers + interfaces + syntax For Developers: .. toctree:: + montefesto tools + wizard + glossary @@ -32,4 +45,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/docs/source/interfaces.rst b/docs/source/interfaces.rst new file mode 100644 index 0000000..9d12a8f --- /dev/null +++ b/docs/source/interfaces.rst @@ -0,0 +1,50 @@ +========== +Interfaces +========== + +An :dfn:`interface` is a syntactic expression which defines an object +protocol. An interface has zero or more method signatures, and can be +implemented by any object which has methods with equivalent signatures to the +interface. + +Let's jump right in:: + + interface Trivial: + "A trivial interface." + +This interface comes with a docstring, which is not required but certainly a +good idea, and nothing else. Any object could implement this interface:: + + object trivia implements Trivial: + "A trivial object implementing a trivial interface." + +When an object **implements** an interface, the interface behaves like any +other auditor and examines the object for compliance with the object protocol. +As with other auditors, the difference between the "implements" and "as" +keywords is whether the object is required to pass the auditor:: + + object levity as Trivial: + "A trivial object which is proven to implement Trivial." + +Let's look at a new interface. This interface carries some **method +signatures**. + +:: + + interface GetPut: + "Getting and putting." + to get() + to put(value) + + object getAndPut as GetPut: + "A poor getter and putter." + + to get(): + return "get" + + to put(_): + null + +We can see that ``getAndPut`` implements the ``GetPut`` interface, but it +isn't very faithful to that interface. Interfaces cannot enforce behavior, +only signatures. diff --git a/docs/source/intro.rst b/docs/source/intro.rst index facc73c..9ed0ed5 100644 --- a/docs/source/intro.rst +++ b/docs/source/intro.rst @@ -16,7 +16,7 @@ Why Monte? Python is great for usability, but has all the security vulnerabilities of its prececessors. E is a relatively obscure language whose fundamental design precludes many types of common vulnerability, but its syntax is difficult to -use and its implementations don't perform competitively. +use and its implementations don't perform competitively. Where do I start? ----------------- @@ -30,6 +30,13 @@ code. If you have problems, join us in #monte on irc.freenode.net, ask your question (use a pastebin_ to share any errors, rather than pasting into the channel), and wait a few hours if nobody is around. +If you'd like to contribute to Monte, check out the Monte_ and Typhon_ issue +trackers and the `pipe dreams`_ wiki page. It's also worth grepping for +``TODO`` in the source of both projects. + +.. _Monte: https://github.com/monte-language/monte/issues +.. _Typhon: https://github.com/monte-language/typhon/issues +.. _pipe dreams: https://github.com/monte-language/monte/wiki/Pipe-Dreams .. _Python: https://docs.python.org/2/tutorial/ .. _E: http://www.skyhunter.com/marcs/ewalnut.html .. _repo: https://github.com/monte-language/monte @@ -39,7 +46,7 @@ Using Monte =========== To use the Monte implementation hosted in Python, it's best to set up a -virtualenv: +virtualenv: .. code-block:: console @@ -47,7 +54,7 @@ virtualenv: $ source v/bin/activate $ pip install -r requirements.txt -To run Monte code (with your virtualenv activated): +To run Monte code (with your virtualenv activated): .. code-block:: console @@ -57,20 +64,47 @@ The Repl -------- Many languages have an interpreter or "Read - Evaluate - Print Loop" for -testing code. Monte's should be documented here if/when it gets one. +testing code. Monte's should be documented here if/when it gets one. Indentation ----------- Standardize your indentation to use spaces, because tabs are a syntax error in -Monte. +Monte. Monte core library code uses four-space indentation. However, any +indentation can be used as long as it's consistent throughout the module. + +Scoping Rules +------------- + +Monte is lexically scoped, with simple scoping rules. In general, names are +only accessible within the scope in which they were defined. + +After an object has been created, the names visible to it aren't accessible +from outside the object. This is because Monte objects cannot share their +internal state; they can only respond to messages. For programmers coming from +object-oriented languages with access modifiers, such as ``private`` and +``protected``, this is somewhat like if there were only one access modifier +for variables, ``private``. (And only one access modifier for methods, +``public``.) + +Closing Over Bindings +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: monte + + var x := 42 + object obj: + to run(): + return x += 1 + +Here, ``obj`` can see ``x``, permitting the usage of ``x`` within ``obj``'s +definition. When ``obj.run()`` is called, ``x`` will be mutated. Monte does +not require any "global" or "nonlocal" keywords to do this. + +Capability Model +---------------- -* 1 space: How can you read that? -* 2 spaces: *sigh* you must be a Googler. -* 3 spaces: What? -* **4 spaces**: Yes. Good coder. Use 4 spaces. -* 5 spaces: No, five is right out. -* 8 spaces: How can you read that? +.. note:: Not sure whether this should be here, or in a separate page. No object created within a scope will be accessible outside of that scope, unless a message about it is passed out. In Monte, the only way for object A @@ -80,32 +114,111 @@ to know that B exists is: * If A created B * If any object that A knows about passed A a message about B -See scope.mt for an example. +For example:: + + def scope(): + def a := 1 + def innerScope(): + def b := 2 + traceln(`a is $a and b is $b`) + + # This line would cause a compile-time error, since the name `b` isn't + # accessible in this scope! + # traceln(`I cannot access $b here`) + + return innerScope + + scope()() Debugging Stuff --------------- -Monte strives to provide useful error messages. +Monte strives to provide useful error messages. -Currently the most convenient way to print out messages from your program is -with the trace() and traceln() built-in functions. The only difference between -them is that traceln() automatically adds a newline. +Currently the most convenient way to print out messages from your program is +with the ``trace()`` and ``traceln()`` built-in functions. The only difference +between them is that ``traceln()`` automatically adds a newline. Methods, Objects, Variables --------------------------- Named values can be either final or variable. A final object cannot be -changed, whereas a variable one can be changed later. See variables.mt. +changed, whereas a variable one can be changed later:: + + var myVariableValue := 6 + + myVariableValue *= 7 + + trace("My variable value: ") + traceln(`$myVariableValue`) + + def myFinalValue := 42 + # Trying to change a final value will result in a compile-time error. See + # what happens when this next line is uncommented! + # myFinalValue /= 6 + + trace("My final value: ") + traceln(`$myFinalValue`) + +Everything is an object. New objects are created with a ``object`` keyword:: + + object helloThere: + to greet(whom): + traceln(`Hello, my dear $whom!`) -Everything is an object. Some objects are created automatically, such as -variables and methods. Other objects are created explicitly, such as -demonstrated in obj.mt. + helloThere.greet("Student") -Objects can also be created by functions, such as shown in createobj.mt. +Objects can also be created by functions:: + + def makeSalutation(time): + return object helloThere: + to greet(whom): + traceln(`Good $time, my dear $whom!`) + + def hi := makeSalutation("morning") + + hi.greet("Student") + +Object Composition +------------------ + +Monte has a simpler approach to object composition and inheritance than many +other object-based and object-oriented languages. Instead of classes or +prototypes, Monte has a simple single syntax for constructing objects, the +object expression:: + + object myObject: + pass + +Unlike Java, Monte objects are not constructed from classes. Unlike JavaScript +or Python, Monte objects are not constructed from prototypes. As a result, it +might not be obvious at first how to build multiple objects which are similar +in behavior. However, Monte has a very simple idiom for class-like constructs. + +:: + + def makeMyObject(): + return object myObject: + pass + +Methods can be attached to objects with the to keyword:: + + object deck: + to size(): + return 52 + +Finally, just like with functions, methods can have guards on their parameters +and return value:: + + object deck: + to size(suits :Int, ranks :Int) :Int: + return suits * ranks Built-In Types -------------- +Monte provides some classic and common value types directly in the syntax. + Int ~~~ @@ -123,22 +236,26 @@ Char ~~~~ Monte's character type is distinct from the string type. Characters are always -surrounded by apostrophes (`'`) and are always unicode. +surrounded by apostrophes (``'``) and are always unicode. -.. warning:: In Python, you may be accustomed to 'single' and "double" quotes - functioning interchangeably. In Monte, double quotes can contain any number - of letters, but single quotes can only hold a single character. +.. warning:: + + In Python, you may be accustomed to 'single' and "double" quotes + functioning interchangeably. In Monte, double quotes can contain any + number of letters, but single quotes can only hold a single character. .. code-block:: monte def u := '☃' +Characters are permitted to be adorable. + String ~~~~~~ Strings are objects with built-in methods and capabilities, rather than -character arrays. Monte's strings are always unicode, like Python3 (but unlike -Python2). Strings are always surrounded by double-quotes (`"`). +character arrays. Monte's strings are always unicode, like Python 3 (but +unlike Python 2). Strings are always surrounded by double-quotes (``"``). .. code-block:: monte @@ -146,18 +263,89 @@ Python2). Strings are always surrounded by double-quotes (`"`). def t := s.replace("World", "Monte hackers") # Hello Monte hackers! def u := "¿Dónde aquí habla Monte o español?" +Lists +~~~~~ + +Among Monte's collection types, the list is a very common type. Lists are +heterogenous ordered unsorted collections with sequencing and indexing, and +have the performance characteristics of arrays in C, vectors in C++, or lists +in Python:: + + def l := ['I', "love", "Monte", 42, 0.5] + def x := l[3] # x == 42 + +Special Characters +------------------ + +In lists and strings, special characters and unicode values can be escaped: + ++-----------------+---------------------------------+ +| Escape Sequence | Meaning | ++=================+=================================+ +| ``\\`` | Backslash (``\``) | ++-----------------+---------------------------------+ +| ``\'`` | Single quote (``'``) | ++-----------------+---------------------------------+ +| ``\"`` | Double quote (``"``) | ++-----------------+---------------------------------+ +| ``\b`` | ASCII Backspace (BS) | ++-----------------+---------------------------------+ +| ``\f`` | ASCII Formfeed (FF) | ++-----------------+---------------------------------+ +| ``\n`` | ASCII Linefeed (LF) | ++-----------------+---------------------------------+ +| ``\r`` | ASCII Carriage Return (CR) | ++-----------------+---------------------------------+ +| ``\t`` | ASCII Horizontal Tab (TAB) | ++-----------------+---------------------------------+ +| ``\uxxxx`` | Character with 16-bit hex value | +| | *xxxx* (Unicode only) | ++-----------------+---------------------------------+ +| ``\Uxxxxxxxx`` | Character with 32-bit hex value | +| | *xxxxxxxx* (Unicode only) | ++-----------------+---------------------------------+ +| ``\xhh`` | Character with hex value *hh* | ++-----------------+---------------------------------+ + +(table mostly from `the Python docs `_) + +.. note:: + + Monte intentionally avoids providing escape notation for ASCII vertical + tabs (``\v``) and octal values (``\o00``) because it is a language of the + future and in the future, nobody uses those. Hexadecimal escapes are still + valid for vertical tabs. + +.. note:: + + As with Python, a backslash (``\``) as the final character of a line + escapes the newline and causes that line and its successor to be + interpereted as one. Data Structures --------------- -Monte has lists built in natively, and various other data structures +Monte has native lists and maps, as well as various other data structures implemented in the language. +Monte Modules +------------- + +A Monte module is a single file. The last statement in the file describes what +it exports. If the last statement in a file defines a method or object, that +method or object is what you get when you import it. If you want to export +several objects from the same file, the last line in the file should simply be +a list of their names. + +To import a module, simply use `def bar = import("foo")` where the filename of +the module is foo.mt. See the files module.mt and imports.mt for an example of +how to export and import objects. + Testing ------- .. note:: Tests are not automatically discovered at present. You need to add - your test to monte/src/package.mt for it to be run correctly. + your test to a package.mt file for it to be run correctly. Unit tests are essential to writing good code. Monte's testing framework is designed to make it simple to write and run good tests. See the testing.mt_ @@ -165,6 +353,10 @@ module for a simple example. Note that for more complex objects, you may need to implement an `_uncall()` method which describes how to recreate the object out of Monte's built-in primitives. Additionally, such objects will need to implement the Selfless interface in order to guarantee they won't have mutable -state so that they can be compared. +state so that they can be compared. + +To test the Python tools surrounding Monte, use Trial. For instance, ``trial +monte.test.test_ast`` (when run from the root of the project) will run the ast +tests. .. _testing.mt: https://github.com/monte-language/monte/blob/master/monte/src/examples/testing.mt diff --git a/docs/source/iteration.rst b/docs/source/iteration.rst new file mode 100644 index 0000000..390f51e --- /dev/null +++ b/docs/source/iteration.rst @@ -0,0 +1,154 @@ +========= +Iteration +========= + +Monte comes with a simple and robust iteration protocol. + +The for-loop +============ + +The simple structure of the ``for`` loop should be familiar in structure to +Python programmers:: + + for value in iterable: + traceln(value) + +A ``for`` loop takes an iterable object and a pattern, and matches each +element in the iterable to the pattern, executing the body of the loop. +``for`` loops permit skipping elements with the ``continue`` keyword:: + + for value in iterable: + if skippable(value): + continue + +They also permit exiting prematurely with the ``break`` keyword:: + + for value in iterable: + if finalValue(value): + break + +All builtin containers are iterable, including lists, maps, and sets. Strings +are also iterable, yielding characters. + +Patterns +======== + +``for`` loops are pattern-based, so arbitrary patterns are permitted in +loops:: + + for some`$sort of @pattern` in iterable: + useThat(pattern) + +Pair Syntax and Keys +==================== + +Unlike other languages, Monte iteration always produces a pair of objects at a +time, called the **key** and **value**. A bit of syntax enables +pattern-matching on the key:: + + for key => value in iterable: + traceln(key) + traceln(value) + +As expected, the key for iteration on a map is the key in the map +corresponding to each value. The key for iteration on lists and strings is the +zero-based index of each item or character. + +It is possible to iterate only over the keys, of course, using an ignore +pattern:: + + for key => _ in iterable: + traceln(key) + +Loops as Expressions +==================== + +Like all structures in Monte, ``for`` loops are expressions, which means that +they can return values and be used where other expressions are used. + +A ``for`` loop usually returns ``null``:: + + def result := for value in 0..10 { value } + +Here, ``result`` is ``null``. + +However, a ``for`` loop can return another value with the ``break`` keyword:: + + def result := for value in 0..10 { break value } + +Since ``break`` was used, the loop exits on its first iteration, returning +``value``, which was ``0``. So ``result`` is ``0``. + +.. note:: + + The syntax of ``break`` permits parentheses around the return value, like + ``break(this)``, and also an empty pair of parentheses to indicate a null + return value, like so: ``break()``. + +Comprehensions +============== + +``for`` loops aren't the only way to consume iterable objects. Monte also has +**comprehensions**, which generate new collections from iterables:: + + [transform(value) for value in iterable] + +This will build and return a list. Maps can also be built with pair syntax:: + + [key => makeValue(key) for key in keyList] + +And, of course, pair syntax can be used for both the pattern and expression in +a comprehension:: + + [value => key for key => value in reverseMap] + +Comprehensions also support *filtering* by a condition. The conditional +expression is called a **predicate** and should return ``true`` or ``false``, +depenting on whether the current value should be *skipped*. For example, let's +generate a list of even numbers:: + + def evens := [number for number in 0..20 if number % 2 == 0] + +Unlike many other languages, the predicate must return a Boolean value; if it +doesn't, then the entire comprehension will fail with an exception. + +Writing Your Own Iterables +========================== + +Monte has an iteration protocol which defines iterable and iterator objects. +By implementing this protocol, it is possible for user-created objects to be +used in ``for`` loops and comprehensions. + +Iterables need to have ``to _makeIterator()``, which returns an iterator. +Iterators need to have ``to next(ej)``, which takes an ejector and either +returns a list of ``[key, value]`` or fires the ejector with any value to end +iteration. Guards do not matter but can be helpful for clarity. + +As an example, let's look at an iterable that counts upward from zero to +infinity:: + + object countingIterable: + to _makeIterator(): + var i := 0 + return object counter: + to next(_): + def rv := [i, i] + i += 1 + return rv + +Since the iterators ignore their ejectors, iteration will never terminate. + +For another example, let's look at an iterator that wraps another iterator and +only lets even values through:: + + def onlyEvens(iterator): + return object evens: + to next(ej): + var rv := iterator.next(ej) + while (rv[1] % 2 != 0): + rv := iterator.next(ej) + return rv + +Note that the ejector is threaded through ``to next(ej)`` into the inner +iterator in order to allow iteration to terminate if/when the inner iterator +becomes exhausted. diff --git a/docs/source/miranda.rst b/docs/source/miranda.rst new file mode 100644 index 0000000..b05c5f6 --- /dev/null +++ b/docs/source/miranda.rst @@ -0,0 +1,121 @@ +================ +Miranda Protocol +================ + +.. epigraph:: + If you cannot afford a method, one will be appointed for you. + +Monte objects, left to their own devices, are black boxes; one cannot perform +any sort of introspection on them. However, there are some powers granted to +anybody who can refer to an object. The runtime grants these powers +automatically, and we refer to them as the **Miranda protocol**. + +The Miranda protocol grants powers in the form of methods, called **Miranda +methods**, which all objects automatically possess. An object may provide its +own Miranda methods, but does not have to; objects are automatically granted +default Miranda methods with correct behavior. Or, as stated above, "if an +object does not have a Miranda method, one will be provided." + +Safety +====== + +Miranda methods should be safe to call. The default definitions will always +respond without throwing exceptions. It is rude but permissible for an object +to provide a custom Miranda method implementation which can throw or eject, or +return incorrect or misleading information. Therefore, be aware of situations +in which Miranda methods are being used. + +.. warning:: + Special mention goes here to the most commonly-called Miranda method, + ``_printOn/1``. Any time that an object is being turned into a string, it + almost certainly involves a little bit of ``_printOn/1``, so be careful. + +Methods +======= + +``_conformTo/1`` + ``_conformTo`` takes a guard and coerces this object to that guard, if + possible. The default implementation returns ``null`` for all guards. + Overriding this method lets an object become other objects when under + scrutiny by guards. + +``_getAllegedType/0`` + ``_getAllegedType`` returns an interface describing this object. If not + specified, an interface which represents the object faithfully will be + created and returned. + + .. note:: + We're gonna rename this to something that doesn't use the word "type" + at some point in the near future. Probably "shape" or "interface"? + + .. warning:: + We haven't implemented this one yet. + +``_printOn/1`` + ``_printOn`` writes text representing this object onto the printer passed + as an argument. + + Customizing ``_printOn`` lets an object change how it is pretty-printed. + The default pretty-printing algorithm is readable but does not divulge the + internal state of an object. + +``_respondsTo/2`` + ``_respondsTo(verb, arity)`` returns a Boolean value indicating whether + this object will respond to a message with the given verb and arity. The + default implementation indicates whether the object's source code listed a + method with the given verb and arity. + + .. warning:: + Determining whether a given object responds to a given message is + undecidable. Therefore, there are times when ``_respondsTo/2`` is + unavoidably wrong, both with false positives and false negatives. + +``_sealedDispatch/1`` + ``_sealedDispatch`` permits this object to discriminate its responses to + messages based on the capabilities of the calling object. + + Occasionally, a calling object will wish to prove its capabilities by + passing some sort of key or token to a receiving object. The receiving + object may then examine the key, and return an object based on the + identity or value of the key. + + We provide ``_sealedDispatch/1`` for a specific subset of these cases. The + caller should pass a brand, and the receiver dispatches on the brand, + returning either a sealed box guarded by the passed-in brand, or ``null`` + if the brand wasn't recognized. + + By default, ``_sealedDispatch`` returns ``null``. This makes it impossible + to determine whether an object actually has a customized + ``_sealedDispatch``. + + A popular analogy for sealed dispatch is the story of the "Red Phone," a + direct line of communication between certain governments in the past. The + Red Phone doesn't ring often, but when it does, you generally know who's + calling. They'll identify themselves, and if you can confirm that it's + the correct caller, then you can have discussions with them that you + wouldn't have over an ordinary phone. + +``_uncall/0`` + ``_uncall`` undoes the call that created this object. The default + implementation returns ``null``, because objects are, by default, not + uncallable. A good implementation of ``_uncall`` will return a list + containing ``[maker, verb, args]`` such that ``M.call(maker, verb, args)`` + will produce a new object which is equal to this object. + + Providing an instance of ``_uncall`` makes an object eligible for + uncall-based catamorphisms (fold, reduce, ...). In particular, uncallable objects are + comparable by value. + + .. note:: + At some point in the near future, you'll need to both implement + ``_uncall`` and also pass an audition proving that your uncall is + correct in order to gain the benefit of uncallability. + +``_whenBroken/1`` + ``_whenBroken``, by default, does nothing on near objects and sends + notifications of breakage through references. It is not interesting. + +``_whenMoreResolved/1`` + ``_whenMoreResolved``, by default, does nothing on near objects and sends + notifications of partial fulfillment through references. It is not + interesting. diff --git a/docs/source/modules.rst b/docs/source/modules.rst index 373b48c..1fd1113 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -19,7 +19,7 @@ module configuration with objects. Created from a module structure. package - A module composed from other modules. + A module structure composed from various module configurations. package script A source file run at link time that produces a module structure for @@ -33,7 +33,7 @@ requirement Module Declaration Syntax ------------------------- -Modules start with a `module` declaration of the form:: +Module files start with a `module` declaration of the form:: module dependency1, dependency2, ... export (name1, name2, ...) @@ -48,8 +48,9 @@ Mechanics Scripts are run in a scope with an ``import(name, parameters)`` function, which can be invoked to load modules. The name is used to locate either a module file or a directory containing a package script -(currently required to be named ``package.mt``). The module is loaded -and its exports are returned as a map. +(currently required to be named ``package.mt``). A configuration is +created from the structure read from this, and then loaded with the +parameters given, and its exports are returned as a map. Package Scripts @@ -60,8 +61,11 @@ package loader object ``pkg``. The package provides these methods: +``readFile(relativePath)`` + Read the module file at the given path and return a module structure. + ``readFiles(relativePath)`` - Creates a map of module names to module structures, for all modules + Creates a map of module names to module structures, for all module files found recursively on the given path. ``readPackage(relativePath)`` diff --git a/docs/source/montefesto.rst b/docs/source/montefesto.rst new file mode 100644 index 0000000..f9784f3 --- /dev/null +++ b/docs/source/montefesto.rst @@ -0,0 +1,88 @@ +========== +Montefesto +========== + +.. epigraph:: + + Secure distributed computation should not be hard. + + -- Corbin, on Monte + +This is the roadmap for Monte development according to Allen and Corbin. If +you want to work on anything on this list, let us know; we're very accepting +of new contributors. + +2015 +==== + +* "Exit stealth mode"; display a sleek and friendly front page to neophytes + and visitors which explains: + + * Why Monte exists + * What Monte can do + * How to get started using Monte + * Licensing and code reuse policies + * Monte branding + +* Have stories for: + + * Writing high-performance Monte code + * Debugging faulty Monte code + * Writing large-scale Monte code + * Developing modular Monte codebases + +* Finish key language features + + * Records + * Named arguments + * m`` + * Bytes + * Finalize on-disk (on-wire) compiled code format + * printer features + * Tubes + * Auditors + * Farrefs + * Arity overloading deprecation + +* Finish key runtime features + + * Decide whether key C/etc. libraries should be bound and exposed (unsafely) + to user-level code: + + * libsodium + * sqlite + +* Finish key compiler features + + * The compiler should be much faster. Concrete goal: Compile a single Monte + module of at least 2KLoC within 500ms on a typical reference machine + (two-core laptop circa 2012.) + * While proving the compiler correct would be arduous, it should certainly + be more solid than it currently is. + * Compiler error messages are currently completely lost. This is not what we + wanted. + +* Finish key integration features + + * Debugger + * IDE support + + * vim (Corbin) + * emacs (Allen) + * sublime/atom (Mike?) + + * Profiling + + * Time + * Space + * Coverage + * Turns + * Vats + * IPC/Networking + +2016 +==== + +We currently don't know what we're going to do for 2016. Possibilities range +from MonteCon to The Monte Foundation to nothing at all. Who knows? It is a +mystery~ diff --git a/docs/source/patterns.rst b/docs/source/patterns.rst new file mode 100644 index 0000000..26857e1 --- /dev/null +++ b/docs/source/patterns.rst @@ -0,0 +1,213 @@ +======== +Patterns +======== + +Monte comes with a powerful and extensible subsystem for destructuring and +viewing objects, called the **pattern subsystem**. A *pattern* is a rule which +conditionally matches objects and binds parts of the matched objects to names. + +Pronounciation +============== + +Speak the name of the pattern, followed by "pattern": "This is a via pattern +with a such-that pattern inside." + +All Patterns +============ + +An exhaustive list of all patterns available in Monte is provided. They are +all shown in the context of the ``def`` expression. + +Kernel Patterns +--------------- + +These patterns are core to the Monte language and are present even in compiled +code. They cannot be reimplemented in terms of other Monte code. + +Ignore +~~~~~~ + +:: + + def _ := value + +Equivalent to ``value``. Does nothing. + +:: + + def _ :Guard := value + +Performs :ref:`guard ` coercion and discards the result. + +Final +~~~~~ + +:: + + def name := value + +One of the most ubiquitous patterns. Binds a name unconditionally to a +``FinalSlot`` and prohibits reassignment. + +:: + + def name :Guard := value + +Like above, but coerced by a :ref:`guard `. + +Var +~~~ + +May be pronounced "var" or "variable". + +:: + + var name := value + var name :Guard := value + +Like a final pattern, but with ``VarSlot`` as the slot, which permits +reassignment to the name later on using an assign expression. + +.. note:: + + While ``var`` can be used to introduce a var pattern, the overall + expression is still a def expression, and it can alternatively be + expressed as:: + + def var name := value + + This is useful for nesting var patterns within other patterns:: + + def [first, var second] := value + +Bind +~~~~ + +:: + + def &&name := binding + +A bind pattern does not bind a name, but binds a *binding*. + +List +~~~~ + +:: + + def [first, second] + tail := value + +A list pattern has two pieces, the **head** and the **tail**, joined by ``+``. +This mirrors construction of a list via addition. The head can be any sequence +of patterns. The tail is an optional pattern and defaults to ``==[]``, +matching exactly the empty list. + +List patterns match ``ConstLists`` of at least the same length as the head, +where each subpattern in the head matches the corresponding element in the +list. The rest of the list is collected into the tail and the tail pattern is +matched against it. + +Via +~~~ + +:: + + def via (view) patt := value + +Via patterns contain a **view** (sometimes called a **transformation**) and a +subpattern. The view is an expression which takes a specimen and ejector and +returns a transformed specimen on success or ejects on failure. This is +similar to a guard but permits much richer transformations in addition to +simple tests. + +A via pattern matches if its view successfully transforms the specimen and the +subpattern matches the transformed specimen. + +Non-Kernel Patterns +------------------- + +These richer patterns permit more powerful destructuring and are safe to use +alongside kernel patterns. + +Exactly +~~~~~~~ + +:: + + def ==specimen := value + +Exactly patterns contain a single expression and match if (and only if) +``value == specimen`` according to typical Monte semantics. + +While this particular formulation of an exactly pattern might not be very +useful, it can be handy as a pattern in switch expressions. + +Not +~~~ + +:: + + def !=specimen := value + +Exactly patterns contain a single expression and match if (and only if) +``value != specimen`` according to typical Monte semantics. + +Such-That +~~~~~~~~~ + +:: + + def patt ? (condition) := value + +The such-that pattern contains a subpattern and a **condition**, not unlike +the condition expression in an ``if`` expression. The such-that pattern first +speculatively performs the pattern match in its subpattern, and then succeeds +or fails based on whether the condition evaluates to ``true`` or ``false``. + +Map +~~~ + +:: + + def ["first" => second, "third" => fourth] | tail := value + +Like a list pattern deconstructing a list, a map pattern deconstructs a ``ConstMap`` and gathers its values. + +Keys can be literals (strings, integers, etc.) but cannot be patterns. + +The tail of the map will be a map of the key/value pairs which were not +matched in the head. The tail pattern defaults to ``==[].asMap()``. + +:: + + # def ["first" => first, "second" => second] := value + def [=> first, => second] := value + +This short syntax for map patterns matches values where the keys are the +strings corresponding to the identifiers. + +:: + + def ["key" => patt := "default value"] := value + +Any pair in a map pattern can have a default value using the above syntax. In +this example, the ``patt`` subpattern will be asked to match against either +the value corresponding to ``"key"``, or ``"default value"``. + +Quasiliteral +~~~~~~~~~~~~ + +:: + + def `$value holes and @pattern holes` := specimen + +Any quasiliteral can be used as a pattern. + +Slot +~~~~ + +:: + + def &name := slot + +The slot pattern, like the bind pattern, allows definition of the slot behind +a name. diff --git a/docs/source/quasiparsers.rst b/docs/source/quasiparsers.rst new file mode 100644 index 0000000..8407c7f --- /dev/null +++ b/docs/source/quasiparsers.rst @@ -0,0 +1,60 @@ +============ +Quasiparsers +============ + +Quasiparsers ("QP" for short) are part of the **quasiliteral** (QL) subsystem +of Monte. QLs are an essential characteristic of Monte, so the design and +production of QPs should be simple and easy. + +Basic Usage +=========== + +QLs are literal objects that reflect the syntax of some language not native to +Monte. They are formed by an identifier indicating which QP to use and a pair +of backticks:: + + def ql := name`and some text` + +The exact object created by a QP varies depending on the QP used. One of the +most common QPs used is called ``simple``. ``simple`` formats the QL text and +returns a string:: + + def hello :String := simple`Hello world!` + +``simple`` is so common that Monte defaults to using it if no QP is +specified:: + + def another :String := `is formed from using simple by default` + +Another QP provided in the safe scope is ``m``, which parses Monte literal +expressions and returns a code object. ``m`` is useful for code generation:: + + def expr := m`2 + 2` + +Values +------ + +Of course, the QL system might not seem very useful if all it can do is turn +literals into objects. We call them *quasi*-literal because we can +syntactically interact with QLs to vary the produced objects. + +:: + + def name := "Joe" + def greeting :String := `Hello, $name!` + +In this example, ``name`` is interpolated into the QL string to produce +"Hello, Joe!" + +Patterns +-------- + +At this point, QLs seem like a very useful tool for constructing objects. They +can also be used to pull objects apart. Just like many other things in Monte, +QLs can be used as patterns:: + + def greeting := "Hello, world!" + def `Hello, @name!` := greeting + +Examine this carefully. This pattern is assigning to ``name``, asserting that +the rest of the pattern (the "Hello, " and "!" fragments) match the specimen. diff --git a/docs/source/railroad_diagrams.py b/docs/source/railroad_diagrams.py new file mode 100644 index 0000000..cf99d42 --- /dev/null +++ b/docs/source/railroad_diagrams.py @@ -0,0 +1,427 @@ +# Display constants +VERTICAL_SEPARATION = 8 +ARC_RADIUS = 10 +DIAGRAM_CLASS = 'railroad-diagram' +TRANSLATE_HALF_PIXEL = True +INTERNAL_ALIGNMENT = 'center' +DEBUG=False + +# Assume a monospace font with each char .5em wide, and the em is 16px +CHARACTER_ADVANCE = 8 + +def e(text): + return str(text).replace('&', '&').replace('"', '"').replace('<', '<') + +def determineGaps(outer, inner): + diff = outer - inner + if INTERNAL_ALIGNMENT == 'left': + return 0, diff + elif INTERNAL_ALIGNMENT == 'right': + return diff, 0 + else: + return diff/2, diff/2 + + + +class DiagramItem(object): + def __init__(self, name, attrs=None, text=None): + self.name = name + self.attrs = attrs or {} + self.children = [text] if text else [] + self.needsSpace = False + + def format(self, x, y, width): + raise NotImplementedError # Virtual + + def addTo(self, parent): + parent.children.append(self) + return self + + def writeSvg(self, write): + write('<{0}'.format(self.name)) + for name, value in sorted(self.attrs.items()): + write(' {0}="{1}"'.format(name, e(value))) + write('>\n') + for child in self.children: + if isinstance(child, DiagramItem): + child.writeSvg(write) + else: + write(e(child)) + write(''.format(self.name)) + + +class Path(DiagramItem): + def __init__(self, x, y): + DiagramItem.__init__(self, 'path', {'d': 'M%s %s' % (x, y)}) + + def m(self, x, y): + self.attrs['d'] += 'm{0} {1}'.format(x,y) + return self + + def h(self, val): + self.attrs['d'] += 'h{0}'.format(val) + return self + + right = h + + def left(self, val): + return self.h(-val) + + def v(self, val): + self.attrs['d'] += 'v{0}'.format(val) + return self + + down = v + + def up(self, val): + return self.v(-val) + + def arc(self, sweep): + x = ARC_RADIUS + y = ARC_RADIUS + if sweep[0] == 'e' or sweep[1] == 'w': + x *= -1 + if sweep[0] == 's' or sweep[1] == 'n': + y *= -1 + cw = 1 if sweep == 'ne' or sweep == 'es' or sweep == 'sw' or sweep == 'wn' else 0 + self.attrs['d'] += 'a{0} {0} 0 0 {1} {2} {3}'.format(ARC_RADIUS, cw, x, y) + return self + + + def format(self): + self.attrs['d'] += 'h.5' + return self + + +def wrapString(value): + return value if isinstance(value, DiagramItem) else Terminal(value) + + +class Diagram(DiagramItem): + def __init__(self, *items): + DiagramItem.__init__(self, 'svg', {'class': DIAGRAM_CLASS}) + self.items = [Start()] + [wrapString(item) for item in items] + [End()] + self.width = 1 + sum(item.width + (20 if item.needsSpace else 0) + for item in self.items) + self.up = max(item.up for item in self.items) + self.down = max(item.down for item in self.items) + self.formatted = False + + def format(self, paddingTop=20, paddingRight=None, paddingBottom=None, paddingLeft=None): + if paddingRight is None: + paddingRight = paddingTop + if paddingBottom is None: + paddingBottom = paddingTop + if paddingLeft is None: + paddingLeft = paddingRight + x = paddingLeft + y = paddingTop + self.up + g = DiagramItem('g') + if TRANSLATE_HALF_PIXEL: + g.attrs['transform'] = 'translate(.5 .5)' + for item in self.items: + if item.needsSpace: + Path(x, y).h(10).addTo(g) + x += 10 + item.format(x, y, item.width).addTo(g) + x += item.width + if item.needsSpace: + Path(x, y).h(10).addTo(g) + x += 10 + self.attrs['width'] = self.width + paddingLeft + paddingRight + self.attrs['height'] = self.up + self.down + paddingTop + paddingBottom + self.attrs['viewBox'] = "0 0 {width} {height}".format(**self.attrs) + g.addTo(self) + self.formatted = True + return self + + + def writeSvg(self, write): + if not self.formatted: + self.format() + return DiagramItem.writeSvg(self, write) + + +class Sequence(DiagramItem): + def __init__(self, *items): + DiagramItem.__init__(self, 'g') + self.items = [wrapString(item) for item in items] + self.width = sum(item.width + (20 if item.needsSpace else 0) + for item in self.items) + self.up = max(item.up for item in self.items) + self.down = max(item.down for item in self.items) + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "sequence" + + def format(self, x, y, width): + leftGap, rightGap = determineGaps(width, self.width) + Path(x, y).h(leftGap).addTo(self) + Path(x+leftGap+self.width, y).h(rightGap).addTo(self) + x += leftGap + for item in self.items: + if item.needsSpace: + Path(x, y).h(10).addTo(self) + x += 10 + item.format(x, y, item.width).addTo(self) + x += item.width + if item.needsSpace: + Path(x, y).h(10).addTo(self) + x += 10 + return self + + +class Choice(DiagramItem): + def __init__(self, default, *items): + DiagramItem.__init__(self, 'g') + assert default < len(items) + self.default = default + self.items = [wrapString(item) for item in items] + self.width = ARC_RADIUS * 4 + max(item.width for item in self.items) + self.up = 0 + self.down = 0 + for i, item in enumerate(self.items): + if i < default: + self.up += max(ARC_RADIUS, item.up + item.down + VERTICAL_SEPARATION) + elif i == default: + self.up += max(ARC_RADIUS, item.up) + self.down += max(ARC_RADIUS, item.down) + else: + assert i > default + self.down += max(ARC_RADIUS, VERTICAL_SEPARATION + item.up + item.down) + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "choice" + + def format(self, x, y, width): + leftGap, rightGap = determineGaps(width, self.width) + + # Hook up the two sides if self is narrower than its stated width. + Path(x, y).h(leftGap).addTo(self) + Path(x + leftGap + self.width, y).h(rightGap).addTo(self) + x += leftGap + + last = len(self.items) - 1 + innerWidth = self.width - ARC_RADIUS * 4 + + # Do the elements that curve above + above = self.items[:self.default] + if above: + distanceFromY = max( + ARC_RADIUS * 2, + self.items[self.default].up + + VERTICAL_SEPARATION + + self.items[self.default - 1].down) + for i, item in list(enumerate(above))[::-1]: + Path(x, y).arc('se').up(distanceFromY - ARC_RADIUS * 2).arc('wn').addTo(self) + item.format(x + ARC_RADIUS * 2, y - distanceFromY, innerWidth).addTo(self) + Path(x + ARC_RADIUS * 2 + innerWidth, y - distanceFromY).arc('ne') \ + .down(distanceFromY - ARC_RADIUS*2).arc('ws').addTo(self) + distanceFromY += max( + ARC_RADIUS, + item.up + + VERTICAL_SEPARATION + + (self.items[i - 1].down if i > 0 else 0)) + + # Do the straight-line path. + Path(x, y).right(ARC_RADIUS * 2).addTo(self) + self.items[self.default].format(x + ARC_RADIUS * 2, y, innerWidth).addTo(self) + Path(x + ARC_RADIUS * 2 + innerWidth, y).right(ARC_RADIUS * 2).addTo(self) + + # Do the elements that curve below + below = self.items[self.default + 1:] + for i, item in enumerate(below): + if i == 0: + distanceFromY = max( + ARC_RADIUS * 2, + self.items[self.default].down + + VERTICAL_SEPARATION + + item.up) + Path(x, y).arc('ne').down(distanceFromY - ARC_RADIUS * 2).arc('ws').addTo(self) + item.format(x + ARC_RADIUS * 2, y + distanceFromY, innerWidth).addTo(self) + Path(x + ARC_RADIUS * 2 + innerWidth, y + distanceFromY).arc('se') \ + .up(distanceFromY - ARC_RADIUS * 2).arc('wn').addTo(self) + distanceFromY += max( + ARC_RADIUS, + item.down + + VERTICAL_SEPARATION + + (below[i + 1].up if i+1 < len(below) else 0)) + return self + + +def Optional(item, skip=False): + return Choice(0 if skip else 1, Skip(), item) + + +class OneOrMore(DiagramItem): + def __init__(self, item, repeat=None): + DiagramItem.__init__(self, 'g') + repeat = repeat or Skip() + self.item = wrapString(item) + self.rep = wrapString(repeat) + self.width = max(self.item.width, self.rep.width) + ARC_RADIUS * 2 + self.up = self.item.up + self.down = max( + ARC_RADIUS * 2, + self.item.down + VERTICAL_SEPARATION + self.rep.up + self.rep.down) + self.needsSpace = True + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "oneormore" + + def format(self, x, y, width): + leftGap, rightGap = determineGaps(width, self.width) + + # Hook up the two sides if self is narrower than its stated width. + Path(x, y).h(leftGap).addTo(self) + Path(x + leftGap + self.width, y).h(rightGap).addTo(self) + x += leftGap + + # Draw item + Path(x, y).right(ARC_RADIUS).addTo(self) + self.item.format(x + ARC_RADIUS, y, self.width - ARC_RADIUS * 2).addTo(self) + Path(x + self.width - ARC_RADIUS, y).right(ARC_RADIUS).addTo(self) + + # Draw repeat arc + distanceFromY = max(ARC_RADIUS*2, self.item.down + VERTICAL_SEPARATION + self.rep.up) + Path(x + ARC_RADIUS, y).arc('nw').down(distanceFromY - ARC_RADIUS * 2) \ + .arc('ws').addTo(self) + self.rep.format(x + ARC_RADIUS, y + distanceFromY, self.width - ARC_RADIUS*2).addTo(self) + Path(x + self.width - ARC_RADIUS, y + distanceFromY).arc('se') \ + .up(distanceFromY - ARC_RADIUS * 2).arc('en').addTo(self) + + return self + + +def ZeroOrMore(item, repeat=None): + result = Optional(OneOrMore(item, repeat)) + return result + + +class Start(DiagramItem): + def __init__(self): + DiagramItem.__init__(self, 'path') + self.width = 20 + self.up = 10 + self.down = 10 + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "start" + + def format(self, x, y, _width): + self.attrs['d'] = 'M {0} {1} v 20 m 10 -20 v 20 m -10 -10 h 20.5'.format(x, y - 10) + return self + + +class End(DiagramItem): + def __init__(self): + DiagramItem.__init__(self, 'path') + self.width = 20 + self.up = 10 + self.down = 10 + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "end" + + def format(self, x, y, _width): + self.attrs['d'] = 'M {0} {1} h 20 m -10 -10 v 20 m 10 -20 v 20'.format(x, y) + return self + + +class Terminal(DiagramItem): + def __init__(self, text): + DiagramItem.__init__(self, 'g', {'class': 'terminal'}) + self.text = text + self.width = len(text) * CHARACTER_ADVANCE + 20 + self.up = 11 + self.down = 11 + self.needsSpace = True + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "terminal" + + def format(self, x, y, width): + leftGap, rightGap = determineGaps(width, self.width) + + # Hook up the two sides if self is narrower than its stated width. + Path(x, y).h(leftGap).addTo(self) + Path(x + leftGap + self.width, y).h(rightGap).addTo(self) + + DiagramItem('rect', {'x': x + leftGap, 'y': y - 11, 'width': self.width, + 'height': self.up + self.down, 'rx': 10, 'ry': 10}).addTo(self) + DiagramItem('text', {'x': x + width / 2, 'y': y + 4}, self.text).addTo(self) + return self + + +class NonTerminal(DiagramItem): + def __init__(self, text): + DiagramItem.__init__(self, 'g', {'class': 'non-terminal'}) + self.text = text + self.width = len(text) * CHARACTER_ADVANCE + 20 + self.up = 11 + self.down = 11 + self.needsSpace = True + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "non-terminal" + + def format(self, x, y, width): + leftGap, rightGap = determineGaps(width, self.width) + + # Hook up the two sides if self is narrower than its stated width. + Path(x, y).h(leftGap).addTo(self) + Path(x + leftGap + self.width, y).h(rightGap).addTo(self) + + DiagramItem('rect', {'x': x + leftGap, 'y': y - 11, 'width': self.width, + 'height': self.up + self.down}).addTo(self) + DiagramItem('text', {'x': x + width / 2, 'y': y + 4}, self.text).addTo(self) + return self + + +class Comment(DiagramItem): + def __init__(self, text): + DiagramItem.__init__(self, 'g') + self.text = text + self.width = len(text) * 7 + 10 + self.up = 11 + self.down = 11 + self.needsSpace = True + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "comment" + + def format(self, x, y, width): + leftGap, rightGap = determineGaps(width, self.width) + + # Hook up the two sides if self is narrower than its stated width. + Path(x, y).h(leftGap).addTo(self) + Path(x + leftGap + self.width, y).h(rightGap).addTo(self) + + DiagramItem('text', {'x': x + width / 2, 'y': y + 5, 'class': 'comment'}, self.text).addTo(self) + return self + + +class Skip(DiagramItem): + def __init__(self): + DiagramItem.__init__(self, 'g') + self.width = 0 + self.up = 0 + self.down = 0 + if DEBUG: + self.attrs['data-updown'] = "{0} {1}".format(self.up, self.down) + self.attrs['data-type'] = "skip" + + def format(self, x, y, width): + Path(x, y).right(width).addTo(self) + return self + + +if __name__ == '__main__': + def add(name, diagram): + sys.stdout.write('

{0}

\n'.format(e(name))) + diagram.writeSvg(sys.stdout.write) + sys.stdout.write('\n') + + import sys + sys.stdout.write("Test") + exec(open('css-example.py-js').read()) diff --git a/docs/source/syntax.rst b/docs/source/syntax.rst new file mode 100644 index 0000000..fa68622 --- /dev/null +++ b/docs/source/syntax.rst @@ -0,0 +1,240 @@ + +module +-------- + +.. image:: _static/rr_module.svg + + +imports +-------- + +.. image:: _static/rr_imports.svg + + +exports +-------- + +.. image:: _static/rr_exports.svg + + +block +-------- + +.. image:: _static/rr_block.svg + + +blockExpr +-------- + +.. image:: _static/rr_blockExpr.svg + + +if +-------- + +.. image:: _static/rr_if.svg + + +escape +-------- + +.. image:: _static/rr_escape.svg + + +for +-------- + +.. image:: _static/rr_for.svg + + +fn +-------- + +.. image:: _static/rr_fn.svg + + +switch +-------- + +.. image:: _static/rr_switch.svg + + +try +-------- + +.. image:: _static/rr_try.svg + + +while +-------- + +.. image:: _static/rr_while.svg + + +when +-------- + +.. image:: _static/rr_when.svg + + +bind +-------- + +.. image:: _static/rr_bind.svg + + +object +-------- + +.. image:: _static/rr_object.svg + + +def +-------- + +.. image:: _static/rr_def.svg + + +interface +-------- + +.. image:: _static/rr_interface.svg + + +meta +-------- + +.. image:: _static/rr_meta.svg + + +pass +-------- + +.. image:: _static/rr_pass.svg + + +guard +-------- + +.. image:: _static/rr_guard.svg + + +expr +-------- + +.. image:: _static/rr_expr.svg + + +assign +-------- + +.. image:: _static/rr_assign.svg + + +lval +-------- + +.. image:: _static/rr_lval.svg + + +infix +-------- + +.. image:: _static/rr_infix.svg + + +comp +-------- + +.. image:: _static/rr_comp.svg + + +order +-------- + +.. image:: _static/rr_order.svg + + +prefix +-------- + +.. image:: _static/rr_prefix.svg + + +call +-------- + +.. image:: _static/rr_call.svg + + +calls +-------- + +.. image:: _static/rr_calls.svg + + +getExpr +-------- + +.. image:: _static/rr_getExpr.svg + + +curry +-------- + +.. image:: _static/rr_curry.svg + + +prim +-------- + +.. image:: _static/rr_prim.svg + + +comprehension +-------- + +.. image:: _static/rr_comprehension.svg + + +iter +-------- + +.. image:: _static/rr_iter.svg + + +pattern +-------- + +.. image:: _static/rr_pattern.svg + + +namePattern +-------- + +.. image:: _static/rr_namePattern.svg + + +noun +-------- + +.. image:: _static/rr_noun.svg + + +quasiliteral +-------- + +.. image:: _static/rr_quasiliteral.svg + + +mapPatternItem +-------- + +.. image:: _static/rr_mapPatternItem.svg + + +mapItem +-------- + +.. image:: _static/rr_mapItem.svg + diff --git a/docs/source/syntax_diagrams.py b/docs/source/syntax_diagrams.py new file mode 100644 index 0000000..5dd12f4 --- /dev/null +++ b/docs/source/syntax_diagrams.py @@ -0,0 +1,402 @@ +'''syntax_diagrams.py -- generate railroad diagrams for Monte syntax +''' + +from railroad_diagrams import ( + Diagram, + NonTerminal, + Sequence, Choice, + Skip, Optional, ZeroOrMore, OneOrMore) + + +diagrams = [] + +def add(name, diagram): + diagrams.append((name, diagram)) + + +add('module', Diagram(Sequence( + Optional(Sequence("module" + , NonTerminal('imports') + , Optional(NonTerminal('exports')))) + , NonTerminal('block')))); + +add('imports', Diagram(ZeroOrMore(NonTerminal('pattern')))); +add('exports', Diagram(Sequence( + 'export', "(", ZeroOrMore(NonTerminal('noun')), ")"))); +add('block', Diagram(Sequence( + "{", + ZeroOrMore( + Choice( + 0, + NonTerminal('blockExpr'), + NonTerminal('expr')), + ";"), + "}" +))); + +add('blockExpr', Diagram(Choice( + 0 + , NonTerminal('if') + , NonTerminal('escape') + , NonTerminal('for') + , NonTerminal('fn') + , NonTerminal('switch') + , NonTerminal('try') + , NonTerminal('while') + , NonTerminal('when') + , NonTerminal('bind') + , NonTerminal('object') + , NonTerminal('def') + , NonTerminal('interface') + , NonTerminal('meta') + , NonTerminal('pass') +))); + +add('if', Diagram( + Sequence("if", "(", NonTerminal('expr'), ")", NonTerminal('block') + , Optional(Sequence("else", Choice(0 + , Sequence("if", NonTerminal('blockExpr@@')) + , NonTerminal('block'))))) +)); + +add('escape', Diagram( + Sequence("escape", NonTerminal('pattern'), NonTerminal('block'), + Optional(Sequence("catch", NonTerminal('pattern'), + NonTerminal('block')))) +)); + +add('for', Diagram( + Sequence("for", + NonTerminal('pattern'), + Optional(Sequence("=>", NonTerminal('pattern'))), + "in", NonTerminal('comp'), + NonTerminal('block'), + Optional(Sequence("catch", NonTerminal('pattern'), NonTerminal('block')))) +)); + +add('fn', Diagram( + Sequence("fn", ZeroOrMore(NonTerminal('pattern'), ','), NonTerminal('block')) +)); + +add('switch', Diagram( + Sequence("switch", "(", NonTerminal('expr'), ")", + "{", + OneOrMore(Sequence("match", NonTerminal('pattern'), + NonTerminal('block'))), "}") +)); + +add('try', Diagram( + Sequence("try", NonTerminal('block'), + ZeroOrMore(Sequence("catch", + NonTerminal('pattern'), NonTerminal('block'))), + Optional(Sequence("finally", NonTerminal('block')))) +)); + +add('while', Diagram( + Sequence("while", "(", NonTerminal('expr'), ")", NonTerminal('block'), + Optional(Sequence("catch", NonTerminal('pattern'), NonTerminal('block')))) +)); + +add('when', Diagram( + Sequence("when", + "(", OneOrMore(NonTerminal('expr'), ','), ")", + ZeroOrMore(Sequence("catch", + NonTerminal('pattern'), NonTerminal('block'))), + Optional(Sequence("finally", NonTerminal('block')))) +)); + +add('bind', Diagram( + Sequence("bind", + NonTerminal("noun"), + Optional(Sequence(":", NonTerminal('guard'))), "objectExpr@@") +)); + +add('object', Diagram( + Sequence("object", Choice(0, Sequence("bind", NonTerminal('noun')), + "_", + NonTerminal("noun")), + Optional(Sequence(":", NonTerminal('guard'))), "objectExpr@@") +)); + +add('def', Diagram( + Sequence("def", Choice(0, + Sequence(Choice(0, + Sequence("bind", NonTerminal("noun"), + Optional(Sequence(":", NonTerminal('guard')))), + NonTerminal("noun")), + Choice(0, "objectFunction@@", NonTerminal('assign'))), + NonTerminal('assign'))) +)); + +add('interface', Diagram( + Sequence("interface", + NonTerminal('namePattern'), + Optional(Sequence("guards", NonTerminal('pattern'))), + Optional(Sequence("extends", OneOrMore(NonTerminal('order'), ','))), + "implements_@@", "msgs@@") +)); + +add('meta', Diagram( + Sequence("meta", ".", Choice(0, + Sequence("context", "(", ")"), + Sequence("getState", "(", ")") + )) +)); + +add('pass', Diagram('pass')); + +add('guard', Diagram(Choice( + 0, Sequence('IDENTIFIER', + Optional(Sequence('[', + OneOrMore(NonTerminal('expr'), ','), + ']'))), + Sequence('(', NonTerminal('expr'), ')') +))); + + +add('expr', +Diagram(Choice(0, + Sequence( + Choice(0, "continue", "break", "return") + , Choice(0, + Sequence("(", ")"), + ";", + NonTerminal('blockExpr'))), + NonTerminal('assign')))); + +add('assign', Diagram(Choice( + 0, + Sequence('def', + NonTerminal('pattern'), + Optional(Sequence("exit", NonTerminal('order'))), + Optional(Sequence(":=", NonTerminal('assign')))), + Sequence(Choice(0, 'var', 'bind'), + NonTerminal('pattern'), + # XXX the next two seem to be optional in the code. + ":=", NonTerminal('assign')), + Sequence(NonTerminal('lval'), ":=", NonTerminal('assign')), + "@op=...XXX", + "VERB_ASSIGN XXX" +) +)); + +add('lval', Diagram(Choice( + 0 + , NonTerminal('noun') + , NonTerminal('getExpr') +))); + +add('infix', Diagram(Sequence( + NonTerminal('comp'), + Optional(Sequence(Choice(0, '||', '&&'), NonTerminal('infix')))))); + +add('comp', Diagram( + NonTerminal('order'), + Optional(Sequence(Choice( + 0, + "=~", + "!~", + "==", + "!=", + "&!", + "^", + "&", + "|" + ), NonTerminal('comp'))))); + +add('order', Diagram( + NonTerminal('prefix'), + Optional(Sequence(Choice( + 0, + "**", + "*", + "/", + "//", + "%", + "+", + "-", + "<<", + ">>", + "..", + "..!", + ">", + "<", + ">=", + "<=", + "<=>" + ), NonTerminal('order'))))); + +add('prefix', Diagram(Choice( + 0 + , Sequence('-', NonTerminal('prim')) + , Sequence(Choice(0, "~", "!"), NonTerminal('call')) + , Sequence('&', NonTerminal('noun')) + , Sequence('&&', NonTerminal('noun')) + , Sequence(NonTerminal('call'), + Optional(Sequence(":", NonTerminal('guard')))) +))); + +add('call', Diagram(Sequence( + NonTerminal('calls'), + Optional(Sequence(NonTerminal('curry'))) +))); + +add('calls', Diagram( + Choice( + 0 + , NonTerminal('prim') + , Sequence( + NonTerminal('calls'), + Optional( + Sequence(Choice(0, ".", "<-"), + Choice(0, ".String.", "IDENTIFIER"))), + Sequence("(", ZeroOrMore(NonTerminal('expr'), ','), ")")) + , NonTerminal('getExpr')) +)); + +add('getExpr', Diagram(Sequence( + NonTerminal('calls'), + Sequence("[", ZeroOrMore(NonTerminal('expr'), ','), "]") +))); + +add('curry', Diagram(Sequence( + Choice(0, '.', '<-'), + Choice(0, ".String.", "IDENTIFIER") +))); + +add('prim', Diagram(Choice( + 0 + ,".String.", ".int.", ".float64.", ".char." + , NonTerminal('quasiliteral') + , "IDENTIFIER" + , Sequence("::", ".String.") + , Sequence("(", NonTerminal('expr'), ")") + , Sequence("{", ZeroOrMore(NonTerminal('expr'), ';'), "}") + , Sequence("[", Choice( + 0 + , Skip() + , OneOrMore(NonTerminal('expr'), ',') + , OneOrMore(Sequence(NonTerminal('expr'), + "=>", NonTerminal('expr')), + ',') + , Sequence("for", NonTerminal('comprehension'))) + , "]") +))); + +add('comprehension', Diagram(Choice( + 0 + , Sequence(NonTerminal('pattern'), + "in", NonTerminal('iter'), + NonTerminal('expr')) + , Sequence(NonTerminal('pattern'), "=>", NonTerminal('pattern'), + "in", NonTerminal('iter'), + NonTerminal('expr'), "=>", NonTerminal('expr')) +))); + +add('iter', Diagram(Sequence( + NonTerminal('order'), + Optional(Sequence("if", NonTerminal('comp'))) +))); + +add('pattern', + Diagram(Sequence( + Choice(0, + NonTerminal('namePattern') + , NonTerminal('quasiLiteral') + , Sequence(Choice(0, "==", "!="), NonTerminal('prim')) + , Sequence("_", ":", NonTerminal('guard')) + , Sequence("via", "(", NonTerminal('expr'), ')', + NonTerminal('pattern')) + , Sequence("[", + OneOrMore(NonTerminal('mapPatternItem'), ','), ']')) + , Optional(Sequence("?", "(", NonTerminal('expr'), ")"))))) + +add('namePattern', Diagram( + Choice(0, + Sequence( + Choice(0, + Sequence("::", ".String."), + "IDENTIFIER"), + Optional(Sequence(':', NonTerminal('guard')))), + Sequence("var", NonTerminal('noun'), + Optional(Sequence(":", NonTerminal('guard')))), + Sequence("&", NonTerminal('noun'), + Optional(Sequence(":", NonTerminal('guard')))), + Sequence("&&", NonTerminal('noun')), + Sequence("bind", NonTerminal('noun'), + Optional(Sequence(":", NonTerminal('guard')))), + ))) + +add('noun', Diagram(Choice( + 0, 'IDENTIFIER', + Sequence('::', '.String.')))); + +add('quasiliteral', Diagram(Sequence( + Optional("IDENTIFIER"), + '`', + ZeroOrMore( + Choice(0, '...', + '$IDENT', + Sequence('${', NonTerminal('expr'), '}'), + '@IDENT', + Sequence('@{', NonTerminal('expr'), '}') + + )) + , '`'))); + +add('mapPatternItem', + Diagram(Sequence( + Choice(0, + Sequence("=>", NonTerminal('namePattern')), + Sequence(Choice(0, + Sequence("(", NonTerminal('expr'), ")"), + ".String.", ".int.", ".float64.", ".char."), + "=>", NonTerminal('pattern'))), + Optional(Sequence(":=", NonTerminal('order')))))) + +add('mapItem', + Diagram(Choice( + 0, + Sequence("=>", Choice( + 0, + Sequence("&", NonTerminal('noun')), + Sequence("&&", NonTerminal('noun')), + NonTerminal('noun'))), + Sequence(NonTerminal('expr'), "=>", NonTerminal('expr'))))) + + +def figFile(name, d, + static='_static/'): + fn = static + ('rr_%s.svg' % name) + with open(fn, 'wb') as out: + d.writeSvg(out.write) + return fn + + +def toReST(rst, ds): + for name, diagram in ds: + fn = figFile(name, diagram) + rst.write(''' +%(name)s +-------- + +.. image:: %(fn)s + +''' + % dict(name=name, fn=fn)) + +def toHTML(out, ds): + from railroad_diagrams import STYLE + from railroad_diagrams import e as esc + out.write( + "Test" % STYLE) + for name, diag in ds: + out.write('

{0}

\n'.format(esc(name))) + diag.writeSvg(out.write) + + +if __name__ == '__main__': + from sys import stdout + #toHTML(stdout, diagrams) + toReST(stdout, diagrams) + diff --git a/docs/source/tubes.rst b/docs/source/tubes.rst new file mode 100644 index 0000000..2fd0319 --- /dev/null +++ b/docs/source/tubes.rst @@ -0,0 +1,58 @@ +===== +Tubes +===== + +Tutorial +======== + +Monte provides a unified paradigm for handling streams of structured data. The +paradigm is known as *tubes*. + +Tubes come in two flavors: *founts* and *drains*. A fount is an object which +can provide data to another tube. A drain is an object which can receive data +from another tube. A tube can be just a fount, just a drain, or both a fount +and a drain. + +This is all pretty abstract. Let's roll up our sleeves and take a look at how +to use some tubes:: + + def echo(fount, drain): + fount.flowTo(drain) + +This code instructs ``fount`` to provide data to ``drain``. This providing of +data will happen whenever ``fount`` wants, until either ``fount`` or ``drain`` +indicate that flow should cease. While this example might seem trivial, it's +sufficient to use as e.g. a TCP echo server. + +Sometimes founts receive their data between turns, and schedule special turns +to send data to drains. Other times founts are eager, and try to feed a drain +immediately during ``flowTo``. If you want to forcibly delay that eagerness +until another turn, just use an eventual send:: + + def echo(fount, drain): + fount<-flowTo(drain) + +If a drain is also a fount, then ``flowTo`` will return a new fount which can +be flowed to another drain. This is called *tube composition* or *tube fusion* +and it is an important concept in tube handling. + +Pumps +----- + +Sometimes an operation on streaming chunks of data only cares about the data +and not about the streaming or chunking. Such an operation can be encapsulated +in a *pump*, which is like a tube but with no flow control. A pump takes one +item at a time and should return zero or more items. + +Pumps are mostly useful because they can be wrapped into tubes, which can then +be composed with other tubes:: + + def [=> makeMapPump] | _ := import("lib/tubes/mapPump") + def [=> makePumpTube] | _ := import("lib/tubes/pumpTube") + + def negate(fount, drain): + def tube := makePumpTube(makeMapPump(fn x {-x})) + fount<-flowTo(tube)<-flowTo(drain) + +This pump uses a mapping function to negate every element that flows through +it, without any concern over flow control. diff --git a/docs/source/wizard.rst b/docs/source/wizard.rst new file mode 100644 index 0000000..069ea03 --- /dev/null +++ b/docs/source/wizard.rst @@ -0,0 +1,104 @@ +=================== +Monte (for Wizards) +=================== + +Why Monte? +========== + +Every new language should solve a problem. What problem does Monte solve? + +E and Python +------------ + +Monte is based on E, a language intended for secure distributed computation. +Monte also incorporates many syntactic ideas from Python, a language designed +for readability and simplicity. The design of Monte incorporates both of these +languages' philosophies, with the goal of supporting environments that are +orders of magnitude more complex than existing systems. + +For a history of E's ideas, see http://www.erights.org/history/index.html + +Networking +---------- + +Unlike many other contemporary programming languages, Monte does not need an +additional networking library to provide solid primitive and high-level +networking operations. This is because Monte was designed to handle networking +as easily as any other kind of input or output. + +Distributed Systems +------------------- + +Monte comes with builtin explicit parallelism suitable for scaling to +arbitrary numbers of processes or machines, and a well-defined concurrency +system that simplifies and streamlines the task of writing event-driven code. + +Monte has one parallel primitive: the **vat**. Vats are objects which +encapsulate an entire Monte runtime and isolate other objects from objects in +other vats. Vats are able to communicate across a variety of gulfs, from +inter-process threads to separate machines on a network. + +Monte also has one concurrent operation. Monte permits messages to be passed +as **eventual sends**. An eventually-sent message will be passed to the target +object at a later time, generating a **promise** which can have more messages +sent to it. Unlike similar mechanisms in Twisted, Node.js, etc., Monte builds +promises and eventual sending directly into the language and runtime, removing +the need for extraneous libraries. + +The Semantics +============= + +Architect's View +---------------- + +Monte programs are a collection of vats, running in one or more processes on +one or more hosts. Vats contain three elements: a stack, a queue, and a heap. +All three contain Monte objects. The queue contains messages to objects in the +heap; messages consist of a verb and may contain objects passed as arguments. +Execution of code in a vat progresses by **turns**; each turn is started by +delivering the next message in the queue to its recipient, which can result in +activation records being placed on the stack and further messages going into +the queue. The turn progresses until the stack is empty. A new turn begins +with the next message on the queue. + +Hacker's View +------------- + +Monte is a pure object-based language in the Smalltalk tradition. All values +are objects and all computation is done by sending messages to objects. +Unlike Smalltalk, Python, Java, etc., objects are not instances of classes. +Unlike Self or JavaScript, objects are not derived from prototypes. Monte +objects are defined via object literal syntax as closures over variable +bindings. Specifically unlike Python, objects don't have attributes, merely +responses to messages. + +Compiler's View +--------------- + +Monte provides both immediate, synchronous calls to methods and eventual, +asynchronous sends to methods. The former provides the usual +subroutine-invocation semantics, whereas the latter enqueues a message to be +delivered in a subsequent vat turn. Monte names for objects are encapsulated +in bindings. + +A Monte name binding consists of a slot and a slot guard. Assignment to names +and accessing names invokes methods on that name's slot. This can be used to +share state between objects, and perform actions on reads from/writes to +names. Slot guards for bindings closed over an object are revealed to +auditors, during auditing. + +Name bindings may have guards, which are given objects and may either accept, +coerce, or reject them. Coercion results in the value produced by the guard +being different from the specimen given. The specimen may be asked to conform +itself to the guard as part of coercion. + +Interface expressions are a tool for creating trivial guards; an object +expression may declare that it implements an interface, and the interface +object may be used as a guard to accept only objects that declare that +interface. + +Network's View +-------------- + +This is a big topic and you should read +http://www.erights.org/elib/concurrency/refmech.html for now. diff --git a/monte/__init__.py b/monte/__init__.py index e69de29..54cf274 100644 --- a/monte/__init__.py +++ b/monte/__init__.py @@ -0,0 +1,15 @@ +import time, collections, atexit, pprint +PROFILE = collections.Counter() + +class TimeRecorder(object): + def __init__(self, key): + self.key = key + def __enter__(self): + self.start = time.time() + def __exit__(self, *a): + PROFILE[self.key] += (time.time() - self.start) + +def print_time(): + pprint.pprint(dict(PROFILE)) + +#atexit.register(print_time) diff --git a/monte/ast.py b/monte/ast.py index 0096131..cae3e40 100644 --- a/monte/ast.py +++ b/monte/ast.py @@ -4,37 +4,35 @@ from terml.nodes import Tag, Term +shiftTable = ''.join(chr((x + 32) % 256) for x in range(256)) +unshiftTable = ''.join(chr((x - 32) % 256) for x in range(256)) + + def asciiShift(bs): - return ''.join(chr((ord(b) + 32) % 256) for b in bs) + return bs.translate(shiftTable) -def asciiUnshift(bs): - return ''.join(chr((ord(b) - 32) % 256) for b in bs) -LONG_SHIFT = 15 -LONG_BASE = 1 << LONG_SHIFT -LONG_MASK = LONG_BASE - 1 +def asciiUnshift(bs): + return bs.translate(unshiftTable) kernelNodeInfo = [ ('null', 0), + ('true', 0), + ('false', 0), ('.String.', None), ('.float64.', None), ('.char.', None), - # different tags for short ints... ('.int.', None), - # ... and long ints - ('.int.', None), - # this one for small tuples... - ('.tuple.', None), - # ... this one for large ('.tuple.', None), + ('.bag.', None), + ('.attr.', 2), ('LiteralExpr', 1), ('NounExpr', 1), ('BindingExpr', 1), ('SeqExpr', 1), ('MethodCallExpr', 3), ('Def', 3), - ('Escape', 3), - ('Catch', 2), + ('Escape', 4), ('Object', 4), ('Script', 3), ('Method', 5), @@ -55,17 +53,55 @@ def asciiUnshift(bs): ('Module', 3) ] -SHORT_INT, LONG_INT = (4, 5) # indices of the two '.int.'s above -BIG_TUPLE, SMALL_TUPLE = (6, 7) # indices of the two '.int.'s above - nodeLookup = dict((v[0], k) for k, v in enumerate(kernelNodeInfo)) arities = dict(kernelNodeInfo) tags = dict((i, Tag(k)) for (i, (k, a)) in enumerate(kernelNodeInfo)) +def zze(val): + if val < 0: + return ((val * 2) ^ -1) | 1 + else: + return val * 2 + + +def zzd(val): + if val & 1: + return (val / 2) ^ -1 + return val / 2 + + +def dumpVarint(value): + if value == 0: + target = "\x00" + else: + target = [] + while value > 0: + chunk = value & 0x7f + value >>= 7 + if value > 0: + target.append(chr(chunk | 0x80)) + else: + target.append(chr(chunk)) + return asciiShift(''.join(target)) + + +def loadVarint(data, i): + val = 0 + pos = 0 + while i < len(data): + byte = (ord(data[i]) - 32) % 256 + val |= (byte & 0x7f) << pos + pos += 7 + i += 1 + if not (byte & 0x80): + return val, i + raise ValueError("Input truncated") + def load(data): dataStack = [] opStack = [] + i = loadTerm(data, 0, dataStack, opStack) while opStack: i = opStack.pop()(data, i, dataStack, opStack) @@ -93,22 +129,10 @@ def loadTerm(data, i, dataStack, opStack): arity = arities[tag.name] literalVal = None if tag.name == '.int.': - if kind == SHORT_INT: - literalVal = readInt32(data, i) - i += 4 - else: - literalVal = 0 - siz = readInt32(data, i) - i += 4 - chunks = [] - for j in range(siz): - chunk = struct.unpack('!h', asciiUnshift(data[i:i + 2]))[0] - chunks.append(chunk) - i += 2 - literalVal |= (chunk << LONG_SHIFT * j) + val, i = loadVarint(data, i) + literalVal = zzd(val) elif tag.name == '.String.': - siz = readInt32(data, i) - i += 4 + siz, i = loadVarint(data, i) literalVal = data[i:i + siz].decode('utf-8') i += siz elif tag.name == '.float64.': @@ -122,12 +146,7 @@ def loadTerm(data, i, dataStack, opStack): literalVal = de.decode(data[i]) i += 1 elif tag.name == '.tuple.': - if kind == BIG_TUPLE: - arity = readInt32(data, i) - i += 4 - else: - arity = ord(asciiUnshift(data[i])) - i += 1 + arity, i = loadVarint(data, i) if arity is None: dataStack.append(Term(tag, literalVal, (), None)) else: @@ -136,10 +155,6 @@ def loadTerm(data, i, dataStack, opStack): return i -def readInt32(data, i): - return struct.unpack('!i', asciiUnshift(data[i:i + 4]))[0] - - def dump(ast): out = StringIO() dumpTerm(ast, out) @@ -149,53 +164,23 @@ def dump(ast): def dumpTerm(term, out): o = term.data name = term.tag.name + out.write(asciiShift(chr(nodeLookup[name]))) if name == '.int.': - if abs(o) < 2**31: - out.write(asciiShift(chr(SHORT_INT))) - writeInt32(o, out) - else: - out.write(asciiShift(chr(LONG_INT))) - chunks = [] - done = False - while True: - if abs(o) > LONG_BASE: - c = o & LONG_MASK - else: - c = o - chunks.append(struct.pack('!h', c)) - o >>= LONG_SHIFT - if o == 0: - break - if o == -1: - if done: - break - done = True - writeInt32(len(chunks), out) - out.write(asciiShift(''.join(chunks))) - return + out.write(dumpVarint(zze(o))) elif name == '.tuple.': - if len(term.args) > 255: - out.write(asciiShift(chr(BIG_TUPLE))) - out.write(asciiShift(struct.pack('!i', len(t.args)))) - else: - out.write(asciiShift(chr(SMALL_TUPLE))) - out.write(asciiShift(chr(len(term.args)))) + out.write(dumpVarint(len(term.args))) for t in term.args: dumpTerm(t, out) - return - out.write(asciiShift(chr(nodeLookup[name]))) - if name == '.String.': - bs = o.encode('utf-8') - writeInt32(len(bs), out) + elif name == '.String.': + bs = o.encode('utf-8') if isinstance(o, unicode) else o + out.write(dumpVarint(len(bs))) out.write(bs) elif name == '.float64.': out.write(asciiShift(struct.pack('!d', o))) elif name == '.char.': - out.write(o.encode('utf-8')) + out.write(o.encode('utf-8') if isinstance(o, unicode) else o) else: + assert name in arities + assert len(term.args) == arities[name], "Bad arity of term: %r" % term for t in term.args: dumpTerm(t, out) - - -def writeInt32(i, out): - out.write(asciiShift(struct.pack('!i', i))) diff --git a/monte/compiler.py b/monte/compiler.py index 89d4c82..cc976a9 100644 --- a/monte/compiler.py +++ b/monte/compiler.py @@ -2,7 +2,7 @@ from keyword import iskeyword from StringIO import StringIO -from monte import ast +from monte import ast, TimeRecorder from monte.parser import parse from monte.expander import expand, scope @@ -98,7 +98,8 @@ def getBinding(self, n, default=_absent): return self.bindings[n] else: if default is _absent: - raise CompileError("No global named " + repr(n)) + raise CompileError("No global named %r; possibilities are %s" + % (n, self.bindings.keys())) else: return default @@ -192,12 +193,13 @@ def makeInner(self): return ScopeLayout(self, self.frame, self.outer) class CustomBinding(object): - def __init__(self, node, pyname, kind, descriptorName): + def __init__(self, node, pyname, kind, descriptorName, frameName=None): self.node = node self.pyname = pyname self.name = node.args[0].args[0].data self.kind = kind self.descriptorName = descriptorName + self.frameName = frameName def getDescriptorName(self): return self.descriptorName @@ -218,7 +220,9 @@ def getSlotPairName(self): return mangleIdent(self.name) + "_slotPair" def getBindingPair(self): - return (self.pyname + '.slot', self.getBindingGuardExpr()) + if self.kind == FRAME: + return '%s._m_slots["%s"]' % (self.frameName, self.name) + return "(%s, %s)" % (self.pyname + '.slot', self.getBindingGuardExpr()) def bindInFrame(self, frame): if self.name not in frame.verbs: @@ -229,7 +233,8 @@ def bindInFrame(self, frame): pyname = frame.selfName + '.' + pyname.rpartition('.')[2] else: pyname = frame.selfName + '.' + pyname - return CustomBinding(self.node, pyname, FRAME, self.descriptorName) + return CustomBinding(self.node, pyname, FRAME, self.descriptorName, + frame.selfName) class Binding(object): @@ -264,7 +269,7 @@ def getBindingExpr(self): bn = "_monte.FinalSlot(%s)" % (self.pyname,) else: bn = self.pyname - return "_monte.Binding(%s, %s)" % (self.bindingGuardExpr, bn) + return "_monte.Binding(%s, %s)" % (bn, self.bindingGuardExpr) def getBindingGuardExpr(self): return self.bindingGuardExpr @@ -276,7 +281,7 @@ def getBindingPair(self): self.bindingGuardExpr) else: pair = (self.slotname, self.bindingGuardExpr) - return pair + return "(%s, %s)" % pair def getSlotPairName(self): return mangleIdent(self.name) + "_slotPair" @@ -437,10 +442,7 @@ def generate_MethodCallExpr(self, out, ctx, node): rcvr, verb, args = node.args rcvrName = self._generate(out, ctx.with_(mode=VALUE), rcvr) argNames = [self._generate(out, ctx.with_(mode=VALUE), arg) for arg in args.args] - if verb.data == "run": - return "%s(%s)" % (rcvrName, ', '.join(argNames)) - else: - return "%s.%s(%s)" % (rcvrName, mangleIdent(verb.data), ', '.join(argNames)) + return "%s.%s(%s)" % (rcvrName, mangleIdent(verb.data), ', '.join(argNames)) def generate_Def(self, out, ctx, node): patt, ej, expr = node.args @@ -454,27 +456,28 @@ def generate_Def(self, out, ctx, node): return n def generate_Escape(self, out, ctx, node): - patt, body, catcher = node.args + patt, body, catchpatt, catchbody = node.args bodyScope = scope(body) pattScope = scope(patt) # only generate ejector code if it's mentioned in the body if bodyScope.namesUsed() & pattScope.outNames(): name = next(iter(pattScope.outNames())) - ej = self._generatePattern(out, ctx, None, + newctx = ctx.with_(layout=ctx.layout.makeInner()) + ej = self._generatePattern(out, newctx, None, '_monte.ejector("%s")' % (name,), patt) out.writeln("try:") sub = out.indent() ejTemp = ctx.layout.gensym(name) escapeTemp = ctx.layout.gensym("escape") - newctx = ctx.with_(layout=ctx.layout.makeInner()) val = self._generate(sub, newctx, body) sub.writeln("%s = %s" % (escapeTemp, val)) out.writeln("except %s._m_type, %s:" % (ej, ejTemp)) - if catcher.tag.name != 'null': - self._generatePattern(sub, ctx, None, - ejTemp + '.args[0]', catcher.args[0]) - val = self._generate(sub, ctx, catcher.args[1]) + if catchpatt.tag.name != 'null': + catchctx = ctx.with_(layout=ctx.layout.makeInner()) + self._generatePattern(sub, catchctx, None, + ejTemp + '.args[0]', catchpatt) + val = self._generate(sub, catchctx, catchbody) sub.writeln("%s = %s" % (escapeTemp, val)) else: sub.writeln("%s = %s.args[0]" % (escapeTemp, ejTemp)) @@ -658,8 +661,7 @@ def generate_Object(self, out, ctx, node): def _collectSlots(self, fields): makeSlots = [] for f in sorted(fields, key=lambda f: f.name): - pair = f.getBindingPair() - makeSlots.append("(%s, %s)" % pair) + makeSlots.append(f.getBindingPair()) return makeSlots def generate_Assign(self, out, ctx, node): @@ -857,8 +859,12 @@ def pattern_BindingPattern(self, out, ctx, ej, val, node): out.writeln("%s = %s" % (pyname, val)) return pyname + def ecompile(source, scope, origin="__main"): - ast = expand(parse(source)) + with TimeRecorder("parse"): + p = parse(source) + with TimeRecorder("expand"): + ast = expand(p) f = StringIO() PythonWriter(ast, origin, scope).output(TextWriter(f)) return f.getvalue().strip() diff --git a/monte/expander.py b/monte/expander.py index 392a11f..2426e09 100644 --- a/monte/expander.py +++ b/monte/expander.py @@ -137,7 +137,8 @@ def getExports(scope, used): def union(scopes, result=StaticScope()): for sc in scopes: - result = result.add(sc) + if sc and not isinstance(sc, list): + result = result.add(sc) return result def foldr(f, a, bs): @@ -243,14 +244,14 @@ def expandDef(self, patt, optEj, rval, nouns): Script(@extends @methodScopes @matcherScopes) -> union(methodScopes + matcherScopes) Object(@doco @nameScope @auditorScope @scriptScope) -> nameScope.add(union(auditorScope).add(scriptScope)) -Method(@doco @verb @paramsScope @guardScope @blockScope) -> union(paramsScope + [guardScope, blockScope.hide()]).hide() +Method(@doco @verb @paramsScope @guardScope @blockScope) -> union(paramsScope + [(guardScope or StaticScope()), blockScope.hide()]).hide() Matcher(@patternScope @blockScope) -> patternScope.add(blockScope).hide() If(@testScope @consqScope @altScope) -> testScope.add(consqScope).hide().add(altScope).hide() KernelTry(@tryScope @patternScope @catchScope) -> tryScope.hide().add(patternScope.add(catchScope)).hide() Finally(@tryScope @finallyScope) -> tryScope.hide().add(finallyScope).hide() -Escape(@ejScope @bodyScope Catch(@argScope @catcherScope)) -> ejScope.add(bodyScope).hide().add(argScope.add(catcherScope)).hide() -Escape(@ejScope @bodyScope null) -> ejScope.add(bodyScope).hide() +Escape(@ejScope @bodyScope null null) -> ejScope.add(bodyScope).hide() +Escape(@ejScope @bodyScope @argScope @catcherScope) -> ejScope.add(bodyScope).hide().add(argScope.add(catcherScope)).hide() MatchBind(@specimen @pattern) -> specimen.add(pattern) @@ -285,20 +286,26 @@ def putVerb(verb): elif verb.startswith("__get"): return "__set"+verb[5:] -def buildQuasi(pairs): +def buildQuasi(name, pairs): textParts = [] exprParts = [] patternParts = [] for text, expr, patt in pairs: if expr: - textParts.append("${%s}" % (len(exprParts),)) + textParts.append(t.MethodCallExpr( + t.NounExpr(name + "__quasiParser"), + "valueHole", + [t.LiteralExpr(len(exprParts))])) exprParts.append(expr) elif patt: - textParts.append("@{%s}" % (len(patternParts),)) + textParts.append(t.MethodCallExpr( + t.NounExpr(name + "__quasiParser"), + "patternHole", + [t.LiteralExpr(len(patternParts))])) patternParts.append(patt) else: - textParts.append(text.data) - return t.LiteralExpr(''.join(textParts)), exprParts, patternParts + textParts.append(t.LiteralExpr(text.data)) + return textParts, exprParts, patternParts #implicit rules: # data transforms to itself @@ -334,11 +341,11 @@ def buildQuasi(pairs): ListExpr(@items) -> mcall("__makeList", "run", *items) -QuasiExpr(null [qexpr:qs]) -> t.MethodCallExpr(mcall("simple__quasiParser", "valueMaker", qs[0]), "substitute", [mcall("__makeList", "run", *qs[1])]) -QuasiExpr(@name [qexpr:qs]) -> t.MethodCallExpr(mcall(name + "__quasiParser", "valueMaker", qs[0]), "substitute", [mcall("__makeList", "run", *qs[1])]) +QuasiExpr(null [qexpr("simple"):qs]) -> t.MethodCallExpr(mcall("simple__quasiParser", "valueMaker", mcall("__makeList", "run", *qs[0])), "substitute", [mcall("__makeList", "run", *qs[1])]) +QuasiExpr(@name [qexpr(name):qs]) -> t.MethodCallExpr(mcall(name + "__quasiParser", "valueMaker", mcall("__makeList", "run", *qs[0])), "substitute", [mcall("__makeList", "run", *qs[1])]) -qexpr = (qtext | qehole)*:pairs -> buildQuasi(pairs) -qpatt = (qtext | qehole | qphole)*:pairs -> buildQuasi(pairs) +qexpr :name = (qtext | qehole)*:pairs -> buildQuasi(name, pairs) +qpatt :name = (qtext | qehole | qphole)*:pairs -> buildQuasi(name, pairs) qtext = QuasiText(:text) -> (text, None, None) qehole = QuasiExprHole(@expr) -> (None, expr, None) qphole = QuasiPatternHole(@patt) -> (None, None, patt) @@ -456,8 +463,8 @@ def buildQuasi(pairs): SuchThatPattern(@pattern @expr) -> t.ViaPattern(t.NounExpr("__suchThat"), t.ListPattern([pattern, t.ViaPattern(mcall("__suchThat", "run", expr), t.IgnorePattern(None))], None)) -QuasiPattern(null [qpatt:qs]) -> t.ViaPattern(mcall("__quasiMatcher", "run", mcall("simple__quasiParser", "matchMaker", qs[0]), mcall("__makeList", "run", *qs[1])), t.ListPattern(qs[2], None)) -QuasiPattern(@name [qpatt:qs]) -> t.ViaPattern(mcall("__quasiMatcher", "run", mcall(name + "__quasiParser", "matchMaker", qs[0]), mcall("__makeList", "run", *qs[1])), t.ListPattern(qs[2], None)) +QuasiPattern(null [qpatt("simple"):qs]) -> t.ViaPattern(mcall("__quasiMatcher", "run", mcall("simple__quasiParser", "matchMaker", mcall("__makeList", "run", *qs[0])), mcall("__makeList", "run", *qs[1])), t.ListPattern(qs[2], None)) +QuasiPattern(@name [qpatt(name):qs]) -> t.ViaPattern(mcall("__quasiMatcher", "run", mcall(name + "__quasiParser", "matchMaker", mcall("__makeList", "run", *qs[0])), mcall("__makeList", "run", *qs[1])), t.ListPattern(qs[2], None)) Interface(@doco nameAndString:nameAnd @guard @extends @implements InterfaceFunction(:params :resultGuard)) @@ -473,9 +480,9 @@ def buildQuasi(pairs): -> t.HideExpr(mcall("__makeMessageDesc", "run", doco and t.LiteralExpr(doco), t.LiteralExpr(verb), mcall("__makeList", "run", *paramDescs), - guard or t.NounExpr("void"))) + guard or t.NounExpr("Void"))) -ParamDesc(name:name @guard) -> mcall("__makeParamDesc", "run", t.LiteralExpr(name), guard or t.NounExpr("any")) +ParamDesc(name:name @guard) -> mcall("__makeParamDesc", "run", t.LiteralExpr(name), guard or t.NounExpr("Any")) Lambda(@doco @patterns @block) -> t.Object(doco, t.IgnorePattern(None), [None], @@ -485,14 +492,14 @@ def buildQuasi(pairs): [])) Object(:doco BindPattern(:name :guard):bp :auditors :script):o transform(bp):exName - transform(t.Object(doco, t.FinalPattern(t.NounExpr(name), None), auditors, script)):exObj + transform(t.Object(doco, t.FinalPattern(name, None), auditors, script)):exObj -> t.Def(exName, None, t.HideExpr(exObj)) Object(@doco @name @auditors Function(@params @guard @block)) -> t.Object(doco, name, auditors, t.Script(None, [t.Method(doco, "run", params, guard, t.Escape(t.FinalPattern(t.NounExpr("__return"), None), - t.SeqExpr([block, t.NounExpr("null")]), None))], + t.SeqExpr([block, t.NounExpr("null")]), None, None))], [])) Object(@doco @name @auditors Script(null @methods @matchers)) -> t.Object(doco, name, auditors, t.Script(None, methods, matchers)) @@ -521,7 +528,7 @@ def buildQuasi(pairs): mcall("M", "callWithPair", t.NounExpr("super"), p))])) ] + maybeSlot)) To(:doco @verb @params @guard @block) -> t.Method(doco, verb, params, guard, t.Escape(t.FinalPattern(t.NounExpr("__return"), None), - t.SeqExpr([block, t.NounExpr("null")]), None)) + t.SeqExpr([block, t.NounExpr("null")]), None, None)) For(:key :value @coll @block @catcher) -> expandFor(self, key, value, coll, block, catcher) @@ -544,12 +551,12 @@ def buildQuasi(pairs): While(@test @block @catcher) = expandWhile(test block catcher) -expandWhile :test :block :catcher -> t.Escape(t.FinalPattern(t.NounExpr("__break"), None), mcall("__loop", "run", mcall("__iterWhile", "run", t.Object(None, t.IgnorePattern(None), [None], t.Script(None, [t.Method(None, "run", [], None, test)], []))), t.Object("While loop body", t.IgnorePattern(None), [None], t.Script(None, [t.Method(None, "run", [t.IgnorePattern(None), t.IgnorePattern(None)], t.NounExpr("boolean"), t.SeqExpr([t.Escape(t.FinalPattern(t.NounExpr("__continue"), None), block, None), t.NounExpr("true")]))], []))), catcher) +expandWhile :test :block :catcher -> t.Escape(t.FinalPattern(t.NounExpr("__break"), None), mcall("__loop", "run", mcall("__iterWhile", "run", t.Object(None, t.IgnorePattern(None), [None], t.Script(None, [t.Method(None, "run", [], None, test)], []))), t.Object("While loop body", t.IgnorePattern(None), [None], t.Script(None, [t.Method(None, "run", [t.IgnorePattern(None), t.IgnorePattern(None)], t.NounExpr("Bool"), t.SeqExpr([t.Escape(t.FinalPattern(t.NounExpr("__continue"), None), block, None, None), t.NounExpr("true")]))], []))), *catcher) When([@arg] @block :catchers @finallyblock) expandWhen(arg block catchers finallyblock) When(@args @block :catchers :finallyblock) expandWhen(mcall("promiseAllFulfilled", "run", t.MethodCallExpr(t.NounExpr("__makeList"), "run", args)) block catchers finallyblock) -expandWhen :arg :block [(Catch(@p @b) -> (p, b))*:catchers] :finallyblock !(self.mktemp("resolution")):resolution kerneltry(expandTryCatch(t.If(mcall("Ref", "isBroken", resolution), mcall("Ref", "broken", mcall("Ref", "optProblem", resolution)), block), catchers) finallyblock):body -> t.HideExpr(mcall("Ref", "whenResolved", arg, t.Object("when-catch 'done' function", t.IgnorePattern(None), [None], t.Script(None, [t.Method(None, "run", [t.FinalPattern(resolution, None)], None, body)], [])))) +expandWhen :arg :block [([@p @b] -> (p, b))*:catchers] :finallyblock !(self.mktemp("resolution")):resolution kerneltry(expandTryCatch(t.If(mcall("Ref", "isBroken", resolution), mcall("Ref", "broken", mcall("Ref", "optProblem", resolution)), block), catchers) finallyblock):body -> t.HideExpr(mcall("Ref", "whenResolved", arg, t.Object("when-catch 'done' function", t.IgnorePattern(None), [None], t.Script(None, [t.Method(None, "run", [t.FinalPattern(resolution, None)], None, body)], [])))) """ @@ -579,8 +586,8 @@ def matchExpr(self, matchers, sp, failures): t.SeqExpr([ t.Def(m.args[0], ej, sp), m.args[1]]), - t.Catch(t.FinalPattern(fail, None), - block)) + t.FinalPattern(fail, None), + block) return block def expandTryCatch(tryblock, catchers): @@ -668,6 +675,7 @@ def expandFor(self, key, value, coll, block, catcher): t.Def(value, None, vTemp), block, t.NounExpr("null")]), + None, None)]))], [])) return t.Escape( @@ -682,7 +690,7 @@ def expandFor(self, key, value, coll, block, catcher): [coll, obj]), t.Assign(fTemp, t.NounExpr("false"))), t.NounExpr("null")]), - catcher) + *catcher) def expandComprehension(self, key, value, coll, filtr, exp, collector): @@ -751,13 +759,13 @@ def expandMatchBind(self, spec, patt): t.SeqExpr([ t.Def(patt, ejector, sp), mcall("__makeList", "run", - TRUE, *[t.BindingExpr(n) for n in patternNouns])]), - t.Catch(t.FinalPattern(problem, None), - t.SeqExpr([ - t.Def(slotpatt(broken), None, - mcall("Ref", "broken", problem)), - mcall("__makeList", "run", - FALSE, *([t.BindingExpr(broken)] * len(patternNouns)))])))), + TRUE, *[t.BindingExpr(n) for n in patternNouns])]), + t.FinalPattern(problem, None), + t.SeqExpr([ + t.Def(slotpatt(broken), None, + mcall("Ref", "broken", problem)), + mcall("__makeList", "run", + FALSE, *([t.BindingExpr(broken)] * len(patternNouns)))]))), result]) def broke(br, ex): diff --git a/monte/lexer.py b/monte/lexer.py index c087ee0..eb72542 100644 --- a/monte/lexer.py +++ b/monte/lexer.py @@ -883,6 +883,17 @@ def charConstant(self): """ if self.currentChar == '\\': nex = self.nextChar() + if nex == 'U': + hexstr = "" + for i in range(8): + hexstr += self.nextChar() + try: + v = int(hexstr, 16) + except ValueError: + self.syntaxError('\\U escape must be eight hex digits') + else: + self.nextChar() + return unichr(v) if nex == 'u': hexstr = "" for i in range(4): @@ -894,6 +905,14 @@ def charConstant(self): else: self.nextChar() return unichr(v) + if nex == 'x': + try: + v = int(self.nextChar() + self.nextChar(), 16) + except ValueError: + self.syntaxError('\\x escape must be four hex digits') + else: + self.nextChar() + return unichr(v) if nex == EOF: self.syntaxError("end of file in middle of literal") c = { @@ -905,10 +924,10 @@ def charConstant(self): '"': '"', '\'': "'", '\\': '\\', - '\n': None + '\n': None # escaped newline for continuation }.get(nex, -1) if c == -1: - self.syntaxError("Unrecognized escaped character") + self.syntaxError("Unrecognized escaped character " + repr(nex)) else: self.nextChar() return c diff --git a/monte/monte.parsley b/monte/monte.parsley index 798ea09..3bb4065 100644 --- a/monte/monte.parsley +++ b/monte/monte.parsley @@ -21,13 +21,13 @@ justNoun = ((identifier:id -> self.keywordCheck(id) sourceHole = ('${' integer:i '}' -> t.QuasiLiteralExpr(i) |'@{' integer:i '}' -> t.QuasiPatternExpr(i)) -quasiString = subquasi*:qs 'QUASI_CLOSE':qc -> qs + [t.QuasiText(qc)] -subquasi = ('QUASI_OPEN':q -> t.QuasiText(q) +quasiString :inPatt = subquasi(inPatt)*:qs 'QUASI_CLOSE':qc -> qs + ([t.QuasiText(qc)] if len(qc) else []) +subquasi :inPatt = ('QUASI_OPEN':q -> t.QuasiText(q) |'${' seq:e '}' -> t.QuasiExprHole(e) |'_' !(noIgnoreExpressionHole()) |'DOLLAR_IDENT':id -> t.QuasiExprHole(t.NounExpr(id)) - |'@{' br pattern:s '}' -> t.QuasiPatternHole(s) - |"AT_IDENT":id -> t.QuasiPatternHole(t.FinalPattern(t.NounExpr(id), None))) + |?(inPatt) '@{' br pattern:s '}' -> t.QuasiPatternHole(s) + |?(inPatt) "AT_IDENT":id -> t.QuasiPatternHole(t.FinalPattern(t.NounExpr(id), None))) reifyExpr = ("&&" noun:v -> t.BindingExpr(v) |"&" noun:v -> t.SlotExpr(v)) @@ -37,8 +37,8 @@ verb = identifier | string comprehension = 'for' forPattern:p 'in' br assign:a -> p + [a] listAndMap = "[" ( expr:e br comprehension:c ('if' expr)?:f ']' -> t.ListComp(*(c + [f, e])) | expr:k "=>" expr:v br comprehension:c ('if' expr)?:f ']' -> t.MapComp(*(c + [f, k, v])) - | assoc:x ("," assoc)*:xs ","? ']' -> t.MapExpr([x] + xs) - | (expr:s ("," expr)*:ss ","? ']')-> t.ListExpr([s] + ss) + | assoc:x ("," assoc)*:xs ","? br ']' -> t.MapExpr([x] + xs) + | (expr:s ("," expr)*:ss ","? br ']')-> t.ListExpr([s] + ss) | ']' -> t.ListExpr([])) assoc = (expr:k "=>" expr:v -> t.MapExprAssoc(k, v) |"=>" (noun | reifyExpr):n -> t.MapExprExport(n) @@ -48,10 +48,10 @@ assoc = (expr:k "=>" expr:v -> t.MapExprAssoc(k, v) prim = ( literal | basic - | identifier?:n quasiString:qs -> t.QuasiExpr(n, qs) + | identifier?:n quasiString(0):qs -> t.QuasiExpr(n, qs) | noun | uri - | parenExpr:p (quasiString:qs -> t.QuasiExpr(p, qs) + | parenExpr:p (quasiString(0):qs -> t.QuasiExpr(p, qs) | -> p) | block:b -> t.HideExpr(b) | listAndMap @@ -123,7 +123,7 @@ ejector = ('break' -> t.Break guard = (noun | parenExpr):e ("[" args:x ']' -> x)*:xs -> t.Guard(e, xs) optGuard = (':' guard)? eqPattern = ('_' optGuard:e -> t.IgnorePattern(e) - |identifier?:n quasiString:q -> t.QuasiPattern(n, q) + |identifier?:n quasiString(1):q -> t.QuasiPattern(n, q) |namePattern |"==" prim:p -> t.SamePattern(p) |"!=" prim:p -> t.NotSamePattern(p) @@ -215,11 +215,11 @@ messageDesc = br (doco?:doc ("to" | "method"):to verb?:v parenParamDescList:ps o paramDesc = (justNoun | ('_') -> None):n optGuard:g -> t.ParamDesc(n, g) parenParamDescList = "(" paramDesc:p ("," paramDesc)*:ps ')' -> [p] + ps -escapeExpr = 'escape' pattern:p block:b catcher?:c -> t.Escape(p, b, c) -indentEscapeExpr = 'escape' pattern:p indentBlock:b indentCatcher?:c -> t.Escape(p, b, c) +escapeExpr = 'escape' pattern:p block:b (catcher | -> (None, None)):c -> t.Escape(p, b, *c) +indentEscapeExpr = 'escape' pattern:p indentBlock:b (indentCatcher | -> (None, None)):c -> t.Escape(p, b, *c) -forExpr = 'for' forPattern:p 'in' br assign:a block:b catcher?:c -> t.For(*(p + [a, b, c])) -indentForExpr = 'for' forPattern:p 'in' br assign:a indentBlock:b indentCatcher?:c -> t.For(*(p + [a, b, c])) +forExpr = 'for' forPattern:p 'in' br assign:a block:b (catcher | -> (None, None)):c -> t.For(*(p + [a, b, c])) +indentForExpr = 'for' forPattern:p 'in' br assign:a indentBlock:b (indentCatcher | -> (None, None)):c -> t.For(*(p + [a, b, c])) forPattern = pattern:p (br "=>" pattern:px -> [p, px] | -> [None, p]) @@ -236,13 +236,13 @@ metaExpr = 'meta' "." identifier:i (?(i == 'getState') -> "State" switchExpr = 'switch' parenExpr:e "{" (matcher:m br -> m)*:ms '}' -> t.Switch(e, ms) indentSwitchExpr = 'switch' parenExpr:e ':' EOLs tok('INDENT') br ((matcher | indentMatcher):m br -> m)*:ms tok('DEDENT') -> t.Switch(e, ms) -tryExpr = 'try' block:tb catcher*:cs ('finally' block)?:fb -> t.Try(tb, cs, fb) -indentTryExpr = 'try' indentBlock:tb indentCatcher*:cs (br 'finally' indentBlock)?:fb -> t.Try(tb, cs, fb) -catcher = 'catch' pattern:p block:b -> t.Catch(p, b) -indentCatcher = br 'catch' pattern:p indentBlock:b -> t.Catch(p, b) +tryExpr = 'try' block:tb catcher*:cs ('finally' block)?:fb -> t.Try(tb, [t.Catch(*c) for c in cs], fb) +indentTryExpr = 'try' indentBlock:tb indentCatcher*:cs (br 'finally' indentBlock)?:fb -> t.Try(tb, [t.Catch(*c) for c in cs], fb) +catcher = 'catch' pattern:p block:b -> (p, b) +indentCatcher = br 'catch' pattern:p indentBlock:b -> (p, b) -whileExpr = 'while' parenExpr:e block:b catcher?:c -> t.While(e, b, c) -indentWhileExpr = 'while' parenExpr:e indentBlock:b indentCatcher?:c -> t.While(e, b, c) +whileExpr = 'while' parenExpr:e block:b (catcher | -> (None, None)):c -> t.While(e, b, c) +indentWhileExpr = 'while' parenExpr:e indentBlock:b (indentCatcher | -> (None, None)):c -> t.While(e, b, c) whenExpr = 'when' parenArgs:a br "->" block:b catcher*:cs ('finally' block)?:fb -> t.When(a, b, cs, fb) indentWhenBlock = '->' EOLs tok('INDENT') br (indentSeq | tok('pass') -> t.SeqExpr([])):s br @@ -256,3 +256,4 @@ topSeq = topExpr:x (seqSep topExpr)*:xs seqSep? -> t.SeqExpr([x] + xs) pragma = 'pragma' "." verb:v "(" string:s ')' -> t.Pragma(v, s) topExpr = (pragma -> t.NounExpr("null")) | blockExpr start = br (module | topSeq)? +justModule = modExp:imports exportExp:exports anything* -> t.Module(imports, exports, None) diff --git a/monte/parser.py b/monte/parser.py index 5ef16b9..3fbfa57 100644 --- a/monte/parser.py +++ b/monte/parser.py @@ -197,6 +197,20 @@ def parse(source, origin="", tracefunc=None): import sys sys.exit(1) + +def parseJustModule(source, origin="", tracefunc=None): + from parsley import _GrammarWrapper + p = makeParser(source, origin) + if tracefunc: + p._trace = tracefunc + try: + return _GrammarWrapper(p, source).justModule() + except ParseError as e: + prettyParseErrorPrinter(e, source) + import sys + sys.exit(1) + + def prettyParseErrorPrinter(err, source): trail = err.trail diff --git a/monte/runtime/audit.py b/monte/runtime/audit.py index c69c617..c7ee634 100644 --- a/monte/runtime/audit.py +++ b/monte/runtime/audit.py @@ -1,11 +1,13 @@ from monte.runtime.base import MonteObject, throw -from monte.runtime.data import String, bwrap, true +from monte.runtime.data import Twine, bwrap, true from monte.runtime.guards.base import deepFrozenGuard -from monte.runtime.guards.data import booleanGuard +from monte.runtime.guards.data import booleanGuard, twineGuard + class Audition(MonteObject): _m_fqn = "Audition" - def __init__(self, fqn, expr, bindings, obj, outerNames): + + def __init__(self, fqn, expr, bindings, obj, outerNames, cache): self.expr = expr self.bindings = bindings self.approvers = [] @@ -13,25 +15,66 @@ def __init__(self, fqn, expr, bindings, obj, outerNames): self.fqn = fqn self._active = True self.outerNames = outerNames + self.askedLog = [] + self.guardLog = [] + self.auditorCache = cache + def ask(self, auditor): if not self._active: raise RuntimeError("audition is out of scope") - #XXX caching of audit results - result = booleanGuard.coerce(auditor.audit(self), throw) - if result is true: - self.approvers.append(auditor) + doCaching = deepFrozenGuard in auditor._m_auditorStamps + cached = False + if doCaching: + if id(auditor) in self.auditorCache: + answer, asked, guards = self.auditorCache[id(auditor)] + + for name, value in guards: + namestr = twineGuard.coerce(name, throw).bare() + if not (self.bindings.get(namestr.s) == value): + break + else: + cached = True + + if cached: + for a in asked: + self.ask(a) + if answer is true: + self.approvers.append(auditor) + return answer + else: + prevlogs = self.askedLog, self.guardLog + self.askedLog = [] + self.guardLog = [] + try: + #print "%s auditing %s" % (auditor, self.fqn) + result = booleanGuard.coerce(auditor.audit(self), throw) + if doCaching and self.guardLog is not None: + #print "audit cached:", result + self.auditorCache[id(auditor)] = (result, self.askedLog[:], self.guardLog[:]) + if result is true: + #print self.fqn, "approved by", auditor + self.approvers.append(auditor) + finally: + self.askedLog, self.guardLog = prevlogs + return result def getObjectExpr(self): return self.expr def getGuard(self, name): - if not isinstance(name, String): - raise RuntimeError("%r is not a string" % (name,)) - if name.s not in self.bindings: + n = twineGuard.coerce(name, throw).bare().s + if n not in self.bindings: + self.guardLog = None raise RuntimeError('"%s" is not a free variable in %s' % - (name.s, str(self.obj))) - return self.bindings[name.s] + (name, str(self.obj))) + answer = self.bindings[n] + if self.guardLog is not None: + if deepFrozenGuard in answer._m_auditorStamps: + self.guardLog.append((name, answer)) + else: + self.guardLog = None + return answer def getFQN(self): return self.fqn @@ -39,9 +82,11 @@ def getFQN(self): def getOuterNames(self): return self.outerNames + class AuditChecker(MonteObject): _m_fqn = "__auditedBy" _m_auditorStamps = (deepFrozenGuard,) + def run(self, auditor, specimen): return bwrap(auditor in getattr(specimen, "_m_auditorStamps", ())) diff --git a/monte/runtime/base.py b/monte/runtime/base.py index 630a625..1b6909d 100644 --- a/monte/runtime/base.py +++ b/monte/runtime/base.py @@ -3,7 +3,7 @@ """ import StringIO -from monte import ast +from monte import ast, TimeRecorder class _SlotDescriptor(object): @@ -16,10 +16,12 @@ def __get__(self, obj, typ): def __set__(self, obj, val): return obj._m_slots[self.name][0].put(val) +ASTCACHE = {} class MonteObject(object): _m_matcherNames = () _m_auditorStamps = () + _m_auditorCache = None _m_fqn = "" def __init__(self): self._m_slots = {} @@ -28,8 +30,14 @@ def _conformTo(self, guard): return self def _m_audit(self, auditors, scope): + if self.__class__._m_auditorCache is None: + self.__class__._m_auditorCache = {} from monte.runtime.audit import Audition - expr = ast.load(self._m_objectExpr) + expr = getattr(self.__class__, '_m_objectAst', None) + if expr is None: + with TimeRecorder("astLoad"): + expr = ast.load(self._m_objectExpr) + self.__class__._m_objectAst = expr bindingGuards = dict([(k, v[1]) for k, v in self._m_slots.iteritems()]) bindingGuards.update(self._m_outers) audition = Audition( @@ -37,9 +45,11 @@ def _m_audit(self, auditors, scope): expr, bindingGuards, self, - scope.keys()) - for auditor in auditors: - audition.ask(auditor) + scope.keys(), + self.__class__._m_auditorCache) + with TimeRecorder("audit"): + for auditor in auditors: + audition.ask(auditor) audition._active = False self._m_auditorStamps = audition.approvers @@ -88,7 +98,8 @@ def __getattr__(self, verb): if self._m_matcherNames: return _MonteMatcher(self, verb) else: - raise AttributeError(verb) + # Has to be AttributeError because some Python code might use getattr() + raise AttributeError("No such method: %s.%s()" % (self._m_fqn, verb)) def __call__(self, *args): return self.run(*args) @@ -105,8 +116,9 @@ def __str__(self): return toString(self) def __iter__(self): - for (k, v) in self._makeIterator(): - yield v + for pair in self._makeIterator(): + from monte.runtime.data import Integer + yield pair.get(Integer(1)) def toString(obj): @@ -157,6 +169,7 @@ def ej(val=null): def ejector(_name): + from monte.runtime.data import null class ejtype(MonteEjection): name = _name pass @@ -165,7 +178,7 @@ class ej(MonteObject): _m_type = ejtype _m_active = True - def __call__(self, val=None): + def run(self, val=null): if not self._m_active: throw("Ejector is not active") raise ejtype(val) @@ -180,10 +193,12 @@ class Throw(MonteObject): _m_fqn = "throw" ## This is patched later to avoid import circularity #_m_auditorStamps = (deepFrozenGuard,) - def __call__(self, val): - from monte.runtime.data import String - if isinstance(val, String): - val = val.s + def run(self, val): + from monte.runtime.data import Twine + from monte.runtime.ref import _resolution + val = _resolution(val) + if isinstance(val, Twine): + val = val.bare().s raise RuntimeError(val) def eject(self, ej, val): from monte.runtime.data import null @@ -193,4 +208,3 @@ def eject(self, ej, val): wrapEjector(ej)(val) throw = Throw() - diff --git a/monte/runtime/bindings.py b/monte/runtime/bindings.py index 5334588..564dcab 100644 --- a/monte/runtime/bindings.py +++ b/monte/runtime/bindings.py @@ -1,6 +1,6 @@ from monte.runtime.base import MonteObject, throw from monte.runtime.data import null -from monte.runtime.guards.base import Guard, anyGuard, deepFrozenFunc, deepFrozenGuard, selflessGuard +from monte.runtime.guards.base import Guard, anyGuard, deepFrozenFunc, deepFrozenGuard, isDeepFrozen, selflessGuard from monte.runtime.tables import ConstList class FinalSlot(MonteObject): @@ -23,6 +23,14 @@ def __init__(self, val, guard=null, ej=throw, unsafe=False): self.guard = anyGuard self.val = val + @classmethod + def run(cls, val): + """ + __makeFinalSlot.run(val), basically. + """ + + return cls(val) + def getGuard(self): return FinalSlotGuard(self.guard) @@ -72,13 +80,16 @@ def _printOn(self, out): class FinalSlotGuard(Guard): _m_fqn = "FinalSlot" - _m_auditorStamps = (selflessGuard, deepFrozenGuard) def __init__(self, valueGuard, maker=False): #XXX separate guard maker from FinalSlot[any] self.maker = maker if valueGuard is null: valueGuard = anyGuard self.valueGuard = valueGuard + if isDeepFrozen(valueGuard): + self._m_auditorStamps = (selflessGuard, deepFrozenGuard) + else: + self._m_auditorStamps = (selflessGuard,) def getValueGuard(self): return self.valueGuard @@ -146,9 +157,9 @@ def _printOn(self, out): class Binding(MonteObject): _m_fqn = "Binding" - def __init__(self, guard, slot): - self.guard = guard + def __init__(self, slot, guard): self.slot = slot + self.guard = guard def get(self): return self.slot @@ -183,10 +194,10 @@ def reifyBinding(arg, ej=_absent): """ if ej is _absent: def guardedSlotToBinding(specimen, ejector): - return Binding(arg, arg.coerce(specimen, ejector)) + return Binding(arg.coerce(specimen, ejector), arg) return guardedSlotToBinding else: - return Binding(anyGuard, arg) + return Binding(arg, anyGuard) @deepFrozenFunc def slotFromBinding(b): diff --git a/monte/runtime/data.py b/monte/runtime/data.py index 3a79b6b..ab5830e 100644 --- a/monte/runtime/data.py +++ b/monte/runtime/data.py @@ -1,6 +1,6 @@ import struct, math from sys import float_info -from monte.runtime.base import MonteObject +from monte.runtime.base import MonteObject, ejector, throw from monte.runtime.flow import MonteIterator class MonteNull(MonteObject): @@ -40,26 +40,31 @@ def __eq__(self, other): return bwrap(self._b == other._b) def _m_and(self, other): - if not isinstance(other, Bool): - raise RuntimeError("Bools can't be compared with non-bools") + from monte.runtime.guards.data import booleanGuard + other = booleanGuard.coerce(other, throw) return bwrap(self._b and other._b) def _m_or(self, other): - if not isinstance(other, Bool): - raise RuntimeError("Bools can't be compared with non-bools") + from monte.runtime.guards.data import booleanGuard + other = booleanGuard.coerce(other, throw) return bwrap(self._b or other._b) def _m_not(self): return bwrap(not self._b) + def butNot(self, other): + from monte.runtime.guards.data import booleanGuard + other = booleanGuard.coerce(other, throw) + return bwrap(self._b and not other._b) + def xor(self, other): - if not isinstance(other, Bool): - raise RuntimeError("Bools can't be compared with non-bools") + from monte.runtime.guards.data import booleanGuard + other = booleanGuard.coerce(other, throw) return bwrap(self._b != other._b) - def op__cmp(self, other): - if not isinstance(other, Bool): - raise RuntimeError("%r is not a boolean" % (other,)) + def op__cmp(self, other): + from monte.runtime.guards.data import booleanGuard + other = booleanGuard.coerce(other, throw) return Integer(cmp(self._b, other._b)) def _printOn(self, out): @@ -73,20 +78,33 @@ def bwrap(b): return true if b else false _CHAR_ESCAPES = { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"': '\\"', - '\'': '\\\'', + u'\b': u'\\b', + u'\t': u'\\t', + u'\n': u'\\n', + u'\f': u'\\f', + u'\r': u'\\r', + u'"': u'\\"', + u'\'': u'\\\'', } + + def escapedChar(c): if c in _CHAR_ESCAPES: return _CHAR_ESCAPES[c] i = ord(c) if i < 32 or i > 126: - return '\\u%04x' % i + return u'\\u%04x' % i + return c + + +def escapedByte(c): + if c in _CHAR_ESCAPES: + return _CHAR_ESCAPES[c] + i = ord(c) + if i > 255: + raise RuntimeError("not a bytestring") + if i < 32 or i > 126: + return u'\\x%02x' % i return c @@ -108,16 +126,24 @@ def __eq__(self, other): return false return bwrap(self._c == other._c) + def asInteger(self): + return Integer(ord(self._c)) + def add(self, other): - if not isinstance(other, Integer): - raise RuntimeError("%r is not an integer" % (other,)) + from monte.runtime.guards.data import intGuard + other = intGuard.coerce(other, throw) return Character(unichr(ord(self._c) + other.n)) def subtract(self, other): - if not isinstance(other, Integer): - raise RuntimeError("%r is not an integer" % (other,)) + from monte.runtime.guards.data import intGuard + other = intGuard.coerce(other, throw) return Character(unichr(ord(self._c) - other.n)) + def op__cmp(self, other): + from monte.runtime.guards.data import charGuard + other = charGuard.coerce(other, throw) + return Integer(cmp(self._c, other._c)) + def next(self): if ord(self._c) == 0x10FFFF: return self @@ -129,13 +155,13 @@ def previous(self): return Character(unichr(ord(self._c) - 1)) def max(self, other): - if not isinstance(other, Character): - raise RuntimeError("%r is not a character" % (other,)) + from monte.runtime.guards.data import charGuard + other = charGuard.coerce(other, throw) return Character(max(self._c, other._c)) def min(self, other): - if not isinstance(other, Character): - raise RuntimeError("%r is not a character" % (other,)) + from monte.runtime.guards.data import charGuard + other = charGuard.coerce(other, throw) return Character(min(self._c, other._c)) def quote(self): @@ -145,6 +171,115 @@ def _printOn(self, out): out.raw_print(self._c) +def makeCharacter(i): + from monte.runtime.guards.data import intGuard + i = intGuard.coerce(i, throw) + return Character(unichr(i.n)) + + +class Bytestring(MonteObject): + def __init__(self, b): + assert isinstance(b, str) + self.b = b + + def quote(self): + return String(u''.join([u'b`'] + [escapedByte(c) for c in self.b] + + [u'`'])) + def _printOn(self, out): + out._m_print(self.quote()) + + def __eq__(self, other): + if not isinstance(other, Bytestring): + return false + return bwrap(self.b == other.b) + + def _makeIterator(self): + from monte.runtime.tables import ConstList + return MonteIterator(ConstList((Integer(i), x) for i, x in enumerate(Integer(ord(b)) for b in self.b))) + + def op__cmp(self, other): + from monte.runtime.guards.data import bytesGuard + other = bytesGuard.coerce(other, throw) + return Integer(cmp(self.b, other.b)) + + def get(self, idx): + from monte.runtime.guards.data import intGuard + idx = intGuard.coerce(idx, throw) + return Integer(ord(self.b[idx.n])) + + def slice(self, start, end=None): + from monte.runtime.guards.data import intGuard + start = intGuard.coerce(start, throw) + start = start.n + if end is not None and not isinstance(end, Integer): + raise RuntimeError("%r is not an integer" % (end,)) + elif end is not None: + end = end.n + if start < 0: + raise RuntimeError("Slice indices must be positive") + if end is not None and end < 0: + raise RuntimeError("Slice indices must be positive") + return Bytestring(self.b[start:end]) + + def size(self): + return Integer(len(self.b)) + + def add(self, other): + from monte.runtime.guards.data import bytesGuard + other = bytesGuard.coerce(other, throw) + return Bytestring(self.b + other.b) + + def multiply(self, n): + from monte.runtime.guards.data import intGuard + n = intGuard.coerce(n, throw) + return Bytestring(self.b * n.n) + + def startsWith(self, other): + from monte.runtime.guards.data import bytesGuard + other = bytesGuard.coerce(other, throw) + + return bwrap(self.b.startswith(other.b)) + + def endsWith(self, other): + from monte.runtime.guards.data import bytesGuard + other = bytesGuard.coerce(other, throw) + + return bwrap(self.b.endswith(other.b)) + + def split(self, other): + from monte.runtime.tables import ConstList + from monte.runtime.guards.data import bytesGuard + other = bytesGuard.coerce(other, throw) + return ConstList(Bytestring(x) for x in self.b.split(other.b)) + + def join(self, items): + it = items._makeIterator() + ej = ejector("iteration") + segments = [] + try: + while True: + key, item = it.next(ej) + segments.append(item) + except ej._m_type: + pass + finally: + ej.disable() + return Bytestring(self.b.join(segments)) + + # E calls this 'replaceAll'. + def replace(self, old, new): + from monte.runtime.guards.data import bytesGuard + old = bytesGuard.coerce(old, throw) + new = bytesGuard.coerce(new, throw) + return Bytestring(self.b.replace(old.b, new.b)) + + def toUpperCase(self): + return String(self.b.upper()) + + def toLowerCase(self): + return String(self.b.lower()) + + class Integer(MonteObject): _m_fqn = "__makeInt$int" #_m_auditorStamps = (deepFrozenGuard,) @@ -172,9 +307,8 @@ def asFloat(self): return Float(float(self.n)) def toString(self, radix): - if not isinstance(radix, Integer): - raise RuntimeError("%r is not a integer" % (radix,)) - radix = radix.n + from monte.runtime.guards.data import intGuard + radix = intGuard.coerce(radix, throw).n if radix == 16: return String(hex(self.n)[2:].decode('ascii')) elif radix == 10: @@ -239,8 +373,8 @@ def butNot(self, other): # Comparator. def op__cmp(self, other): - if not isinstance(other, (Integer, Float)): - raise RuntimeError("%r is not a number" % (other,)) + from monte.runtime.guards.data import floatGuard + other = floatGuard.coerce(other, throw) return Integer(cmp(self.n, other.n)) # Comparison protocol. @@ -294,19 +428,26 @@ def bitLength(self): return Integer(self.n.bit_length()) def max(self, other): - if not isinstance(other, Integer): - raise RuntimeError("%r is not an integer" % (other,)) + from monte.runtime.guards.data import intGuard + other = intGuard.coerce(other, throw) return numWrap(max(self.n, other.n)) def min(self, other): - if not isinstance(other, Integer): - raise RuntimeError("%r is not an integer" % (other,)) + from monte.runtime.guards.data import intGuard + other = intGuard.coerce(other, throw) return numWrap(min(self.n, other.n)) def _printOn(self, out): out.raw_print(unicode(self.n)) +def makeInteger(s, radix=Integer(10)): + from monte.runtime.guards.data import intGuard, twineGuard + s = twineGuard.coerce(s, throw) + radix = intGuard.coerce(radix, throw) + return Integer(int(s.bare().s, radix.n)) + + class Float(MonteObject): _m_fqn = "__makeFloat$float" #_m_auditorStamps = (deepFrozenGuard,) @@ -318,7 +459,7 @@ def __hash__(self): return hash(self.n) def __eq__(self, other): - if not isinstance(other, (Integer, Float)): + if not isinstance(other, Float): return false return bwrap(self.n == other.n) @@ -350,8 +491,8 @@ def pow(self, other): # Comparator. def op__cmp(self, other): - if not isinstance(other, (Integer, Float)): - raise RuntimeError("%r is not a number" % (other,)) + from monte.runtime.guards.data import floatGuard + other = floatGuard.coerce(other, throw) #cmp doesn't do NaNs, so if self.n < other.n: return Float(-1.0) @@ -380,10 +521,10 @@ def isZero(self): return bwrap(0 == self.n) def isNaN(self): - return math.isnan(self.n) + return bwrap(math.isnan(self.n)) def isInfinite(self): - return math.isinf(self.n) + return bwrap(math.isinf(self.n)) # Floatish methods. @@ -436,16 +577,22 @@ def previous(self): # Misc. def max(self, other): - if not isinstance(other, (Float, Integer)): - raise RuntimeError("%r is not a number" % (other,)) + from monte.runtime.guards.data import floatGuard + other = floatGuard.coerce(other, throw) return numWrap(max(self.n, other.n)) def min(self, other): - if not isinstance(other, (Float, Integer)): - raise RuntimeError("%r is not an integer" % (other,)) + from monte.runtime.guards.data import floatGuard + other = floatGuard.coerce(other, throw) return numWrap(min(self.n, other.n)) +def makeFloat(s): + from monte.runtime.guards.data import twineGuard + s = twineGuard.coerce(s, throw) + return Float(s.bare().s) + + def numWrap(n): if isinstance(n, float): return Float(n) @@ -459,8 +606,304 @@ def numWrap(n): infinity = Float(float('inf')) -class String(MonteObject): - _m_fqn = "__makeStr$str" +class TwineMaker(MonteObject): + _m_fqn = "__makeString" + + def fromParts(self, parts): + from monte.runtime.guards.tables import listGuard + parts = listGuard.coerce(parts, throw) + if len(parts.l) == 0: + return theEmptyTwine + elif len(parts.l) == 1: + return parts.l[0] + elif all(isinstance(p, String) for p in parts): + return String(u''.join(p.s for p in parts)) + else: + return CompositeTwine(parts) + + def fromString(self, s, span=null): + from monte.runtime.guards.data import twineGuard + s = twineGuard.coerce(s, throw).bare() + if span is null: + return s + else: + return LocatedTwine(s.s, span) + + def fromChars(self, chars): + from monte.runtime.guards.tables import listGuard + from monte.runtime.guards.data import charGuard + chars = listGuard.coerce(chars, throw) + return String(u''.join(charGuard.coerce(c, throw)._c for c in chars.l)) + +theTwineMaker = TwineMaker() + + +class Twine(MonteObject): + def add(self, other): + from monte.runtime.guards.data import twineGuard + from monte.runtime.tables import ConstList + other = twineGuard.coerce(other, throw) + mine = self.getParts().l + his = other.getParts().l + if len(mine) > 1 and len(his) > 1: + # Smush the last and first segments together, if they'll fit. + mine = mine[:-1] + mine[-1]._m_mergedParts(his[0]).l + his = his[1:] + return theTwineMaker.fromParts(ConstList(mine + his)) + + def asFrom(self, origin, startLine=Integer(1), startCol=Integer(0)): + from monte.runtime.tables import ConstList + from monte.runtime.guards.data import intGuard + startLine = intGuard.coerce(startLine, throw) + startCol = intGuard.coerce(startCol, throw) + parts = [] + s = self.bare().s + end = len(s) + i = 0 + j = s.find(u'\n') + while i < end: + if j == -1: + j = end - 1 + endCol = Integer(startCol.n + j - i) + span = SourceSpan(origin, true, startLine, startCol, + startLine, endCol) + parts.append(LocatedTwine(s[i:j + 1], span)) + startLine = Integer(startLine.n + 1) + startCol = Integer(0) + i = j + 1 + j = s.find(u'\n', i) + return theTwineMaker.fromParts(ConstList(parts)) + + def endsWith(self, other): + return self.bare().endsWith(other) + + def getPartAt(self, pos): + from monte.runtime.tables import ConstList + from monte.runtime.guards.data import intGuard + pos = intGuard.coerce(pos, throw) + if pos.n < 0: + raise RuntimeError("Index out of bounds") + parts = self.getParts().l + sofar = 0 + for (i, atom) in enumerate(parts): + siz = atom.size() + if pos.n < sofar + siz.n: + return ConstList([Integer(i), Integer(pos.n - sofar)]) + sofar += siz.n + raise RuntimeError("%s not in 0..!%s" % (pos.n, sofar)) + + def getSourceMap(self): + from monte.runtime.tables import ConstList, ConstMap + parts = self.getParts().l + result = [] + offset = 0 + for part in parts: + partSize = part.size().n + span = part.getSpan() + if span is not null: + k = ConstList([Integer(offset), Integer(offset + partSize)]) + result.append((k, span)) + offset += partSize + return ConstMap(dict(result), [x[0] for x in result]) + + def infect(self, other, oneToOne=false): + from monte.runtime.guards.data import twineGuard + other = twineGuard.coerce(other, throw) + if oneToOne is true: + if self.size() == other.size(): + return self._m_infectOneToOne(other) + else: + raise RuntimeError("%r and %r must be the same size" % + (other, self)) + else: + span = self.getSpan() + if span is not null: + span = span.notOneToOne() + return theTwineMaker.fromString(other, span) + + def join(self, items): + from monte.runtime.guards.data import twineGuard + from monte.runtime.tables import ConstList + it = items._makeIterator() + ej = ejector("iteration") + segments = [] + try: + while True: + key, item = it.next(ej) + item = twineGuard.coerce(item, throw) + segments.append(item) + segments.append(self) + except ej._m_type: + pass + finally: + ej.disable() + if segments: + segments.pop() + return theTwineMaker.fromParts(ConstList(segments)) + + def op__cmp(self, other): + return Integer(cmp(self.bare().s, other.bare().s)) + + def multiply(self, n): + from monte.runtime.guards.data import intGuard + n = intGuard.coerce(n, throw) + result = theEmptyTwine + for _ in range(n.n): + result = result.add(self) + return result + + def quote(self): + result = String(u'"') + p1 = 0 + for p2 in range(self.size().n): + ch = self.get(Integer(p2)) + if ch._c != '\n': + ech = escapedChar(ch._c) + if len(ech) > 1: + result = result.add(self.slice(Integer(p1), Integer(p2))) + result = result.add(self.slice(Integer(p2), + Integer(p2 + 1)) + .infect(String(ech))) + p1 = p2 + 1 + result = result.add(self.slice(Integer(p1), self.size())) + return result.add(String(u'"')) + + def split(self, other): + from monte.runtime.tables import ConstList + from monte.runtime.guards.data import twineGuard + other = twineGuard.coerce(other, throw) + sepLen = other.size().n + if sepLen == Integer(0): + raise RuntimeError("separator must not empty") + result = [] + p1 = 0 + p2 = self.indexOf(other).n + while p2 != -1: + result.append(self.slice(Integer(p1), Integer(p2))) + p1 = p2 + sepLen + p2 = self.indexOf(other, Integer(p1)).n + result.append(self.slice(Integer(p1), self.size())) + return ConstList(result) + + def startsWith(self, other): + return self.bare().startsWith(other) + + # E calls this 'replaceAll'. + def replace(self, old, new): + from monte.runtime.guards.data import twineGuard + old = twineGuard.coerce(old, throw) + new = twineGuard.coerce(new, throw) + result = theEmptyTwine + oldLen = old.size().n + if oldLen == 0: + raise RuntimeError("can't replace the null string") + p1 = 0 + p2 = self.indexOf(old).n + while p2 != -1: + left = self.slice(Integer(p1), Integer(p2)) + chunk = self.slice(Integer(p2), Integer(p2 + oldLen)) + result = result.add(left).add(chunk.infect(new, false)) + p1 = p2 + oldLen + p2 = self.indexOf(old, Integer(p1)).n + result = result.add(self.slice(Integer(p1), self.size())) + return result + + def toUpperCase(self): + return self.infect(String(self.bare().s.upper()), true) + + def toLowerCase(self): + return self.infect(String(self.bare().s.lower()), true) + + +class EmptyTwine(Twine): + def _uncall(self): + from monte.runtime.tables import ConstList + return ConstList([theTwineMaker, "fromParts", + ConstList([ConstList([])])]) + + def size(self): + return Integer(0) + + def bare(self): + return self + + def get(self, idx): + raise RuntimeError("index out of bounds") + + def getParts(self): + from monte.runtime.tables import ConstList + return ConstList([]) + + def getSpan(self): + return null + + def isBare(self): + return true + + def slice(self, start, end=None): + return self + + def _printOn(self, out): + out.raw_print(u"") + + def _m_infectOneToOne(self, other): + return other + +theEmptyTwine = EmptyTwine() + + +def _slice(self, start, end=None): + from monte.runtime.guards.data import intGuard + start = intGuard.coerce(start, throw).n + if end is not None: + end = intGuard.coerce(end, throw).n + if start < 0: + raise RuntimeError("Slice indices must be positive") + if end is not None and end < 0: + raise RuntimeError("Slice indices must be positive") + return self.s[start:end] + + +class AtomicTwine(Twine): + def endsWith(self, other): + from monte.runtime.guards.data import twineGuard + suffix = twineGuard.coerce(other, throw).bare().s + return bwrap(self.s.endswith(suffix)) + + def get(self, idx): + from monte.runtime.guards.data import intGuard + idx = intGuard.coerce(idx, throw) + return Character(self.s[idx.n]) + + def getParts(self): + from monte.runtime.tables import ConstList + return ConstList([self]) + + def indexOf(self, target, start=None): + from monte.runtime.guards.data import intGuard, twineGuard + target = twineGuard.coerce(target, throw).bare().s + if start is not None: + start = intGuard.coerce(start, throw).n + return Integer(self.s.find(target, start)) + + def size(self): + return Integer(len(self.s)) + + def startsWith(self, other): + from monte.runtime.guards.data import twineGuard + other = twineGuard.coerce(other, throw).bare().s + return bwrap(self.s.startswith(other)) + + def _makeIterator(self): + from monte.runtime.tables import ConstList + return MonteIterator(ConstList((Integer(i), x)) for i, x in enumerate(Character(c) for c in self.s)) + + def _printOn(self, out): + out.raw_print(self.s) + + +class String(AtomicTwine): + _m_fqn = "__makeString$str" #_m_auditorStamps = (deepFrozenGuard,) def __init__(self, s): @@ -468,96 +911,319 @@ def __init__(self, s): raise RuntimeError("%r is not a unicode string" % (s,)) self.s = s - def _printOn(self, out): - out.raw_print(self.s) - - def quote(self): - return String(u''.join([u'"'] + [escapedChar(c) for c in self.s] + [u'"'])) - def __hash__(self): return hash(self.s) def __eq__(self, other): - if not isinstance(other, (String)): + if not isinstance(other, Twine): return false - return bwrap(self.s == other.s) + return bwrap(self.s == other.bare().s) - def _makeIterator(self): - return MonteIterator(enumerate(Character(c) for c in self.s)) + def bare(self): + return self - def op__cmp(self, other): - if not isinstance(other, String): + def _m_mergedParts(self, other): + from monte.runtime.tables import ConstList + if isinstance(other, String): + return ConstList([self.s + other.s]) + if isinstance(other, Twine): + return ConstList([self, other]) + else: raise RuntimeError("%r is not a string" % (other,)) - return Integer(cmp(self.s, other.s)) + def getSpan(self): + return null + + def isBare(self): + return true + + def slice(self, start, end=None): + return String(_slice(self, start, end)) + + def split(self, other): + from monte.runtime.tables import ConstList + from monte.runtime.guards.data import twineGuard + other = twineGuard.coerce(other, throw).bare().s + return ConstList(String(x) for x in self.s.split(other)) + + def _m_infectOneToOne(self, other): + return other.bare() + + +class LocatedTwine(AtomicTwine): + _m_fqn = "__makeString$LocatedTwine" + + def __init__(self, s, span): + if not isinstance(s, unicode): + raise RuntimeError("%r is not a unicode string" % (s,)) + assert isinstance(span, SourceSpan) # XXX get a guard in here + self.s = s + self.span = span + + if (span._isOneToOne is true and + len(s) != (span.endCol.n - span.startCol.n + 1)): + raise RuntimeError("one to one must have matching size") + + def bare(self): + return String(self.s) + + def getSpan(self): + return self.span + + def isBare(self): + return false + + def _m_mergedParts(self, other): + from monte.runtime.tables import ConstList + if isinstance(other, LocatedTwine): + if self.span._isOneToOne is true: + cover = spanCover(self.span, other.span) + if cover is not null and cover._isOneToOne is true: + return ConstList([LocatedTwine(self.s + other.s, cover)]) + if self.span == other.span: + return ConstList([LocatedTwine(self.s + other.s, self.span)]) + return ConstList([self, other]) + + def slice(self, start, stop=None): + sl = String(_slice(self, start, stop)) + if self.span._isOneToOne is true: + if stop is not None: + stop = stop.n + else: + stop = len(self.s) + startCol = self.span.startCol.n + start.n + endCol = startCol + (stop - start.n) - 1 + span = SourceSpan(self.span.uri, true, self.span.startLine, + Integer(startCol), self.span.endLine, Integer(endCol)) + else: + span = self.span + return theTwineMaker.fromString(sl, span) + + def _uncall(self): + from monte.runtime.tables import ConstList + return ConstList([theTwineMaker, String(u"fromString"), + ConstList([self.bare(), self.span])]) + + def _m_infectOneToOne(self, other): + return theTwineMaker.fromString(other, self.span) + + +class CompositeTwine(Twine): + _m_fqn = "__makeString$CompositeTwine" + + def __init__(self, parts): + from monte.runtime.guards.tables import listGuard + parts = listGuard.coerce(parts, throw) + self.parts = parts + self.sizeCache = None + + def bare(self): + return String(u''.join(p.bare().s for p in self.parts.l)) def get(self, idx): - if not isinstance(idx, Integer): - raise RuntimeError("%r is not an integer" % (idx,)) - return Character(self.s[idx.n]) + from monte.runtime.guards.data import intGuard + idx = intGuard.coerce(idx, throw) + part, offset = self.getPartAt(idx).l + return self.parts.l[part.n].get(offset) + + def getParts(self): + from monte.runtime.tables import ConstList + return ConstList(self.parts) + + def getSpan(self): + if not self.parts: + return null + result = self.parts.l[0].getSpan() + for p in self.parts.l[1:]: + if result is null: + return null + result = spanCover(result, p.getSpan()) + return result + + def indexOf(self, target, start=None): + return self.bare().indexOf(target, start) + + def isBare(self): + return false def slice(self, start, end=None): - if not isinstance(start, Integer): - raise RuntimeError("%r is not an integer" % (start,)) - start = start.n + from monte.runtime.guards.data import intGuard + from monte.runtime.tables import ConstList + start = intGuard.coerce(start, throw) + startn = start.n if end is not None and not isinstance(end, Integer): raise RuntimeError("%r is not an integer" % (end,)) elif end is not None: - end = end.n - if start < 0: + endn = end.n + else: + endn = self.size().n + if startn < 0: raise RuntimeError("Slice indices must be positive") - if end is not None and end < 0: + if end is not None and endn < 0: raise RuntimeError("Slice indices must be positive") - return String(self.s[start:end]) + if (startn == endn): + return theEmptyTwine + leftIdx, leftOffset = self.getPartAt(start).l + rightIdx, rightOffset = self.getPartAt(Integer(endn - 1)).l + if leftIdx.n == rightIdx.n: + return self.parts.l[leftIdx.n].slice(leftOffset, + Integer(rightOffset.n + 1)) + left = self.parts.l[leftIdx.n] + middle = self.parts.l[leftIdx.n + 1:rightIdx.n] + right = self.parts.l[rightIdx.n].slice(Integer(0), + Integer(rightOffset.n + 1)) + result = (left.slice(leftOffset, left.size()) + .add(theTwineMaker.fromParts(ConstList(middle))) + .add(right)) + return result def size(self): - return Integer(len(self.s)) + if self.sizeCache is None: + self.sizeCache = Integer(sum(p.size().n for p in self.parts)) + return self.sizeCache - def add(self, other): - if not isinstance(other, String): - raise RuntimeError("%r is not a string" % (other,)) + def _printOn(self, out): + for p in self.parts: + out._m_print(p) - return String(self.s + other.s) + def _uncall(self): + from monte.runtime.tables import ConstList + return ConstList([theTwineMaker, String(u'fromParts'), + self.parts]) - def multiply(self, n): - if not isinstance(n, Integer): - raise RuntimeError("%r is not an integer" % (n,)) - return String(self.s * n.n) + def _m_infectOneToOne(self, other): + result = theEmptyTwine + pos = 0 + for p in self.parts: + siz = p.size().n + segment = other.bare().s[pos:pos + siz] + result = result.add(p._m_infectOneToOne(String(segment))) + pos += siz + return result - def startsWith(self, other): - if not isinstance(other, String): - raise RuntimeError("%r is not a string" % (other,)) - return bwrap(self.s.startswith(other.s)) +def makeSourceSpan(*a): + return SourceSpan(*a) - def endsWith(self, other): - if not isinstance(other, String): - raise RuntimeError("%r is not a string" % (other,)) - return bwrap(self.s.endswith(other.s)) +class SourceSpan(MonteObject): + """ + Information about the original location of a span of text. Twines use + this to remember where they came from. - def split(self, other): - if not isinstance(other, String): - raise RuntimeError("%r is not a string" % (other,)) + uri: Name of document this text came from. - # E calls this 'rjoin'. - def join(self, items): - return String(self.s.join(items)) + isOneToOne: Whether each character in that Twine maps to the + corresponding source character position. - # E calls this 'replaceAll'. - def replace(self, old, new): - if not isinstance(old, String): - raise RuntimeError("%r is not a string" % (old,)) - if not isinstance(new, String): - raise RuntimeError("%r is not a string" % (new,)) - return String(self.s.replace(old.s, new.s)) + startLine, endLine: Line numbers for the beginning and end of the + span. Line numbers start at 1. - def toUpperCase(self): - return String(self.s.upper()) + startCol, endCol: Column numbers for the beginning and end of the + span. Column numbers start at 0. - def toLowerCase(self): - return String(self.s.lower()) + """ + _m_fqn = "SourceSpan" + #_m_auditorStamps = (deepFrozenGuard,) - # XXX Twine methods. + def __init__(self, uri, isOneToOne, startLine, startCol, + endLine, endCol): + from monte.runtime.guards.data import intGuard + if (startLine != endLine and isOneToOne): + raise RuntimeError("one-to-one spans must be on a line") + self.uri = uri + self._isOneToOne = isOneToOne + self.startLine = intGuard.coerce(startLine, throw) + self.startCol = intGuard.coerce(startCol, throw) + self.endLine = intGuard.coerce(endLine, throw) + self.endCol = intGuard.coerce(endCol, throw) + + def notOneToOne(self): + """ + Return a new SourceSpan for the same text that doesn't claim + one-to-one correspondence. + """ + return SourceSpan(self.uri, false, self.startLine, self.startCol, + self.endLine, self.endCol) + + def isOneToOne(self): + return self._isOneToOne + + def getStartLine(self): + return self.startLine + + def getStartCol(self): + return self.startCol + + def getEndLine(self): + return self.endLine + + def getEndCol(self): + return self.endCol + def _printOn(self, out): + out.raw_print(u"<") + out._m_print(self.uri) + out.raw_print(u"#:") + out.raw_print(u"span" if self._isOneToOne is true else u"blob") + out.raw_print(u"::") + for x in (self.startLine, self.startCol, self.endLine): + out._m_print(x) + out.raw_print(u":") + out._m_print(self.endCol) + out.raw_print(u">") + + def _uncall(self): + from monte.runtime.tables import ConstList + return ConstList([makeSourceSpan, String(u'run'), + ConstList([self.uri, self._isOneToOne, + self.startLine, self.startCol, + self.endLine, self.endCol])]) + + def combine(self, other): + return spanCover(self, other) + + +def spanCover(a, b): + """ + Create a new SourceSpan that covers spans `a` and `b`. + """ + + if a is null or b is null: + return null + # XXX need a guard here + assert isinstance(a, SourceSpan) + assert isinstance(b, SourceSpan) + if a.uri != b.uri: + return null + if (a._isOneToOne is true and b._isOneToOne is true + and a.endLine == b.startLine + and a.endCol.add(Integer(1)) == b.startCol): + # These spans are adjacent. + return SourceSpan(a.uri, true, + a.startLine, a.startCol, + b.endLine, b.endCol) + + # find the earlier start point + if a.startLine < b.startLine: + startLine = a.startLine + startCol = a.startCol + elif a.startLine == b.startLine: + startLine = a.startLine + startCol = min(a.startCol, b.startCol) + else: + startLine = b.startLine + startCol = b.startCol + + #find the later end point + if b.endLine > a.endLine: + endLine = b.endLine + endCol = b.endCol + elif a.endLine == b.endLine: + endLine = a.endLine + endCol = max(a.endCol, b.endCol) + else: + endLine = a.endLine + endCol = a.endCol + return SourceSpan(a.uri, false, startLine, startCol, endLine, endCol) diff --git a/monte/runtime/equalizer.py b/monte/runtime/equalizer.py index b6e19f8..61826b7 100644 --- a/monte/runtime/equalizer.py +++ b/monte/runtime/equalizer.py @@ -1,7 +1,8 @@ import warnings from monte.runtime.base import MonteObject, toQuote -from monte.runtime.data import null, true, false, bwrap, Integer, Float, String, Character, Bool +from monte.runtime.data import (null, true, false, bwrap, Integer, Float, + String, Character, Bool) from monte.runtime.guards.base import deepFrozenGuard, selflessGuard from monte.runtime.tables import ConstList, ConstMap @@ -18,10 +19,10 @@ def _pushSofar(left, right, sofar): lid, rid = id(left), id(right) if rid < lid: lid, rid = rid, lid - sofar.append((lid, rid)) + sofar[(lid, rid)] = (left, right) -def _same(left, right, sofar): +def _same(left, right, sofar, dbg=False): from monte.runtime.ref import _resolution left = _resolution(left) right = _resolution(right) @@ -44,11 +45,13 @@ def _same(left, right, sofar): return false _pushSofar(left, right, sofar) for l, r in zip(left, right): - result = _same(l, r, sofar) + result = _same(l, r, sofar, dbg) if result is null: return null if result is false: return false + if result is not true: + import pdb; pdb.set_trace() return true if t in DOES_OWN_HASHING: @@ -68,13 +71,13 @@ def _same(left, right, sofar): return bwrap(left.n == right.n) elif t is Bool: return bwrap(left._b == right._b) - elif t is String: + elif t is String: # Other Twines have uncall methods. return bwrap(left.s == right.s) elif t is Character: return bwrap(left._c == right._c) warnings.warn("Asked to equalize unknown type %r" % t, - RuntimeWarning) + RuntimeWarning) return false @@ -82,15 +85,19 @@ class Equalizer(MonteObject): _m_fqn = "__equalizer" _m_auditorStamps = (deepFrozenGuard,) - def sameEver(self, left, right): - result = _same(left, right, []) + def debugSameEver(self, left, right): + import pdb; pdb.set_trace() + return self.sameEver(left, right, True) + + def sameEver(self, left, right, dbg=False): + result = _same(left, right, {}, dbg) if result is null: raise RuntimeError("Not sufficiently settled: %s == %s" % ( toQuote(left), toQuote(right))) return result def sameYet(self, left, right): - result = _same(left, right, []) + result = _same(left, right, {}) if result is None: return false else: diff --git a/monte/runtime/guards/base.py b/monte/runtime/guards/base.py index e451465..e23eeec 100644 --- a/monte/runtime/guards/base.py +++ b/monte/runtime/guards/base.py @@ -1,12 +1,16 @@ -from monte.runtime.base import MonteObject, ejector, Throw, throw, toQuote, toString -from monte.runtime.data import MonteNull, Bool, Character, Integer, Float, String, bwrap, true, false, null +from monte.runtime.base import (MonteObject, ejector, Throw, throw, toQuote, + toString) +from monte.runtime.data import (MonteNull, Bool, Bytestring, Character, + CompositeTwine, Integer, Float, LocatedTwine, + SourceSpan, String, bwrap, makeSourceSpan, + theTwineMaker, true, false, null) from monte.runtime.flow import monteLooper def tryCoerce(guard, specimen): ej = ejector("coercion attempt") try: return guard.coerce(specimen, ej) - except ej._m_type, p: + except ej._m_type: return None finally: ej.disable() @@ -70,6 +74,17 @@ def requireDeepFrozen(specimen, sofar, ej, root): else: throw.eject(ej, "%s is not DeepFrozen because %s is not" % (toQuote(root), toQuote(specimen))) +def isDeepFrozen(specimen): + ej = ejector("ok") + try: + requireDeepFrozen(specimen, set(), ej, specimen) + except ej._m_type: + return false + finally: + ej.disable() + return true + + def auditForDeepFrozen(audition, ej): from monte.expander import scope expr = audition.getObjectExpr() @@ -129,8 +144,8 @@ def audit(self, audition): DeepFrozenGuard._m_auditorStamps = (deepFrozenGuard,) #To avoid circular imports -for o in (MonteNull, Bool, Character, Integer, Float, String, Throw, - monteLooper): +for o in (MonteNull, Bool, Bytestring, Character, Integer, Float, String, + SourceSpan, Throw, theTwineMaker, makeSourceSpan, monteLooper): o._m_auditorStamps = (deepFrozenGuard,) @@ -143,11 +158,11 @@ def _subCoerce(self, specimen, ej): if isinstance(specimen, self.typ): return specimen else: - throw.eject(ej, "is not a %s" % (self.typ,)) + throw.eject(ej, "%r is not a %s" % (toQuote(specimen), self.typ,)) class AnyGuard(PrintFQN, MonteObject): - _m_fqn = "any" + _m_fqn = "Any" _m_auditorStamps = (deepFrozenGuard,) def coerce(self, specimen, ej): return specimen @@ -171,7 +186,7 @@ def __init__(self, guards): def _printOn(self, out): #XXX pretty printing would be nice here too - out.raw_print(u'any[') + out.raw_print(u'Any[') it = iter(self.guards) out.quote(next(it)) for g in it: @@ -184,7 +199,7 @@ def coerce(self, specimen, ej): val = tryCoerce(guard, specimen) if val is not None: return val - throw.eject(ej, "doesn't match any of %s" % (self.guards,)) + throw.eject(ej, "%s doesn't match any of %s" % (toQuote(specimen), self.guards)) def supersetOf(self, other): for g in self.guards: @@ -200,7 +215,7 @@ def passes(self, specimen): def _subCoerce(self, specimen, ej): if not selflessGuard in specimen._m_auditorStamps: - throw.eject(ej, "is not Selfless") + throw.eject(ej, "%s is not Selfless" % (toQuote(specimen),)) def __eq__(self, other): # to avoid MonteObject.__eq__'s invocation of equalizer @@ -208,7 +223,7 @@ def __eq__(self, other): def audit(self, auditor): #XXX Fixme - return True + return true selflessGuard = SelflessGuard() @@ -220,6 +235,8 @@ def audit(self, audition): transparentStamp = TransparentStamp() +for o in CompositeTwine, LocatedTwine, SourceSpan: + o._m_auditorStamps = (selflessGuard, transparentStamp) class TransparentGuard(Guard): _m_fqn = "Transparent" @@ -229,7 +246,7 @@ def passes(self, specimen): def _subCoerce(self, specimen, ej): if not transparentStamp in specimen._m_auditorStamps: - throw.eject(ej, "is not Transparent") + throw.eject(ej, "%s is not Transparent" % (toQuote(self.specimen),)) def audit(self, audition): from monte.expander import scope @@ -409,7 +426,7 @@ class NullOkGuard(Guard): _m_auditorStamps = (deepFrozenGuard,) def coerce(self, specimen, ej): if specimen is not null: - throw.eject(ej, "must be null") + throw.eject(ej, "%s must be null" % (toQuote(specimen),)) return null def _printOn(self, out): @@ -481,7 +498,11 @@ def coerce(self, specimen, ej): if auditedBy.run(self, conformed): return conformed else: - throw.eject(ej, "Not stamped by %s" % (self,)) + throw.eject(ej, "%s not stamped by %s" % (toQuote(specimen), self)) + + @classmethod + def run(cls, *a): + return cls(*a) @classmethod def makePair(cls, doc, fqn, supers, auditors, msgs): @@ -520,4 +541,4 @@ def coerce(self, specimen, ej): if auditedBy.run(self, conformed): return conformed else: - throw.eject(ej, "Not stamped by %s" % (self,)) + throw.eject(ej, "%s not stamped by %s" % (toQuote(specimen), self)) diff --git a/monte/runtime/guards/data.py b/monte/runtime/guards/data.py index 0123637..769ed63 100644 --- a/monte/runtime/guards/data.py +++ b/monte/runtime/guards/data.py @@ -2,13 +2,14 @@ from functools import partial from monte.runtime.base import throw -from monte.runtime.data import (bwrap, null, true, false, Character, Float, - Integer, String) -from monte.runtime.guards.base import PythonTypeGuard, Guard, PrintFQN, deepFrozenGuard +from monte.runtime.data import (bwrap, null, true, false, Bytestring, Character, + Float, Integer, String, Twine) +from monte.runtime.guards.base import (PythonTypeGuard, Guard, PrintFQN, + deepFrozenGuard) class VoidGuard(PrintFQN, Guard): - _m_fqn = "void" + _m_fqn = "Void" _m_auditorStamps = (deepFrozenGuard,) def _subCoerce(self, specimen, ej): if specimen in [None, null]: @@ -19,13 +20,14 @@ def _subCoerce(self, specimen, ej): class BooleanGuard(PrintFQN, Guard): - _m_fqn = "boolean" + _m_fqn = "Bool" _m_auditorStamps = (deepFrozenGuard,) def _subCoerce(self, specimen, ej): - if specimen in [true, false]: + if specimen is true or specimen is false: return specimen elif specimen in [True, False]: - return bwrap(specimen) + import pdb; pdb.set_trace() + raise ValueError("yer doin it wrong") else: throw.eject(ej, "%r is not a boolean" % (specimen,)) @@ -33,19 +35,11 @@ def _subCoerce(self, specimen, ej): class IntegerGuard(PrintFQN, Guard): - _m_fqn = "int" + _m_fqn = "Int" _m_auditorStamps = (deepFrozenGuard,) - def __init__(self, constraint=None, constraintMessage=''): - super(IntegerGuard, self).__init__() - self.constraint = constraint - self.constraintMessage = constraintMessage - def _subCoerce(self, specimen, ej): if isinstance(specimen, Integer): - if self.constraint is not None and not self.constraint(specimen): - throw.eject(ej, 'Constraint not satisfied: ' + - self.constraintMessage.format(specimen)) return specimen else: throw.eject(ej, "%r is not a number" % (specimen,)) @@ -54,19 +48,10 @@ def _subCoerce(self, specimen, ej): class FloatGuard(PrintFQN, Guard): - _m_fqn = "float" + _m_fqn = "Double" _m_auditorStamps = (deepFrozenGuard,) - def __init__(self, constraint=None, constraintMessage=''): - super(FloatGuard, self).__init__() - self.constraint = constraint - self.constraintMessage = constraintMessage - def _subCoerce(self, specimen, ej): - if self.constraint is not None and not self.constraint(specimen): - throw.eject(ej, 'Constraint not satisfied: ' + - self.constraintMessage.format(specimen)) - if isinstance(specimen, Integer): return Float(specimen.n) elif isinstance(specimen, Float): @@ -77,6 +62,7 @@ def _subCoerce(self, specimen, ej): floatGuard = FloatGuard() -charGuard = PythonTypeGuard(Character, "char") -stringGuard = PythonTypeGuard(String, "str") - +charGuard = PythonTypeGuard(Character, "Char") +stringGuard = PythonTypeGuard(String, "Str") +twineGuard = PythonTypeGuard(Twine, "Twine") +bytesGuard = PythonTypeGuard(Bytestring, "Bytes") diff --git a/monte/runtime/guards/tables.py b/monte/runtime/guards/tables.py index 66ad6fd..f486b97 100644 --- a/monte/runtime/guards/tables.py +++ b/monte/runtime/guards/tables.py @@ -1,4 +1,4 @@ -from monte.runtime.base import throw +from monte.runtime.base import throw, toQuote from monte.runtime.data import true from monte.runtime.guards.base import Guard, anyGuard, deepFrozenGuard from monte.runtime.tables import ConstList, ConstMap @@ -22,7 +22,7 @@ def _subCoerce(self, specimen, ej): return ConstList(specimen.l[:i] + remainder) return specimen else: - throw.eject(ej, "is not a ConstList") + throw.eject(ej, repr(specimen) + " is not a ConstList") class ConstListGuard(_ConstListGuard): def __init__(self): @@ -59,7 +59,7 @@ def _subCoerce(self, specimen, ej): d[coercedK] = coercedV return ConstMap(d, ks) else: - throw.eject(ej, "is not a ConstMap") + throw.eject(ej, toQuote(specimen) + " is not a ConstMap") class ConstMapGuard(_ConstMapGuard): def __init__(self): diff --git a/monte/runtime/helpers.py b/monte/runtime/helpers.py index 7de6f68..09ad1a6 100644 --- a/monte/runtime/helpers.py +++ b/monte/runtime/helpers.py @@ -2,12 +2,27 @@ Objects used by Monte syntax expansions. """ from monte.runtime.base import MonteObject, ejector, throw -from monte.runtime.data import true, false, null, String, Integer, bwrap +from monte.runtime.data import (true, false, null, Twine, Integer) from monte.runtime.equalizer import equalizer from monte.runtime.flow import MonteIterator from monte.runtime.guards.base import deepFrozenGuard, deepFrozenFunc +from monte.runtime.guards.data import intGuard, twineGuard +from monte.runtime.guards.tables import listGuard, mapGuard from monte.runtime.ref import UnconnectedRef -from monte.runtime.tables import ConstList, FlexList, ConstMap, FlexMap, mapMaker +from monte.runtime.tables import (ConstList, FlexList, ConstMap, FlexMap, + mapMaker) + + +class Func(object): + def __init__(self, f): + self._m_auditorStamps = getattr(f, '_m_auditorStamps', ()) + self.f = f + + def __call__(self, *a, **kw): + return self.f(*a, **kw) + + def run(self, *a, **kw): + return self.f(*a, **kw) @deepFrozenFunc def validateFor(flag): @@ -68,12 +83,13 @@ def asBigAs(self, left, right): class MakeVerbFacet(MonteObject): _m_fqn = "__makeVerbFacet$verbFacet" _m_auditorStamps = (deepFrozenGuard,) + def curryCall(self, obj, verb): - if not isinstance(verb, String): - raise RuntimeError("%r is not a string" % (verb,)) + verb = twineGuard.coerce(verb, throw).bare().s + def facet(*a): - return getattr(obj, verb.s)(*a) - return facet + return getattr(obj, verb)(*a) + return Func(facet) makeVerbFacet = MakeVerbFacet() @@ -115,8 +131,7 @@ def extractor(specimen, ejector): return extractor else: def extractor(specimen, ejector): - if not isinstance(specimen, (ConstMap, FlexMap)): - raise RuntimeError("%r is not a map" % (specimen,)) + specimen = mapGuard.coerce(specimen, throw) value = specimen.d.get(x, _absent) if value is _absent: value = ConstList([instead(), specimen]) @@ -135,12 +150,9 @@ def coerce(self, specimen, ej): @deepFrozenFunc def splitList(cut): - if not isinstance(cut, Integer): - raise RuntimeError("%r is not an integer" % (cut,)) - cut = cut.n + cut = intGuard.coerce(cut, throw).n def listSplitter(specimen, ej): - if not isinstance(specimen, (ConstList, FlexList)): - raise RuntimeError("%r is not a list" % (specimen,)) + specimen = listGuard.coerce(specimen, throw) if len(specimen.l) < cut: throw.eject( ej, "A %s size list doesn't match a >= %s size list pattern" @@ -161,8 +173,7 @@ def broken(self): return UnconnectedRef("boolean flow expression failed", self.vat) def failureList(self, size): - if not isinstance(size, Integer): - raise RuntimeError("%r is not an integer" % (size,)) + size = intGuard.coerce(size, throw) return ConstList([false] + [self.broken()] * size.n) @deepFrozenFunc diff --git a/monte/runtime/load.py b/monte/runtime/load.py index 73c8577..92118f5 100644 --- a/monte/runtime/load.py +++ b/monte/runtime/load.py @@ -1,14 +1,22 @@ import linecache, sys, uuid, os from types import ModuleType as module - +from monte import TimeRecorder from monte.compiler import ecompile from monte.expander import expand, scope -from monte.parser import parse +from monte.parser import parseJustModule from monte.runtime.base import MonteObject -from monte.runtime.data import String, null +from monte.runtime.data import String, Twine, null from monte.runtime.tables import ConstList, ConstMap, FlexList, FlexMap +# XXX really should be guards -- but all this code is gonna go away, anyhow. +def typecheck(specimen, classes): + from monte.runtime.ref import _resolution + specimen = _resolution(specimen) + if not isinstance(specimen, classes): + raise RuntimeError("%r is not a %r" % (specimen, classes)) + return specimen + class GeneratedCodeLoader(object): """ Object for use as a module's __loader__, to display generated @@ -19,16 +27,22 @@ def __init__(self, source): def get_source(self, name): return self.source +COMPILE_CACHE = {} + def eval(source, scope=None, origin="__main"): - name = uuid.uuid4().hex + name = origin.encode('ascii') mod = module(name) mod.__name__ = name mod._m_outerScope = scope - pysrc, _, lastline = ecompile(source, scope, origin).rpartition('\n') - pysrc = '\n'.join(["from monte.runtime import compiler_helpers as _monte", - pysrc, - "_m_evalResult = " + lastline]) + if source in COMPILE_CACHE: + pysrc = COMPILE_CACHE[source].encode('ascii') + else: + pysrc, _, lastline = ecompile(source, scope, origin).rpartition('\n') + pysrc = '\n'.join(["from monte.runtime import compiler_helpers as _monte", + pysrc.encode('utf-8'), + "_m_evalResult = " + lastline.encode('utf-8')]) mod.__loader__ = GeneratedCodeLoader(pysrc) + COMPILE_CACHE[source] = pysrc code = compile(pysrc, name, "exec") import __builtin__ __builtin__.eval(code, mod.__dict__) @@ -37,6 +51,23 @@ def eval(source, scope=None, origin="__main"): return mod._m_evalResult +class ModuleMap(ConstMap): + _m_fqn = "ModuleMap" + def __init__(self, configs, d, keys=None): + ConstMap.__init__(self, d, keys=None) + self.configs = configs + + def _m_or(self, behind): + newconfigs = self.configs + getattr(behind, 'configs', ()) + newmap = ConstMap._m_or(self, behind) + return ModuleMap(newconfigs, newmap.d, newmap._keys) + + def getConfigs(self): + return ConstList(tuple(self.configs)) + + # XXX add maker and uncall + + class FileModuleStructure(MonteObject): _m_fqn = "FileModuleStructure" def __init__(self, filename, imports, exports, scope): @@ -53,6 +84,9 @@ def configure(self, params): def run(self, params=None): return self.configure(params).export() + def _printOn(self, out): + out.raw_print("" % (self.filename,)) + class SyntheticModuleStructure(MonteObject): _m_fqn = "SyntheticModuleStructure" @@ -77,15 +111,15 @@ def __init__(self, structure, args, scope): self.structure = structure self.args = args self.scope = scope - self.requires = [] + self.requires = set() for c in args: - self.requires.extend(c.requires) + self.requires.update(c.requires) self._inputs = None self._contents = None def load(self, mapping): - if not isinstance(mapping, (ConstMap, FlexMap)): - raise RuntimeError("must be a mapping") + mapping = typecheck(mapping, (ConstMap, FlexMap)) + # XXX reorganize to be less side-effect-y if self._contents is not None: if self._inputs is not mapping: raise RuntimeError("you are confused somehow") @@ -101,10 +135,22 @@ def load(self, mapping): origin=modname)(*args) self._contents = ConstMap(d) - def export(self): - return ConstMap(dict((String(ex), ConfigurationExport(self, ex)) - for ex in self.structure.exports)) + return ModuleMap((self,), dict((String(ex), ConfigurationExport(self, ex)) + for ex in self.structure.exports)) + + def _printOn(self, out): + out.raw_print(u"<") + out._m_print(self.structure) + out.raw_print(u"([") + if self.structure.imports: + for name in self.structure.imports[:-1]: + out.raw_print(u"%s => " % (name,)) + out._m_print(self.args.d[String(name)]) + out.raw_print(u", ") + out.raw_print(u"%s => " % (self.structure.imports[-1],)) + out._m_print(self.args.d[String(self.structure.imports[-1])]) + out.raw_print(u"])") class ConfigurationExport(MonteObject): @@ -118,12 +164,16 @@ def load(self, mapping): self.config.load(mapping) return self.config._contents.get(String(self.name)) + def _printOn(self, out): + out._m_print(self.config) + out.raw_print(u"::" + self.name) + + def extractArglist(mapping, argnames): argnames = set() for k in mapping._keys: - if not isinstance(k, String): - raise RuntimeError("keys must be strings") - argnames.add(k.s) + k = typecheck(k, Twine).bare().s + argnames.add(k) def compareArglists(loadname, provided, required): @@ -148,8 +198,8 @@ def __init__(self, structure, args): self._inputs = None def load(self, mapping): - if not isinstance(mapping, (ConstMap, FlexMap)): - raise RuntimeError("must be a mapping") + mapping = typecheck(mapping, (ConstMap, FlexMap)) + # XXX reorganize to be less side-effect-y if self._contents is not None: if self._inputs is not mapping: raise RuntimeError("you are confused somehow") @@ -157,24 +207,26 @@ def load(self, mapping): package = {} for k, v in self.structure.config.d.items(): package[k] = v.load(mapping) + + for config in getattr(self.structure.config, 'configs', ()): + config.load(mapping) self._inputs = mapping self._contents = ConstMap(package) def export(self): - return ConstMap(dict((String(ex), ConfigurationExport(self, ex)) - for ex in self.structure.exports)) + return ModuleMap((self,), dict((String(ex), ConfigurationExport(self, ex)) + for ex in self.structure.exports)) class RequireConfiguration(MonteObject): _m_fqn = "Require" def __init__(self, name): self.name = name - self.requires = [name] + self.requires = set([name]) def load(self, mapping): - if not isinstance(mapping, (ConstMap, FlexMap)): - raise RuntimeError("must be a mapping") - return mapping.d[self.name] + mapping = typecheck(mapping, (ConstMap, FlexMap)) + return mapping.d[String(self.name)] def getModuleStructure(name, location, scope, testCollector): @@ -195,7 +247,7 @@ def getModuleStructure(name, location, scope, testCollector): def readModuleFile(moduleFilename): - ast = parse(open(moduleFilename).read()) + ast = parseJustModule(open(moduleFilename).read()) if ast.tag.name != 'Module': raise ValueError("'%s' is not a module" % (moduleFilename,)) imports = [] @@ -224,21 +276,30 @@ def buildPackage(packageDirectory, name, scope, testCollector): class TestCollector(MonteObject): _m_fqn = "TestCollector" - requires = () + requires = set(()) def __init__(self): self.tests = FlexMap({}) def run(self, prefix, tests): - if not isinstance(tests, (ConstList, FlexList)): - raise RuntimeError("must be a list of test functions") + try: + tests = typecheck(tests, (ConstMap, FlexMap)) + except RuntimeError: + pass + else: + self.tests.putAll(tests) + return null + + tests = typecheck(tests, (ConstList, FlexList)) for item in tests.l: - self.tests.put(String(prefix + '.' + item._m_fqn), item) + if prefix and not prefix.endswith('.'): + prefix += '.' + self.tests.put(String(prefix + item._m_fqn.replace('$', '.')), item) return null class TestStructureFacet(MonteObject): _m_fqn = "TestStructureFacet" - requires = () + requires = set(()) def __init__(self, prefix, collector): self.prefix = prefix self.collector = collector @@ -248,7 +309,7 @@ def load(self, args): class TestConfigFacet(MonteObject): _m_fqn = "TestConfigFacet" - requires = () + requires = set(()) def __init__(self, prefix, collector): self.prefix = prefix self.collector = collector @@ -259,7 +320,7 @@ def run(self, tests): class NullTestCollector(MonteObject): _m_fqn = "NullTestCollector" - requires = () + requires = set(()) def load(self, tests): return self def run(self, tests): @@ -275,9 +336,7 @@ def __init__(self, name, root, scope, testCollector): self._testCollector = testCollector def readFiles(self, pathstr): - if not isinstance(pathstr, String): - raise RuntimeError("path must be a string") - path = pathstr.s + path = typecheck(pathstr, Twine).bare().s def collectModules(): root = os.path.join(self.root, path) @@ -297,29 +356,28 @@ def collectModules(): return ConstMap(structures) def readFile(self, pathstr): - if not isinstance(pathstr, String): - raise RuntimeError("path must be a string") - path = pathstr.s + path = typecheck(pathstr, Twine).bare().s fullpath = os.path.join(self.root, path) imports, exports = readModuleFile(fullpath) return FileModuleStructure(fullpath, imports, exports, self.scope) def readPackage(self, subpkgName): - if not isinstance(subpkgName, String): - raise RuntimeError("expected a string") - subpkgPath = subpkgName.s + subpkgPath = typecheck(subpkgName, Twine).bare().s subpkgName = os.path.normpath(subpkgPath) + if self.name: + name = u'.'.join([self.name, subpkgName]) + else: + name = subpkgName subpkg = buildPackage( os.path.join(self.root, subpkgPath), - u'.'.join([self.name, subpkgName]), + name, self.scope, self._testCollector) return subpkg def require(self, name): - if not isinstance(name, String): - raise RuntimeError("name must be a string") - return RequireConfiguration(name.s) + name = typecheck(name, Twine).bare().s + return RequireConfiguration(name) def testCollector(self): if self._testCollector is None: @@ -327,15 +385,13 @@ def testCollector(self): return TestStructureFacet(self.name, self._testCollector) def makeModule(self, mapping): - if not isinstance(mapping, (ConstMap, FlexMap)): - raise RuntimeError("must be a mapping") - requires = [] + mapping = typecheck(mapping, (ConstMap, FlexMap)) + requires = set([]) exports = [] for k in mapping._keys: - if not isinstance(k, String): - raise RuntimeError("keys must be strings") - exports.append(k.s) - requires.extend(mapping.d[k].requires) + k = typecheck(k, Twine) + exports.append(k.bare().s) + requires.update(mapping.d[k].requires) return SyntheticModuleStructure(mapping, requires, exports) @@ -346,7 +402,8 @@ def loader(name, mapping=None): mapping = ConstMap({}) path = os.path.join(os.path.dirname(__file__), '..', 'src') s = getModuleStructure(name, os.path.abspath(path), scope, None) - requires = ConstMap(dict((k, RequireConfiguration(k.s)) for k in mapping.d)) + requires = ConstMap(dict((k, RequireConfiguration(k.bare().s)) + for k in mapping.d)) conf = s.configure(requires) conf.load(mapping) return conf._contents diff --git a/monte/runtime/m.py b/monte/runtime/m.py index 856cb28..40ddf6d 100644 --- a/monte/runtime/m.py +++ b/monte/runtime/m.py @@ -1,16 +1,22 @@ -from monte.runtime.base import MonteObject, toString, toQuote +from monte.runtime.base import MonteObject, toString, toQuote, throw from monte.runtime.data import String from monte.runtime.guards.base import deepFrozenGuard +from monte.runtime.guards.data import twineGuard +from monte.runtime.guards.tables import listGuard class M(MonteObject): _m_fqn = "M" _m_auditorStamps = (deepFrozenGuard,) + def call(self, obj, verb, arglist): - return getattr(obj, verb)(*arglist) + verb = twineGuard.coerce(verb, throw) + arglist = listGuard.coerce(arglist, throw) + return getattr(obj, verb.bare().s)(*arglist.l) def callWithPair(self, obj, (verb, arglist)): - #XXX typecheck - return getattr(obj, verb.s)(*arglist.l) + verb = twineGuard.coerce(verb, throw) + arglist = listGuard.coerce(arglist, throw) + return getattr(obj, verb.bare().s)(*arglist.l) def send(self, obj, verb, arglist): raise NotImplementedError() diff --git a/monte/runtime/ref.py b/monte/runtime/ref.py index 221e427..d4311bc 100644 --- a/monte/runtime/ref.py +++ b/monte/runtime/ref.py @@ -127,7 +127,7 @@ def isFar(self, ref): def whenResolved(self, o, callback): p, r = self.promise() prob = self.vat.sendOnly( - o, String('_whenMoreResolved'), + o, String(u'_whenMoreResolved'), ConstList([_whenResolvedReactor(callback, o, r)])) if prob is not None: return self.broken(prob) @@ -136,13 +136,13 @@ def whenResolved(self, o, callback): def whenResolvedOnly(self, o, callback): p, r = self.promise() return self.vat.sendOnly( - o, String('_whenMoreResolved'), + o, String(u'_whenMoreResolved'), ConstList([_whenResolvedReactor(callback, o, r)])) def whenBroken(self, o, callback): p, r = self.promise() prob = self.vat.sendOnly( - o, String('_whenMoreResolved'), + o, String(u'_whenMoreResolved'), ConstList([_whenBrokenReactor(callback, o, r)])) if prob is not None: return self.broken(prob) @@ -151,7 +151,7 @@ def whenBroken(self, o, callback): def whenBrokenOnly(self, o, callback): p, r = self.promise() return self.vat.sendOnly( - o, String('_whenMoreResolved'), + o, String(u'_whenMoreResolved'), ConstList([_whenBrokenReactor(callback, o, r)])) diff --git a/monte/runtime/scope.py b/monte/runtime/scope.py index 6577589..86a5a0d 100644 --- a/monte/runtime/scope.py +++ b/monte/runtime/scope.py @@ -1,7 +1,9 @@ from monte.runtime.audit import auditedBy from monte.runtime.base import throw from monte.runtime.bindings import reifyBinding, FinalSlot, VarSlot -from monte.runtime.data import (Integer, String, true, false, nan, infinity, null) +from monte.runtime.data import (makeCharacter, makeFloat, makeInteger, String, + true, false, nan, infinity, null, + theTwineMaker, makeSourceSpan) from monte.runtime.equalizer import equalizer from monte.runtime.flow import monteLooper from monte.runtime.guards.base import (anyGuard, deepFrozenGuard, nullOkGuard, @@ -10,7 +12,8 @@ transparentGuard, ParamDesc, MessageDesc, ProtocolDesc) from monte.runtime.guards.data import (booleanGuard, charGuard, intGuard, - floatGuard, stringGuard, voidGuard) + floatGuard, stringGuard, twineGuard, + voidGuard) from monte.runtime.guards.tables import listGuard, mapGuard from monte.runtime.helpers import (accumulateList, accumulateMap, BooleanFlow, comparer, extract, Empty, iterWhile, @@ -24,6 +27,18 @@ from monte.runtime.text import simpleQuasiParser, quasiMatcher from monte.runtime.trace import trace, traceln + +class Func(object): + def __init__(self, f): + self._m_auditorStamps = getattr(f, '_m_auditorStamps', ()) + self.f = f + + def __call__(self, *a, **kw): + return self.f(*a, **kw) + + def run(self, *a, **kw): + return self.f(*a, **kw) + bootScope = { ## Primitive non-literal values 'true': true, @@ -36,7 +51,7 @@ # XXX Create this properly per-vat, when we have vats. 'M': theM, 'throw': throw, - '__loop': monteLooper, + '__loop': Func(monteLooper), ## Primitive reference/object operations # XXX Create this properly per-vat, when we have vats. @@ -48,34 +63,35 @@ "SubrangeGuard": subrangeGuardMaker, ## Primitive: tracing - 'trace': trace, - 'traceln': traceln, + 'trace': Func(trace), + 'traceln': Func(traceln), ## Data constructors '__makeList': makeMonteList, '__makeMap': mapMaker, - '__makeInt': Integer, + '__makeCharacter': Func(makeCharacter), + '__makeInt': Func(makeInteger), + '__makeFloat': Func(makeFloat), '__makeFinalSlot': FinalSlot, '__makeVarSlot': VarSlot, # '__makeCoercedSlot': makeCoercedSlot, # '__makeGuardedSlot': makeGuardedSlot, - # '__makeTwine': makeTwine, + '__makeString': theTwineMaker, # 'term__quasiParser': makeQBuilder, - '__makeOrderedSpace': null, ## Primitive: guards - 'any': anyGuard, - 'void': voidGuard, + 'Any': anyGuard, + 'Void': voidGuard, ## Primitive: atomic data guards - 'boolean': booleanGuard, - 'str': stringGuard, - # 'Twine': twineGuard, + 'Bool': booleanGuard, + 'Str': stringGuard, + 'Twine': twineGuard, # 'TextWriter': textWriterGuard, ## XXX wrap as ordered spaces - 'char': charGuard, - 'float': floatGuard, - 'int': intGuard, + 'Char': charGuard, + 'Double': floatGuard, + 'Int': intGuard, ## data guards # 'all': makeIntersectionGuard, @@ -87,8 +103,8 @@ # 'set': setGuard, ## Protocol/guard constructors - '__makeMessageDesc': MessageDesc, - '__makeParamDesc': ParamDesc, + '__makeMessageDesc': Func(MessageDesc), + '__makeParamDesc': Func(ParamDesc), '__makeProtocolDesc': ProtocolDesc, ## guard meta @@ -98,7 +114,7 @@ ## Utility guards # 'notNull': notNullGuard, - 'nullOk': nullOkGuard, + 'NullOk': nullOkGuard, ## Primitive: reference conditions 'Selfless': selflessGuard, @@ -121,29 +137,33 @@ 'simple__quasiParser': simpleQuasiParser, ## expansion utilities - '__accumulateList': accumulateList, - '__accumulateMap': accumulateMap, - '__bind': makeViaBinder, + '__accumulateList': Func(accumulateList), + '__accumulateMap': Func(accumulateMap), + '__bind': Func(makeViaBinder), #XXX vat '__booleanFlow': BooleanFlow(None), '__comparer': comparer, - '__iterWhile': iterWhile, + '__iterWhile': Func(iterWhile), '__makeVerbFacet': makeVerbFacet, '__mapEmpty': Empty(), - '__mapExtract': extract, - '__matchSame': matchSame, - '__quasiMatcher': quasiMatcher, - '__slotToBinding': reifyBinding, - '__splitList': splitList, - '__suchThat': suchThat, - '__switchFailed': switchFailed, + '__mapExtract': Func(extract), + '__matchSame': Func(matchSame), + '__quasiMatcher': Func(quasiMatcher), + '__slotToBinding': Func(reifyBinding), + '__splitList': Func(splitList), + '__suchThat': Func(suchThat), + '__switchFailed': Func(switchFailed), # '__promiseAllFulfilled': promiseAllFulfilled, - '__validateFor': validateFor, + '__validateFor': Func(validateFor), ## misc # '__identityFunc': identityFunc, 'help': help, + + # move this into something importable + 'makeSourceSpan': Func(makeSourceSpan), + } def createSafeScope(scope): @@ -151,7 +171,7 @@ def createSafeScope(scope): bits = loader(String(u"prim")) scope = scope.copy() for k, v in bits.d.iteritems(): - scope[k.s] = v + scope[k.bare().s] = v return scope # ioScope = { diff --git a/monte/runtime/tables.py b/monte/runtime/tables.py index 453a825..6d7d540 100644 --- a/monte/runtime/tables.py +++ b/monte/runtime/tables.py @@ -1,8 +1,9 @@ -from monte.runtime.base import MonteObject +from monte.runtime.base import MonteObject, ejector, throw from monte.runtime.data import String, Integer, bwrap, null, true, false from monte.runtime.flow import MonteIterator from monte.runtime.guards.base import (deepFrozenFunc, deepFrozenGuard, selflessGuard, transparentStamp) +from monte.runtime.guards.data import intGuard class EListMixin(object): @@ -21,7 +22,7 @@ def _printOn(self, out): out.raw_print(u']') def _makeIterator(self): - return MonteIterator((Integer(i), o) for (i, o) + return MonteIterator(ConstList((Integer(i), o)) for (i, o) in zip(range(len(self.l)), self.l)) def size(self): @@ -31,9 +32,9 @@ def contains(self, item): return bwrap(item in self.l) def add(self, other): - if not isinstance(other, EListMixin): - raise RuntimeError("%r is not a list" % (other,)) - return ConstList(self.l + other.l) + from monte.runtime.guards.tables import listGuard + other = listGuard.coerce(other, throw) + return ConstList(tuple(self.l) + tuple(other.l)) def diverge(self, guard=None): return FlexList(self.l[:], guard) @@ -51,36 +52,28 @@ def last(self): return self.l[-1] def get(self, idx): - if not isinstance(idx, Integer): - raise RuntimeError("%r is not a integer" % (idx,)) + idx = intGuard.coerce(idx, throw) if not 0 <= idx.n < len(self.l): raise IndexError(idx.n) return self.l[idx.n] def slice(self, start, stop=None): - if not isinstance(start, Integer): - raise RuntimeError("%r is not a integer" % (start,)) - start = start.n + start = intGuard.coerce(start, throw).n if stop is not None: - if not isinstance(stop, Integer): - raise RuntimeError("%r is not a integer" % (stop,)) - stop = stop.n + stop = intGuard.coerce(stop, throw).n return ConstList(self.l[start:stop]) def _m_with(self, *a): if len(a) == 1: return ConstList(tuple(self.l) + (a[0],)) elif len(a) == 2: - if not isinstance(a[0], Integer): - raise RuntimeError("%r is not a integer" % (a[0],)) - i = a[0].n + i = intGuard.coerce(a[0], throw).n return ConstList(tuple(self.l[:i]) + (a[1],) + tuple(self.l[i:])) else: raise RuntimeError("with() takes 1 or 2 arguments") def multiply(self, n): - if not isinstance(n, Integer): - raise RuntimeError("%r is not a integer" % (n,)) + n = intGuard.coerce(n, throw) return ConstList(self.l * n.n) def asMap(self): @@ -95,6 +88,9 @@ def asKeys(self): def asSet(self): raise NotImplementedError() + def reversed(self): + return ConstList(reversed(self.l)) + class ConstList(EListMixin, MonteObject): _m_fqn = "__makeList$ConstList" @@ -104,8 +100,8 @@ def __init__(self, l): self.l = tuple(l) def op__cmp(self, other): - if not isinstance(other, ConstList): - raise RuntimeError("%r is not a ConstList" % (other,)) + from monte.runtime.guards.tables import listGuard + other = listGuard.coerce(other, throw) return Integer(cmp(self.l, other.l)) def snapshot(self): @@ -137,8 +133,7 @@ def readOnly(self): return ROList(self.l) def put(self, idx, value): - if not isinstance(idx, Integer): - raise RuntimeError("%r is not a integer" % (idx,)) + idx = intGuard.coerce(idx, throw) if not 0 <= idx.n < len(self.l): raise IndexError(idx) if self.valueGuard is not None: @@ -157,8 +152,8 @@ def push(self, value): return null def extend(self, other): - if not isinstance(other, (ConstList, FlexList)): - raise RuntimeError("%r is not a list" % (other,)) + from monte.runtime.guards.tables import listGuard + contents = listGuard.coerce(other, throw) contents = other.l if self.valueGuard is not None: contents = [self.valueGuard.coerce(x, null) for x in contents] @@ -169,17 +164,14 @@ def pop(self): return self.l.pop() def get(self, index): - if not isinstance(index, Integer): - raise RuntimeError("Expected Integer, got %r" % index) + index = intGuard.coerce(index, throw) return self.l[index.n] def setSlice(self, start, bound, other): - if not isinstance(other, (ConstList, FlexList)): - raise RuntimeError("%r is not a list" % (other,)) - if not isinstance(start, Integer): - raise RuntimeError("%r is not a integer" % (start,)) - if not isinstance(bound, Integer): - raise RuntimeError("%r is not a integer" % (bound,)) + from monte.runtime.guards.tables import listGuard + other = listGuard.coerce(other, throw) + start = intGuard.coerce(start, throw) + bound = intGuard.coerce(bound, throw) if not 0 <= start.n < len(self.l): raise IndexError(start) if not 0 <= bound.n <= len(self.l): @@ -191,9 +183,8 @@ def setSlice(self, start, bound, other): return null def insert(self, idx, value): - if not isinstance(idx, Integer): - raise RuntimeError("%r is not a integer" % (idx,)) - if not 0 <= idx.n < len(self.l): + idx = intGuard.coerce(idx, throw) + if not 0 <= idx.n <= len(self.l): raise IndexError(idx) if self.valueGuard is not None: value = self.valueGuard.coerce(value, null) @@ -201,10 +192,8 @@ def insert(self, idx, value): return null def removeSlice(self, start, bound): - if not isinstance(start, Integer): - raise RuntimeError("%r is not a integer" % (start,)) - if not isinstance(bound, Integer): - raise RuntimeError("%r is not a integer" % (bound,)) + start = intGuard.coerce(start, throw) + bound = intGuard.coerce(bound, throw) if not 0 <= start.n < len(self.l): raise IndexError(start) if not 0 <= bound.n <= len(self.l): @@ -219,11 +208,30 @@ def _uncall(self): return ConstList([ConstList([self.l]), String(u"diverge"), ConstList([])]) def _makeIterator(self): - return MonteIterator((Integer(i), o) for (i, o) in zip(range(len(self.l), self.l))) + return MonteIterator(ConstList((Integer(i), o)) for (i, o) in zip(range(len(self.l)), self.l)) + +class ListMaker(MonteObject): + _m_fqn = "__makeList" + _m_auditorStamps = (deepFrozenGuard,) + + def run(self, *items): + return ConstList(items) + + def fromIterable(self, coll): + items = [] + it = coll._makeIterator() + ej = ejector("iteration") + try: + while True: + key, item = it.next(ej) + items.append(item) + except ej._m_type: + pass + finally: + ej.disable() + return ConstList(items) -@deepFrozenFunc -def makeMonteList(*items): - return ConstList(items) +makeMonteList = ListMaker() _absent = object() @@ -282,11 +290,11 @@ def size(self): return Integer(len(self.d)) def _makeIterator(self): - return MonteIterator((k, self.d[k]) for k in self._keys) + return MonteIterator(ConstList((k, self.d[k])) for k in self._keys) def _m_or(self, behind): - if not isinstance(behind, (ConstMap, FlexMap)): - raise RuntimeError("%r is not a map" % (behind,)) + from monte.runtime.guards.tables import mapGuard + behind = mapGuard.coerce(behind, throw) if len(self.d) == 0: return behind.snapshot() elif len(behind.d) == 0: @@ -296,8 +304,8 @@ def _m_or(self, behind): return flex.snapshot() def _m_and(self, mask): - if not isinstance(mask, (ConstMap, FlexMap)): - raise RuntimeError("%r is not a map" % (mask,)) + from monte.runtime.guards.tables import mapGuard + mask = mapGuard.coerce(mask, throw) if len(self.d) > len(mask.d): bigger = self smaller = mask @@ -314,8 +322,8 @@ def _m_and(self, mask): return flex.snapshot() def butNot(self, mask): - if not isinstance(mask, (ConstMap, FlexMap)): - raise RuntimeError("%r is not a map" % (mask,)) + from monte.runtime.guards.tables import mapGuard + mask = mapGuard.coerce(mask, throw) if len(self.d) == 0: return ConstMap({}) elif len(mask.d) == 0: @@ -408,8 +416,8 @@ def domain(self): raise NotImplementedError() def removeKeys(self, mask): - if not isinstance(mask, (ConstMap, FlexMap)): - raise RuntimeError("%r is not a map" % (mask,)) + from monte.runtime.guards.tables import mapGuard + mask = mapGuard.coerce(mask, throw) for k in mask._keys: self.removeKey(k) @@ -435,8 +443,8 @@ def put(self, k, v): self._keys.append(k) def putAll(self, other): - if not isinstance(other, (ConstMap, FlexMap)): - raise RuntimeError("%r is not a map" % (other,)) + from monte.runtime.guards.tables import mapGuard + other = mapGuard.coerce(other, throw) for k in other._keys: self.put(k, other.d[k]) @@ -449,12 +457,14 @@ class mapMaker(object): _m_auditorStamps = (deepFrozenGuard,) @staticmethod def fromPairs(pairs): - return ConstMap(dict(p for (i, p) in pairs._makeIterator()), [p.get(Integer(0)) for p in pairs]) + from monte.runtime.guards.tables import listGuard + return ConstMap(dict(listGuard.coerce(p.get(Integer(1)), null).l + for p in pairs._makeIterator()), + [p.get(Integer(1)).get(Integer(0)) for p in pairs._makeIterator()]) @staticmethod def fromColumns(keys, vals): - if not isinstance(keys, (ConstList, FlexList)): - raise RuntimeError("%r is not a list" % (keys,)) - if not isinstance(vals, (ConstList, FlexList)): - raise RuntimeError("%r is not a list" % (vals,)) + from monte.runtime.guards.tables import listGuard + keys = listGuard.coerce(keys, throw) + vals = listGuard.coerce(vals, throw) return ConstMap(dict(zip(keys.l, vals.l)), keys) diff --git a/monte/runtime/text.py b/monte/runtime/text.py index 2a69006..5d1f4ff 100644 --- a/monte/runtime/text.py +++ b/monte/runtime/text.py @@ -1,6 +1,9 @@ from monte.runtime.base import MonteObject, throw, toString -from monte.runtime.data import String, Character +from monte.runtime.data import String, Twine, Character +from monte.runtime.ref import _resolution from monte.runtime.guards.base import deepFrozenGuard +from monte.runtime.guards.data import twineGuard +from monte.runtime.guards.tables import listGuard from monte.runtime.tables import ConstList, FlexList def findOneOf(elts, specimen, start): @@ -9,113 +12,87 @@ def findOneOf(elts, specimen, start): return i + start return -1 +LITERAL, VALUE_HOLE, PATTERN_HOLE = object(), object(), object() + class Substituter(MonteObject): _m_fqn = "simple__quasiParser$Substituter" _m_auditorStamps = (deepFrozenGuard,) def __init__(self, template): - if not isinstance(template, String): - raise RuntimeError("%r is not a string" % (template,)) - self.template = template.s self.segments = segs = [] - last = 0 - i = 0 - while i < len(self.template): - i = findOneOf('$@', self.template, last) - if i == -1: - # No more QL values or patterns; just go ahead and package up - # the last segment if it exists. - if last < len(self.template): - segs.append(('literal', self.template[last:])) - break - if self.template[i + 1] == self.template[i]: - segs.append(('literal', self.template[last:i])) - last = i - elif self.template[i + 1] != '{': - i -= 1 + for seg in template: + seg = _resolution(seg) + if isinstance(seg, Twine): + segs.append((LITERAL, seg.bare().s)) else: - if last != i and last < len(self.template) - 1: - segs.append(('literal', self.template[last:i])) - last = i - if self.template[i] == '@': - typ = 'pattern' - else: - typ = 'value' - i += 2 - sub = i - while True: - i += 1 - c = self.template[i] - if c == '}': - break - elif not c.isdigit(): - raise RuntimeError("Missing '}'", self.template) - segs.append((typ, int(self.template[sub:i]))) - last = i + 1 + segs.append(seg) def substitute(self, values): - if not isinstance(values, (ConstList, FlexList)): - raise RuntimeError("%r is not a list" % (values,)) + values = listGuard.coerce(values, throw) return String(u"".join(self._sub(values.l))) def _sub(self, values): for typ, val in self.segments: - if typ == 'literal': + if typ is LITERAL: yield val - elif typ == 'value': + elif typ is VALUE_HOLE: yield toString(values[val]) else: raise RuntimeError("Can't substitute with a pattern") def matchBind(self, values, specimen, ej): #XXX maybe put this on a different object? - if not isinstance(specimen, String): - raise RuntimeError("%r is not a string" % (specimen,)) - if not isinstance(values, (ConstList, FlexList)): - raise RuntimeError("%r is not a list" % (values,)) - specimen = specimen.s + specimen = twineGuard.coerce(specimen, ej).bare().s + values = listGuard.coerce(values, throw) values = values.l i = 0 bindings = [] for n in range(len(self.segments)): typ, val = self.segments[n] - if typ == 'literal': + if typ is LITERAL: j = i + len(val) if specimen[i:j] != val: throw.eject(ej, "expected %r..., found %r" % ( val, specimen[i:j])) - elif typ == 'value': + elif typ is VALUE_HOLE: s = values[val] - if not isinstance(s, String): - raise RuntimeError("%r is not a string" % (s,)) - s = s.s + s = twineGuard.coerce(s, throw).bare().s j = i + len(s) if specimen[i:j] != s: throw.eject(ej, "expected %r... ($-hole %s), found %r" % ( s, val, specimen[i:j])) - elif typ == 'pattern': + elif typ is PATTERN_HOLE: nextVal = None if n == len(self.segments) - 1: bindings.append(String(specimen[i:])) + i = len(specimen) continue nextType, nextVal = self.segments[n + 1] - if nextType == 'value': - nextVal = values[nextVal] - if not isinstance(nextVal, String): - raise RuntimeError("%r is not a string" % (nextVal,)) - nextVal = nextVal.s - elif nextType == 'pattern': + if nextType is VALUE_HOLE: + nextVal = twineGuard.coerce(values[nextVal], throw).bare().s + elif nextType is PATTERN_HOLE: bindings.append(String(u"")) continue - j = specimen.find(nextVal) + j = specimen.find(nextVal, i) if j == -1: - throw.eject(ej, "expected %r..., found %r" % (nextVal.s, specimen[i:])) + throw.eject(ej, "expected %r..., found %r" % ( + nextVal, + specimen[i:])) bindings.append(String(specimen[i:j])) i = j - return ConstList(bindings) + + if (i == len(specimen)): + return ConstList(bindings) + throw.eject(ej, "Excess unmatched: " + specimen[i:]) class SimpleQuasiParser(MonteObject): _m_fqn = "simple__quasiParser" _m_auditorStamps = (deepFrozenGuard,) + def valueHole(self, n): + return (VALUE_HOLE, n.n) + + def patternHole(self, n): + return (PATTERN_HOLE, n.n) + def valueMaker(self, template): return Substituter(template) @@ -127,19 +104,21 @@ def matchMaker(self, template): def quasiMatcher(matchMaker, values): def matchit(specimen, ej): return matchMaker.matchBind(values, specimen, ej) - return matchit + from monte.runtime.scope import Func + return Func(matchit) class TextWriter(MonteObject): - def __init__(self, out, newline=u'\n', context=None): + def __init__(self, out, newline=String(u'\n'), context=None): self.out = out self.context = context or set() self.newline = newline def indent(self, morePrefix): - return TextWriter(self.out, self.newline + u' ' * 4, self.context) + return TextWriter(self.out, self.newline.add(morePrefix), self.context) def quote(self, obj): + obj = _resolution(obj) if isinstance(obj, (String, Character)): self._m_print(obj.quote()) else: @@ -172,4 +151,8 @@ def _m_print(self, obj): def println(self, obj): self._m_print(obj) - self.raw_print(self.newline) + self._m_print(self.newline) + + def lnPrint(self, obj): + self._m_print(self.newline) + self._m_print(obj) diff --git a/monte/src/examples/imports.mt b/monte/src/examples/imports.mt index 6158282..e1a8109 100644 --- a/monte/src/examples/imports.mt +++ b/monte/src/examples/imports.mt @@ -1,4 +1,4 @@ -def [makeFoo, mkB, baz] := import("examples/module") +def [=> makeFoo, "makeBar" => mkB, => Baz] := import("examples/module", ["magicNumber" => 42]) traceln("Imported some things. Let's make them do stuff.") @@ -8,4 +8,4 @@ oof.doSomething() def bar := mkB() bar.doSomething() -baz.doSomething() +Baz.doSomething() diff --git a/monte/src/examples/module.mt b/monte/src/examples/module.mt index 4e1dbb6..10cc92a 100644 --- a/monte/src/examples/module.mt +++ b/monte/src/examples/module.mt @@ -1,7 +1,9 @@ +module magicNumber +export (makeFoo, makeBar, Baz) def makeFoo(title): return object Foo: to doSomething(): - traceln(`Hi, I'm a Foo called $title`) + traceln(`Hi, I'm a Foo called $title. The magic number is $magicNumber`) def makeBar(): return object Bar: @@ -11,5 +13,3 @@ def makeBar(): object Baz: to doSomething(): traceln("Beep!") - -[makeFoo, makeBar, Baz] diff --git a/monte/src/examples/sealing.mt b/monte/src/examples/sealing.mt new file mode 100644 index 0000000..360d5a4 --- /dev/null +++ b/monte/src/examples/sealing.mt @@ -0,0 +1,66 @@ +module unittest +export (makeBrandPair) + +/** makeBrandPair -- Rights Amplification + cf http://www.erights.org/elib/capability/ode/ode-capabilities.html#rights-amp + cribbed from wiki.erights.org/wiki/Walnut/Secure_Distributed_Computing/Capability_Patterns#Sealers_and_Unsealers + */ +def makeBrandPair(nickname): + object noObject: + pass + + var shared := noObject + + def makeSealedBox(obj): + object box: + to shareContent(): + shared := obj + to _printOn(t): + t.print(`<$nickname sealed box>`) + return box + + object sealer: + to seal(obj): + return makeSealedBox(obj) + + to _printOn(t): + t.print(`<$nickname sealer>`) + + object unsealer: + to unseal(box): + shared := noObject + box.shareContent() + if (shared == noObject): + throw("invalid box") + def contents := shared + shared := noObject + return contents + to _printOn(t): + t.print(`<$nickname unsealer>`) + + return [sealer, unsealer] + + +def t(assert): + def happy(): + def [s, u] := makeBrandPair("bob") + assert.equal(`$s`, "") + assert.equal(`$u`, "") + + def x := s.seal("abc") + assert.equal(`$x`, "") + assert.equal(u.unseal(x), "abc") + + def evil(): + def [s, u] := makeBrandPair("bob") + def x := s.seal("abc") + + def [ss, uu] := makeBrandPair("evil") + + assert.raises(def _(fail){ + uu.unseal(x) + }) + + return [happy, evil] + +unittest([t]) diff --git a/monte/src/examples/ternary.mt b/monte/src/examples/ternary.mt new file mode 100644 index 0000000..54d2ffd --- /dev/null +++ b/monte/src/examples/ternary.mt @@ -0,0 +1,7 @@ +def even(i): + return if (i % 2 == 0) { "yes" } else { "no"} + +traceln("is 23 even?") +traceln(even(23)) +traceln("is 42 even?") +traceln(even(42)) diff --git a/monte/src/examples/testing.mt b/monte/src/examples/testing.mt index 73c1688..a8fac6f 100644 --- a/monte/src/examples/testing.mt +++ b/monte/src/examples/testing.mt @@ -1,10 +1,10 @@ module unittest export (makeMaths) -def makeMaths(a :int, b :int): +def makeMaths(a :Int, b :Int): return object Maths: - to add() :int: + to add() :Int: return a + b - to subtract() :int: + to subtract() :Int: return a - b def mathTests(assert): diff --git a/monte/src/heap.mt b/monte/src/heap.mt index 3423f14..27eec29 100644 --- a/monte/src/heap.mt +++ b/monte/src/heap.mt @@ -53,13 +53,13 @@ def makeHeap(contents) :Heap: ret.push(heap.pop()) return ret.snapshot() - to visualize() :str: + to visualize() :Str: var out := "" for v in storage: out += `$v ` return out - to checkInvariant() :boolean: + to checkInvariant() :Bool: var errors := 0 for [i, v] in enumerate(storage): def c1i := 2 * i + 1 diff --git a/monte/src/monte_ast.mt b/monte/src/monte_ast.mt new file mode 100644 index 0000000..4e1055b --- /dev/null +++ b/monte/src/monte_ast.mt @@ -0,0 +1,2741 @@ +module unittest +export (astBuilder) + +def MONTE_KEYWORDS := [ +"as", "bind", "break", "catch", "continue", "def", "else", "escape", +"exit", "extends", "export", "finally", "fn", "for", "guards", "if", +"implements", "in", "interface", "match", "meta", "method", "module", +"object", "pass", "pragma", "return", "switch", "to", "try", "var", +"via", "when", "while", "_"] + +def idStart := 'a'..'z' | 'A'..'Z' | '_'..'_' +def idPart := idStart | '0'..'9' +def INDENT := " " +# note to future drunk self: lower precedence number means add parens when +# inside a higher-precedence-number expression +def priorities := [ + "indentExpr" => 0, + "braceExpr" => 1, + "assign" => 2, + "logicalOr" => 3, + "logicalAnd" => 4, + "comp" => 5, + "order" => 6, + "interval" => 7, + "shift" => 8, + "addsub" => 9, + "divmul" => 10, + "pow" => 11, + "prefix" => 12, + "send" => 13, + "coerce" => 14, + "call" => 15, + "prim" => 16, + + "pattern" => 0] + +object makeScopeSet: + to run(items): + def map := [].asMap().diverge() + for k in items: + map[k] := null + return makeScopeSet.fromKeys(map.snapshot()) + to fromKeys(map): + return object scopeset extends map: + to _makeIterator(): + return super.getKeys()._makeIterator() + to contains(k): + return super.maps(k) + to subtract(right): + def new := super.diverge() + for k in right: + if (super.maps(k)): + new.removeKey(k) + return makeScopeSet.fromKeys(new.snapshot()) + to and(right): + return makeScopeSet.fromKeys(super.and(right)) + to or(right): + return makeScopeSet.fromKeys(super.or(right)) + to _conformTo(guard): + return super + to printOn(out): + out.print(super.getKeys()) + +def makeStaticScope(read, set, defs, vars, metaStateExpr): + def namesRead := makeScopeSet(read) + def namesSet := makeScopeSet(set) + def defNames := makeScopeSet(defs) + def varNames := makeScopeSet(vars) + return object staticScope: + to getNamesRead(): + return namesRead + + to getNamesSet(): + return namesSet + + to getDefNames(): + return defNames + + to getVarNames(): + return varNames + + to getMetaStateExprFlag(): + return metaStateExpr + + to hide(): + return makeStaticScope(namesRead, namesSet, [], [], + metaStateExpr) + + to add(right): + if (right == null): + return staticScope + def rightNamesRead := (right.getNamesRead() - defNames) - varNames + def rightNamesSet := right.getNamesSet() - varNames + def badAssigns := rightNamesSet & defNames + if (badAssigns.size() > 0): + throw(`Can't assign to final nouns ${badAssigns}`) + return makeStaticScope(namesRead | rightNamesRead, + namesSet | rightNamesSet, + defNames | right.getDefNames(), + varNames | right.getVarNames(), + metaStateExpr | right.getMetaStateExprFlag()) + to namesUsed(): + return namesRead | namesSet + + to outNames(): + return defNames | varNames + + to printOn(out): + out.print("<") + out.print(namesSet) + out.print(" := ") + out.print(namesRead) + out.print(" =~ ") + out.print(defNames) + out.print(" + var ") + out.print(varNames) + out.print(" ") + out.print(metaStateExpr) + out.print(">") + +def emptyScope := makeStaticScope([], [], [], [], false) + +def sumScopes(nodes): + var result := emptyScope + for node in nodes: + if (node != null): + result += node.getStaticScope() + return result + +def scopeMaybe(optNode): + if (optNode == null): + return emptyScope + return optNode.getStaticScope() + +def all(iterable, pred): + for item in iterable: + if (!pred(item)): + return false + return true + +def maybeTransform(node, f): + if (node == null): + return null + return node.transform(f) + +def transformAll(nodes, f): + def results := [].diverge() + for n in nodes: + results.push(n.transform(f)) + return results.snapshot() + +def isIdentifier(name): + if (MONTE_KEYWORDS.contains(name.toLowerCase())): + return false + return idStart(name[0]) && all(name.slice(1), idPart) + +def printListOn(left, nodes, sep, right, out, priority): + out.print(left) + if (nodes.size() >= 1): + for n in nodes.slice(0, nodes.size() - 1): + n.subPrintOn(out, priority) + out.print(sep) + nodes.last().subPrintOn(out, priority) + out.print(right) + +def printDocstringOn(docstring, out, indentLastLine): + if (docstring == null): + if (indentLastLine): + out.println("") + return + out.lnPrint("\"") + def lines := docstring.split("\n") + for line in lines.slice(0, 0.max(lines.size() - 2)): + out.println(line) + if (lines.size() > 0): + out.print(lines.last()) + if (indentLastLine): + out.println("\"") + else: + out.print("\"") + +def printSuiteOn(leaderFn, printContents, cuddle, noLeaderNewline, out, priority): + def indentOut := out.indent(INDENT) + if (priorities["braceExpr"] <= priority): + if (cuddle): + out.print(" ") + leaderFn() + if (noLeaderNewline): + indentOut.print(" {") + else: + indentOut.println(" {") + printContents(indentOut, priorities["braceExpr"]) + out.println("") + out.print("}") + else: + if (cuddle): + out.println("") + leaderFn() + if (noLeaderNewline): + indentOut.print(":") + else: + indentOut.println(":") + printContents(indentOut, priorities["indentExpr"]) + +def printExprSuiteOn(leaderFn, suite, cuddle, out, priority): + printSuiteOn(leaderFn, suite.subPrintOn, cuddle, false, out, priority) + +def printDocExprSuiteOn(leaderFn, docstring, suite, out, priority): + printSuiteOn(leaderFn, fn o, p { + printDocstringOn(docstring, o, true) + suite.subPrintOn(o, p) + }, false, true, out, priority) + +def printObjectSuiteOn(leaderFn, docstring, suite, out, priority): + printSuiteOn(leaderFn, fn o, p { + printDocstringOn(docstring, o, false) + suite.subPrintOn(o, p) + }, false, true, out, priority) + +def astWrapper(node, maker, args, span, scope, termFunctor, transformArgs): + return object astNode extends node: + to getStaticScope(): + return scope + to getSpan(): + return span + to getNodeName(): + return termFunctor.getTag().getName() + to asTerm(): + def termit(subnode, maker, args, span): + return subnode.asTerm() + return term`$termFunctor(${transformArgs(termit)}*)`.withSpan(span) + to transform(f): + return f(astNode, maker, transformArgs(f), span) + to _uncall(): + return [maker, "run", args + [span]] + to _printOn(out): + astNode.subPrintOn(out, 0) + +def makeLiteralExpr(value, span): + object literalExpr: + to getValue(): + return value + to subPrintOn(out, priority): + out.quote(value) + return astWrapper(literalExpr, makeLiteralExpr, [value], span, + emptyScope, term`LiteralExpr`, fn f {[value]}) + +def makeNounExpr(name, span): + def scope := makeStaticScope([name], [], [], [], false) + object nounExpr: + to getName(): + return name + to subPrintOn(out, priority): + if (isIdentifier(name)): + out.print(name) + else: + out.print("::") + out.quote(name) + return astWrapper(nounExpr, makeNounExpr, [name], span, + scope, term`NounExpr`, fn f {[name]}) + +def makeTempNounExpr(namePrefix, span): + object name extends namePrefix: + to _printOn(out): + out.print("$") + def scope := makeStaticScope([name], [], [], [], false) + object tempNounExpr: + to getName(): + return namePrefix + to subPrintOn(out, priority): + out.print(name) + return astWrapper(tempNounExpr, makeTempNounExpr, [name], span, + scope, term`TempNounExpr`, fn f {[namePrefix]}) + +def makeSlotExpr(noun, span): + def scope := noun.getStaticScope() + object slotExpr: + to getNoun(): + return noun + to subPrintOn(out, priority): + out.print("&") + out.print(noun) + return astWrapper(slotExpr, makeSlotExpr, [noun], span, + scope, term`SlotExpr`, fn f {[noun.transform(f)]}) + +def makeMetaContextExpr(span): + def scope := emptyScope + object metaContextExpr: + to subPrintOn(out, priority): + out.print("meta.context()") + return astWrapper(metaContextExpr, makeMetaContextExpr, [], span, + scope, term`MetaContextExpr`, fn f {[]}) + +def makeMetaStateExpr(span): + def scope := makeStaticScope([], [], [], [], true) + object metaStateExpr: + to subPrintOn(out, priority): + out.print("meta.getState()") + return astWrapper(metaStateExpr, makeMetaStateExpr, [], span, + scope, term`MetaStateExpr`, fn f {[]}) + +def makeBindingExpr(noun, span): + def scope := noun.getStaticScope() + object bindingExpr: + to getNoun(): + return noun + to subPrintOn(out, priority): + out.print("&&") + out.print(noun) + return astWrapper(bindingExpr, makeBindingExpr, [noun], span, + scope, term`BindingExpr`, fn f {[noun.transform(f)]}) + +def makeSeqExpr(exprs, span): + def scope := sumScopes(exprs) + object seqExpr: + to getExprs(): + return exprs + to subPrintOn(out, priority): + if (priority > priorities["braceExpr"]): + out.print("(") + var first := true + if (priorities["braceExpr"] >= priority && exprs == []): + out.print("pass") + for e in exprs: + if (!first): + out.println("") + first := false + e.subPrintOn(out, priority.min(priorities["braceExpr"])) + return astWrapper(seqExpr, makeSeqExpr, [exprs], span, + scope, term`SeqExpr`, fn f {[transformAll(exprs, f)]}) + +def makeModule(imports, exports, body, span): + def scope := sumScopes(imports + exports) + object ::"module": + to getImports(): + return imports + to getExports(): + return exports + to getBody(): + return body + to subPrintOn(out, priority): + out.print("module") + if (imports.size() > 0): + out.print(" ") + printListOn("", imports, ", ", "", out, priorities["braceExpr"]) + out.println("") + if (exports.size() > 0): + out.print("export ") + printListOn("(", exports, ", ", ")", out, priorities["braceExpr"]) + out.println("") + body.subPrintOn(out, priorities["indentExpr"]) + return astWrapper(::"module", makeModule, [imports, exports, body], span, + scope, term`Module`, fn f {[ + transformAll(imports, f), + transformAll(exports, f), + body.transform(f)]}) + +def makeMethodCallExpr(rcvr, verb, arglist, span): + def scope := sumScopes(arglist + [rcvr]) + object methodCallExpr: + to getReceiver(): + return rcvr + to getVerb(): + return verb + to getArgs(): + return arglist + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + rcvr.subPrintOn(out, priorities["call"]) + out.print(".") + if (isIdentifier(verb)): + out.print(verb) + else: + out.quote(verb) + printListOn("(", arglist, ", ", ")", out, priorities["braceExpr"]) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(methodCallExpr, makeMethodCallExpr, + [rcvr, verb, arglist], span, scope, term`MethodCallExpr`, + fn f {[rcvr.transform(f), verb, transformAll(arglist, f)]}) + +def makeFunCallExpr(receiver, args, span): + def scope := sumScopes(args + [receiver]) + object funCallExpr: + to getReceiver(): + return receiver + to getArgs(): + return args + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + receiver.subPrintOn(out, priorities["call"]) + printListOn("(", args, ", ", ")", out, priorities["braceExpr"]) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(funCallExpr, makeFunCallExpr, [receiver, args], span, + scope, term`FunCallExpr`, fn f {[receiver.transform(f), transformAll(args, f)]}) + +def makeSendExpr(rcvr, verb, arglist, span): + def scope := sumScopes(arglist + [rcvr]) + object sendExpr: + to getReceiver(): + return rcvr + to getVerb(): + return verb + to getArgs(): + return arglist + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + rcvr.subPrintOn(out, priorities["call"]) + out.print(" <- ") + if (isIdentifier(verb)): + out.print(verb) + else: + out.quote(verb) + printListOn("(", arglist, ", ", ")", out, priorities["braceExpr"]) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(sendExpr, makeSendExpr, + [rcvr, verb, arglist], span, scope, term`SendExpr`, + fn f {[rcvr.transform(f), verb, transformAll(arglist, f)]}) + +def makeFunSendExpr(receiver, args, span): + def scope := sumScopes(args + [receiver]) + object funSendExpr: + to getReceiver(): + return receiver + to getArgs(): + return args + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + receiver.subPrintOn(out, priorities["call"]) + printListOn(" <- (", args, ", ", ")", out, priorities["braceExpr"]) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(funSendExpr, makeFunSendExpr, [receiver, args], span, + scope, term`FunSendExpr`, fn f {[receiver.transform(f), transformAll(args, f)]}) + +def makeGetExpr(receiver, indices, span): + def scope := sumScopes(indices + [receiver]) + object getExpr: + to getReceiver(): + return receiver + to getIndices(): + return indices + to subPrintOn(out, priority): + receiver.subPrintOn(out, priorities["call"]) + printListOn("[", indices, ", ", "]", out, priorities["braceExpr"]) + + return astWrapper(getExpr, makeGetExpr, [receiver, indices], span, + scope, term`GetExpr`, fn f {[receiver.transform(f), transformAll(indices, f)]}) + +def makeAndExpr(left, right, span): + def scope := left.getStaticScope() + right.getStaticScope() + object andExpr: + to getLeft(): + return left + to getRight(): + return right + to subPrintOn(out, priority): + if (priorities["logicalAnd"] < priority): + out.print("(") + left.subPrintOn(out, priorities["logicalAnd"]) + out.print(" && ") + right.subPrintOn(out, priorities["logicalAnd"]) + if (priorities["logicalAnd"] < priority): + out.print(")") + return astWrapper(andExpr, makeAndExpr, [left, right], span, + scope, term`AndExpr`, fn f {[left.transform(f), right.transform(f)]}) + +def makeOrExpr(left, right, span): + def scope := left.getStaticScope() + right.getStaticScope() + object orExpr: + to getLeft(): + return left + to getRight(): + return right + to subPrintOn(out, priority): + if (priorities["logicalOr"] < priority): + out.print("(") + left.subPrintOn(out, priorities["logicalOr"]) + out.print(" || ") + right.subPrintOn(out, priorities["logicalOr"]) + if (priorities["logicalOr"] < priority): + out.print(")") + return astWrapper(orExpr, makeOrExpr, [left, right], span, + scope, term`OrExpr`, fn f {[left.transform(f), right.transform(f)]}) + +def operatorsToNamePrio := [ + "+" => ["add", "addsub"], + "-" => ["subtract", "addsub"], + "*" => ["multiply", "divmul"], + "//" => ["floorDivide", "divmul"], + "/" => ["approxDivide", "divmul"], + "%" => ["mod", "divmul"], + "**" => ["pow", "pow"], + "&" => ["and", "comp"], + "|" => ["or", "comp"], + "^" => ["xor", "comp"], + "&!" => ["butNot", "comp"], + "<<" => ["shiftLeft", "comp"], + ">>" => ["shiftRight", "comp"]] + +def makeBinaryExpr(left, op, right, span): + def scope := left.getStaticScope() + right.getStaticScope() + object binaryExpr: + to getLeft(): + return left + to getOp(): + return op + to getOpName(): + return operatorsToNamePrio[op][0] + to getRight(): + return right + to subPrintOn(out, priority): + def opPrio := priorities[operatorsToNamePrio[op][1]] + if (opPrio < priority): + out.print("(") + left.subPrintOn(out, opPrio) + out.print(" ") + out.print(op) + out.print(" ") + right.subPrintOn(out, opPrio) + if (opPrio < priority): + out.print(")") + return astWrapper(binaryExpr, makeBinaryExpr, [left, op, right], span, + scope, term`BinaryExpr`, fn f {[left.transform(f), op, right.transform(f)]}) + +def comparatorsToName := [ + ">" => "greaterThan", "<" => "lessThan", + ">=" => "geq", "<=" => "leq", + "<=>" => "asBigAs"] + +def makeCompareExpr(left, op, right, span): + def scope := left.getStaticScope() + right.getStaticScope() + object compareExpr: + to getLeft(): + return left + to getOp(): + return op + to getOpName(): + return comparatorsToName[op] + to getRight(): + return right + to subPrintOn(out, priority): + if (priorities["comp"] < priority): + out.print("(") + left.subPrintOn(out, priorities["comp"]) + out.print(" ") + out.print(op) + out.print(" ") + right.subPrintOn(out, priorities["comp"]) + if (priorities["comp"] < priority): + out.print(")") + return astWrapper(compareExpr, makeCompareExpr, [left, op, right], span, + scope, term`CompareExpr`, fn f {[left.transform(f), op, right.transform(f)]}) + +def makeRangeExpr(left, op, right, span): + def scope := left.getStaticScope() + right.getStaticScope() + object rangeExpr: + to getLeft(): + return left + to getOp(): + return op + to getOpName(): + if (op == ".."): + return "thru" + else if (op == "..!"): + return "till" + to getRight(): + return right + to subPrintOn(out, priority): + if (priorities["interval"] < priority): + out.print("(") + left.subPrintOn(out, priorities["interval"]) + out.print(op) + right.subPrintOn(out, priorities["interval"]) + if (priorities["interval"] < priority): + out.print(")") + return astWrapper(rangeExpr, makeRangeExpr, [left, op, right], span, + scope, term`RangeExpr`, fn f {[left.transform(f), op, right.transform(f)]}) + +def makeSameExpr(left, right, direction, span): + def scope := left.getStaticScope() + right.getStaticScope() + object sameExpr: + to getLeft(): + return left + to getDirection(): + return direction + to getRight(): + return right + to subPrintOn(out, priority): + if (priorities["comp"] < priority): + out.print("(") + left.subPrintOn(out, priorities["comp"]) + if (direction): + out.print(" == ") + else: + out.print(" != ") + right.subPrintOn(out, priorities["comp"]) + if (priorities["comp"] < priority): + out.print(")") + return astWrapper(sameExpr, makeSameExpr, [left, right, direction], span, + scope, term`SameExpr`, fn f {[left.transform(f), right.transform(f), direction]}) + +def makeMatchBindExpr(specimen, pattern, span): + def scope := specimen.getStaticScope() + pattern.getStaticScope() + object matchBindExpr: + to getSpecimen(): + return specimen + to getPattern(): + return pattern + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + specimen.subPrintOn(out, priorities["call"]) + out.print(" =~ ") + pattern.subPrintOn(out, priorities["pattern"]) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(matchBindExpr, makeMatchBindExpr, [specimen, pattern], span, + scope, term`MatchBindExpr`, fn f {[specimen.transform(f), pattern.transform(f)]}) + +def makeMismatchExpr(specimen, pattern, span): + def scope := specimen.getStaticScope() + pattern.getStaticScope() + object mismatchExpr: + to getSpecimen(): + return specimen + to getPattern(): + return pattern + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + specimen.subPrintOn(out, priorities["call"]) + out.print(" !~ ") + pattern.subPrintOn(out, priorities["pattern"]) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(mismatchExpr, makeMismatchExpr, [specimen, pattern], span, + scope, term`MismatchExpr`, fn f {[specimen.transform(f), pattern.transform(f)]}) + +def unaryOperatorsToName := ["~" => "complement", "!" => "not", "-" => "negate"] + +def makePrefixExpr(op, receiver, span): + def scope := receiver.getStaticScope() + object prefixExpr: + to getOp(): + return op + to getOpName(): + return unaryOperatorsToName[op] + to getReceiver(): + return receiver + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + out.print(op) + receiver.subPrintOn(out, priorities["call"]) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(prefixExpr, makePrefixExpr, [op, receiver], span, + scope, term`PrefixExpr`, fn f {[op, receiver.transform(f)]}) + +def makeCoerceExpr(specimen, guard, span): + def scope := specimen.getStaticScope() + guard.getStaticScope() + object coerceExpr: + to getSpecimen(): + return specimen + to getGuard(): + return guard + to subPrintOn(out, priority): + if (priorities["coerce"] < priority): + out.print("(") + specimen.subPrintOn(out, priorities["coerce"]) + out.print(" :") + guard.subPrintOn(out, priorities["prim"]) + if (priorities["coerce"] < priority): + out.print(")") + return astWrapper(coerceExpr, makeCoerceExpr, [specimen, guard], span, + scope, term`CoerceExpr`, fn f {[specimen.transform(f), guard.transform(f)]}) + +def makeCurryExpr(receiver, verb, isSend, span): + def scope := receiver.getStaticScope() + object curryExpr: + to getReceiver(): + return receiver + to getVerb(): + return verb + to getIsSend(): + return isSend + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + receiver.subPrintOn(out, priorities["call"]) + if (isSend): + out.print(" <- ") + else: + out.print(".") + if (isIdentifier(verb)): + out.print(verb) + else: + out.quote(verb) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(curryExpr, makeCurryExpr, [receiver, verb, isSend], span, + scope, term`CurryExpr`, fn f {[receiver.transform(f), verb, isSend]}) + +def makeExitExpr(name, value, span): + def scope := scopeMaybe(value) + object exitExpr: + to getName(): + return name + to getValue(): + return value + to subPrintOn(out, priority): + if (priorities["call"] < priority): + out.print("(") + out.print(name) + if (value != null): + out.print(" ") + value.subPrintOn(out, priority) + if (priorities["call"] < priority): + out.print(")") + return astWrapper(exitExpr, makeExitExpr, [name, value], span, + scope, term`ExitExpr`, fn f {[name, maybeTransform(value, f)]}) + +def makeForwardExpr(patt, span): + def scope := patt.getStaticScope() + object forwardExpr: + to getNoun(): + return patt.getNoun() + to subPrintOn(out, priority): + if (priorities["assign"] < priority): + out.print("(") + out.print("def ") + patt.subPrintOn(out, priorities["prim"]) + if (priorities["assign"] < priority): + out.print(")") + return astWrapper(forwardExpr, makeForwardExpr, [patt], span, + scope, term`ForwardExpr`, fn f {[patt.transform(f)]}) + +def makeVarPattern(noun, guard, span): + def scope := makeStaticScope([], [], [], [noun.getName()], false) + object varPattern: + to getNoun(): + return noun + to getGuard(): + return guard + to subPrintOn(out, priority): + out.print("var ") + noun.subPrintOn(out, priority) + if (guard != null): + out.print(" :") + guard.subPrintOn(out, priorities["order"]) + return astWrapper(varPattern, makeVarPattern, [noun, guard], span, + scope, term`VarPattern`, + fn f {[noun.transform(f), maybeTransform(guard, f)]}) + +def makeBindPattern(noun, span): + def scope := makeStaticScope([], [], [noun.getName()], [], false) + object bindPattern: + to getNoun(): + return noun + to subPrintOn(out, priority): + out.print("bind ") + noun.subPrintOn(out, priority) + return astWrapper(bindPattern, makeBindPattern, [noun], span, + scope, term`BindPattern`, fn f {[noun.transform(f)]}) + +def makeDefExpr(pattern, exit_, expr, span): + def scope := if (exit_ == null) { + pattern.getStaticScope() + expr.getStaticScope() + } else { + pattern.getStaticScope() + exit_.getStaticScope() + expr.getStaticScope() + } + object defExpr: + to getPattern(): + return pattern + to getExit(): + return exit_ + to getExpr(): + return expr + to subPrintOn(out, priority): + if (priorities["assign"] < priority): + out.print("(") + if (![makeVarPattern, makeBindPattern].contains(pattern._uncall()[0])): + out.print("def ") + pattern.subPrintOn(out, priorities["pattern"]) + if (exit_ != null): + out.print(" exit ") + exit_.subPrintOn(out, priorities["call"]) + out.print(" := ") + expr.subPrintOn(out, priorities["assign"]) + if (priorities["assign"] < priority): + out.print(")") + return astWrapper(defExpr, makeDefExpr, [pattern, exit_, expr], span, + scope, term`DefExpr`, fn f {[pattern.transform(f), if (exit_ == null) {null} else {exit_.transform(f)}, expr.transform(f)]}) + +def makeAssignExpr(lvalue, rvalue, span): + def [lmaker, _, largs] := lvalue._uncall() + def lscope := if (lmaker == makeNounExpr || lmaker == makeTempNounExpr) { + makeStaticScope([], [lvalue.getName()], [], [], false) + } else { + lvalue.getStaticScope() + } + def scope := lscope + rvalue.getStaticScope() + object assignExpr: + to getLvalue(): + return lvalue + to getRvalue(): + return rvalue + to subPrintOn(out, priority): + if (priorities["assign"] < priority): + out.print("(") + lvalue.subPrintOn(out, priorities["call"]) + out.print(" := ") + rvalue.subPrintOn(out, priorities["assign"]) + if (priorities["assign"] < priority): + out.print(")") + return astWrapper(assignExpr, makeAssignExpr, [lvalue, rvalue], span, + scope, term`AssignExpr`, fn f {[lvalue.transform(f), rvalue.transform(f)]}) + +def makeVerbAssignExpr(verb, lvalue, rvalues, span): + def [lmaker, _, largs] := lvalue._uncall() + def lscope := if (lmaker == makeNounExpr || lmaker == makeTempNounExpr) { + makeStaticScope([], [lvalue.getName()], [], [], false) + } else { + lvalue.getStaticScope() + } + def scope := lscope + sumScopes(rvalues) + object verbAssignExpr: + to getLvalue(): + return lvalue + to getRvalues(): + return rvalues + to subPrintOn(out, priority): + if (priorities["assign"] < priority): + out.print("(") + lvalue.subPrintOn(out, priorities["call"]) + out.print(" ") + if (isIdentifier(verb)): + out.print(verb) + else: + out.quote(verb) + out.print("= ") + printListOn("(", rvalues, ", ", ")", out, priorities["assign"]) + if (priorities["assign"] < priority): + out.print(")") + return astWrapper(verbAssignExpr, makeVerbAssignExpr, [verb, lvalue, rvalues], span, + scope, term`VerbAssignExpr`, fn f {[verb, lvalue.transform(f), transformAll(rvalues, f)]}) + + +def makeAugAssignExpr(op, lvalue, rvalue, span): + def [lmaker, _, largs] := lvalue._uncall() + def lscope := if (lmaker == makeNounExpr || lmaker == makeTempNounExpr) { + makeStaticScope([], [lvalue.getName()], [], [], false) + } else { + lvalue.getStaticScope() + } + def scope := lscope + rvalue.getStaticScope() + object augAssignExpr: + to getOp(): + return op + to getOpName(): + return operatorsToNamePrio[op][0] + to getLvalue(): + return lvalue + to getRvalue(): + return rvalue + to subPrintOn(out, priority): + if (priorities["assign"] < priority): + out.print("(") + lvalue.subPrintOn(out, priorities["call"]) + out.print(" ") + out.print(op) + out.print("= ") + rvalue.subPrintOn(out, priorities["assign"]) + if (priorities["assign"] < priority): + out.print(")") + return astWrapper(augAssignExpr, makeAugAssignExpr, [op, lvalue, rvalue], span, + scope, term`AugAssignExpr`, fn f {[op, lvalue.transform(f), rvalue.transform(f)]}) + +def makeMethod(docstring, verb, patterns, resultGuard, body, span): + def scope := sumScopes(patterns + [resultGuard, body]).hide() + object ::"method": + to getDocstring(): + return docstring + to getVerb(): + return verb + to getPatterns(): + return patterns + to getResultGuard(): + return resultGuard + to getBody(): + return body + to subPrintOn(out, priority): + printDocExprSuiteOn(fn { + out.lnPrint("method ") + if (isIdentifier(verb)) { + out.print(verb) + } else { + out.quote(verb) + } + printListOn("(", patterns, ", ", ")", out, priorities["pattern"]) + if (resultGuard != null) { + out.print(" :") + resultGuard.subPrintOn(out, priorities["call"]) + } + }, docstring, body, out, priority) + return astWrapper(::"method", makeMethod, [docstring, verb, patterns, resultGuard, body], span, + scope, term`Method`, fn f {[docstring, verb, transformAll(patterns, f), maybeTransform(resultGuard, f), body.transform(f)]}) + +def makeTo(docstring, verb, patterns, resultGuard, body, span): + def scope := sumScopes(patterns + [resultGuard, body]).hide() + object ::"to": + to getDocstring(): + return docstring + to getVerb(): + return verb + to getPatterns(): + return patterns + to getResultGuard(): + return resultGuard + to getBody(): + return body + to subPrintOn(out, priority): + + printDocExprSuiteOn(fn { + out.lnPrint("to ") + if (isIdentifier(verb)) { + out.print(verb) + } else { + out.quote(verb) + } + printListOn("(", patterns, ", ", ")", out, priorities["pattern"]) + if (resultGuard != null) { + out.print(" :") + resultGuard.subPrintOn(out, priorities["call"]) + } + }, docstring, body, out, priority) + return astWrapper(::"to", makeTo, [docstring, verb, patterns, resultGuard, body], span, + scope, term`To`, fn f {[docstring, verb, transformAll(patterns, f), maybeTransform(resultGuard, f), body.transform(f)]}) + +def makeMatcher(pattern, body, span): + def scope := (pattern.getStaticScope() + body.getStaticScope()).hide() + object matcher: + to getPattern(): + return pattern + to getBody(): + return body + to subPrintOn(out, priority): + printExprSuiteOn(fn { + out.lnPrint("match "); + pattern.subPrintOn(out, priorities["pattern"]); + }, body, false, out, priority) + return astWrapper(matcher, makeMatcher, [pattern, body], span, + scope, term`Matcher`, fn f {[pattern.transform(f), body.transform(f)]}) + +def makeCatcher(pattern, body, span): + def scope := (pattern.getStaticScope() + body.getStaticScope()).hide() + object catcher: + to getPattern(): + return pattern + to getBody(): + return body + to subPrintOn(out, priority): + printExprSuiteOn(fn { + out.print("catch "); + pattern.subPrintOn(out, priorities["pattern"]); + }, body, true, out, priority) + return astWrapper(catcher, makeCatcher, [pattern, body], span, + scope, term`Catcher`, fn f {[pattern.transform(f), body.transform(f)]}) + +def makeScript(extend, methods, matchers, span): + def scope := sumScopes(methods + matchers) + object script: + to getExtends(): + return extend + to getMethods(): + return methods + to getMatchers(): + return matchers + to printObjectHeadOn(name, asExpr, auditors, out, priority): + out.print("object ") + name.subPrintOn(out, priorities["pattern"]) + if (asExpr != null): + out.print(" as ") + asExpr.subPrintOn(out, priorities["call"]) + if (auditors.size() > 0): + printListOn(" implements ", auditors, ", ", "", out, priorities["call"]) + if (extend != null): + out.print(" extends ") + extend.subPrintOn(out, priorities["order"]) + to subPrintOn(out, priority): + for m in methods + matchers: + m.subPrintOn(out, priority) + out.print("\n") + return astWrapper(script, makeScript, [extend, methods, matchers], span, + scope, term`Script`, fn f {[maybeTransform(extend, f), transformAll(methods, f), transformAll(matchers, f)]}) + +def makeFunctionScript(patterns, resultGuard, body, span): + def scope := sumScopes(patterns + [resultGuard, body]).hide() + object functionScript: + to getPatterns(): + return patterns + to getResultGuard(): + return resultGuard + to getBody(): + return body + to printObjectHeadOn(name, asExpr, auditors, out, priority): + out.print("def ") + name.subPrintOn(out, priorities["pattern"]) + printListOn("(", patterns, ", ", ")", out, priorities["pattern"]) + if (resultGuard != null): + out.print(" :") + resultGuard.subPrintOn(out, priorities["call"]) + if (asExpr != null): + out.print(" as ") + asExpr.subPrintOn(out, priorities["call"]) + if (auditors.size() > 0): + printListOn(" implements ", auditors, ", ", "", out, priorities["call"]) + to subPrintOn(out, priority): + body.subPrintOn(out, priority) + out.print("\n") + return astWrapper(functionScript, makeFunctionScript, [patterns, resultGuard, body], span, + scope, term`FunctionScript`, fn f {[transformAll(patterns, f), maybeTransform(resultGuard, f), body.transform(f)]}) + +def makeFunctionExpr(patterns, body, span): + def scope := (sumScopes(patterns) + body.getStaticScope()).hide() + object functionExpr: + to getPatterns(): + return patterns + to getBody(): + return body + to subPrintOn(out, priority): + printExprSuiteOn(fn { + printListOn("fn ", patterns, ", ", "", out, priorities["pattern"]) + }, body, false, out, priorities["assign"]) + return astWrapper(functionExpr, makeFunctionExpr, [patterns, body], span, + scope, term`FunctionExpr`, fn f {[transformAll(patterns, f), body.transform(f)]}) + +def makeListExpr(items, span): + def scope := sumScopes(items) + object listExpr: + to getItems(): + return items + to subPrintOn(out, priority): + printListOn("[", items, ", ", "]", out, priorities["braceExpr"]) + return astWrapper(listExpr, makeListExpr, [items], span, + scope, term`ListExpr`, fn f {[transformAll(items, f)]}) + +def makeListComprehensionExpr(iterable, filter, key, value, body, span): + def scope := sumScopes([iterable, key, value, filter, body]).hide() + object listComprehensionExpr: + to getKey(): + return key + to getValue(): + return value + to getIterable(): + return iterable + to getFilter(): + return filter + to getBody(): + return body + to subPrintOn(out, priority): + out.print("[for ") + if (key != null): + key.subPrintOn(out, priorities["pattern"]) + out.print(" => ") + value.subPrintOn(out, priorities["pattern"]) + out.print(" in (") + iterable.subPrintOn(out, priorities["braceExpr"]) + out.print(") ") + if (filter != null): + out.print("if (") + filter.subPrintOn(out, priorities["braceExpr"]) + out.print(") ") + body.subPrintOn(out, priorities["braceExpr"]) + out.print("]") + return astWrapper(listComprehensionExpr, makeListComprehensionExpr, [iterable, filter, key, value, body], span, + scope, term`ListComprehensionExpr`, fn f {[iterable.transform(f), maybeTransform(filter, f), maybeTransform(key, f), value.transform(f), body.transform(f)]}) + +def makeMapExprAssoc(key, value, span): + def scope := key.getStaticScope() + value.getStaticScope() + object mapExprAssoc: + to getKey(): + return key + to getValue(): + return value + to subPrintOn(out, priority): + key.subPrintOn(out, priorities["braceExpr"]) + out.print(" => ") + value.subPrintOn(out, priorities["braceExpr"]) + return astWrapper(mapExprAssoc, makeMapExprAssoc, [key, value], span, + scope, term`MapExprAssoc`, fn f {[key.transform(f), value.transform(f)]}) + +def makeMapExprExport(value, span): + def scope := value.getStaticScope() + object mapExprExport: + to getValue(): + return value + to subPrintOn(out, priority): + out.print("=> ") + value.subPrintOn(out, priorities["prim"]) + return astWrapper(mapExprExport, makeMapExprExport, [value], span, + scope, term`MapExprExport`, fn f {[value.transform(f)]}) + +def makeMapExpr(pairs ? (pairs.size() > 0), span): + def scope := sumScopes(pairs) + object mapExpr: + to getPairs(): + return pairs + to subPrintOn(out, priority): + printListOn("[", pairs, ", ", "]", out, priorities["braceExpr"]) + return astWrapper(mapExpr, makeMapExpr, [pairs], span, + scope, term`MapExpr`, fn f {[transformAll(pairs, f)]}) + +def makeMapComprehensionExpr(iterable, filter, key, value, bodyk, bodyv, span): + def scope := sumScopes([iterable, key, value, filter, bodyk, bodyv]).hide() + object mapComprehensionExpr: + to getIterable(): + return iterable + to getFilter(): + return filter + to getKey(): + return key + to getValue(): + return value + to getBodyKey(): + return bodyk + to getBodyValue(): + return bodyv + to subPrintOn(out, priority): + out.print("[for ") + if (key != null): + key.subPrintOn(out, priorities["pattern"]) + out.print(" => ") + value.subPrintOn(out, priorities["pattern"]) + out.print(" in (") + iterable.subPrintOn(out, priorities["braceExpr"]) + out.print(") ") + if (filter != null): + out.print("if (") + filter.subPrintOn(out, priorities["braceExpr"]) + out.print(") ") + bodyk.subPrintOn(out, priorities["braceExpr"]) + out.print(" => ") + bodyv.subPrintOn(out, priorities["braceExpr"]) + out.print("]") + return astWrapper(mapComprehensionExpr, makeMapComprehensionExpr, [iterable, filter, key, value, bodyk, bodyv], span, + scope, term`MapComprehensionExpr`, fn f {[iterable.transform(f), maybeTransform(filter, f), maybeTransform(key, f), value.transform(f), bodyk.transform(f), bodyv.transform(f)]}) + +def makeForExpr(iterable, key, value, body, catchPattern, catchBody, span): + def scope := sumScopes([iterable, key, value, body]).hide() + object forExpr: + to getKey(): + return key + to getValue(): + return value + to getIterable(): + return iterable + to getBody(): + return body + to getCatchPattern(): + return catchPattern + to getCatchBody(): + return catchBody + to subPrintOn(out, priority): + printExprSuiteOn(fn { + out.print("for ") + if (key != null) { + key.subPrintOn(out, priorities["pattern"]) + out.print(" => ") + } + value.subPrintOn(out, priorities["pattern"]) + out.print(" in ") + iterable.subPrintOn(out, priorities["braceExpr"]) + }, body, false, out, priority) + if (catchPattern != null): + printExprSuiteOn(fn { + out.print("catch ") + catchPattern.subPrintOn(out, priorities["pattern"]) + }, catchBody, true, out, priority) + return astWrapper(forExpr, makeForExpr, [iterable, key, value, body, catchPattern, catchBody], + span, + scope, term`ForExpr`, fn f {[iterable.transform(f), maybeTransform(key, f), value.transform(f), body.transform(f), maybeTransform(catchPattern, f), maybeTransform(catchBody, f)]}) + +def makeObjectExpr(docstring, name, asExpr, auditors, script, span): + def scope := name.getStaticScope() + sumScopes([asExpr] + auditors).hide() + script.getStaticScope() + object ObjectExpr: + to getDocstring(): + return docstring + to getName(): + return name + to getAsExpr(): + return asExpr + to getAuditors(): + return auditors + to getScript(): + return script + to subPrintOn(out, priority): + def printIt := if (script._uncall()[0] == makeFunctionScript) { + printDocExprSuiteOn + } else { + printObjectSuiteOn + } + printIt(fn { + script.printObjectHeadOn(name, asExpr, auditors, out, priority) + }, docstring, script, out, priority) + return astWrapper(ObjectExpr, makeObjectExpr, [docstring, name, asExpr, auditors, script], span, + scope, term`ObjectExpr`, fn f {[docstring, name.transform(f), maybeTransform(asExpr, f), transformAll(auditors, f), script.transform(f)]}) + +def makeParamDesc(name, guard, span): + def scope := scopeMaybe(guard) + object paramDesc: + to getName(): + return name + to getGuard(): + return guard + to subPrintOn(out, priority): + if (name == null): + out.print("_") + else: + out.print(name) + if (guard != null): + out.print(" :") + guard.subPrintOn(out, priorities["call"]) + return astWrapper(paramDesc, makeParamDesc, [name, guard], span, + scope, term`ParamDesc`, fn f {[name, maybeTransform(guard, f)]}) + +def makeMessageDesc(docstring, verb, params, resultGuard, span): + def scope := sumScopes(params + [resultGuard]) + object messageDesc: + to getDocstring(): + return docstring + to getVerb(): + return verb + to getParams(): + return params + to getResultGuard(): + return resultGuard + to subPrintOn(head, out, priority): + #XXX hacckkkkkk + if (head == "to"): + out.println("") + out.print(head) + out.print(" ") + if (isIdentifier(verb)): + out.print(verb) + else: + out.quote(verb) + printListOn("(", params, ", ", ")", out, priorities["pattern"]) + if (resultGuard != null): + out.print(" :") + resultGuard.subPrintOn(out, priorities["call"]) + if (docstring != null): + def bracey := priorities["braceExpr"] <= priority + def indentOut := out.indent(INDENT) + if (bracey): + indentOut.print(" {") + else: + indentOut.print(":") + printDocstringOn(docstring, indentOut, bracey) + if (bracey): + out.print("}") + + return astWrapper(messageDesc, makeMessageDesc, [docstring, verb, params, resultGuard], span, + scope, term`MessageDesc`, fn f {[docstring, verb, transformAll(params, f), maybeTransform(resultGuard, f)]}) + + +def makeInterfaceExpr(docstring, name, stamp, parents, auditors, messages, span): + def scope := name.getStaticScope() + sumScopes(parents + [stamp] + auditors + messages) + object interfaceExpr: + to getDocstring(): + return docstring + to getName(): + return name + to getStamp(): + return stamp + to getParents(): + return parents + to getAuditors(): + return auditors + to getMessages(): + return messages + to subPrintOn(out, priority): + out.print("interface ") + out.print(name) + if (stamp != null): + out.print(" guards ") + stamp.subPrintOn(out, priorities["pattern"]) + if (parents.size() > 0): + printListOn(" extends ", parents, ", ", "", out, priorities["call"]) + if (auditors.size() > 0): + printListOn(" implements ", auditors, ", ", "", out, priorities["call"]) + def indentOut := out.indent(INDENT) + if (priorities["braceExpr"] <= priority): + indentOut.print(" {") + else: + indentOut.print(":") + printDocstringOn(docstring, indentOut, false) + for m in messages: + m.subPrintOn("to", indentOut, priority) + indentOut.print("\n") + if (priorities["braceExpr"] <= priority): + out.print("}") + return astWrapper(interfaceExpr, makeInterfaceExpr, [docstring, name, stamp, parents, auditors, messages], span, + scope, term`InterfaceExpr`, fn f {[docstring, name.transform(f), maybeTransform(stamp, f), transformAll(parents, f), transformAll(auditors, f), transformAll(messages, f)]}) + +def makeFunctionInterfaceExpr(docstring, name, stamp, parents, auditors, messageDesc, span): + def scope := name.getStaticScope() + sumScopes(parents + [stamp] + auditors + [messageDesc]) + object functionInterfaceExpr: + to getDocstring(): + return docstring + to getName(): + return name + to getMessageDesc(): + return messageDesc + to getStamp(): + return stamp + to getParents(): + return parents + to getAuditors(): + return auditors + to subPrintOn(out, priority): + out.print("interface ") + out.print(name) + var cuddle := true + if (stamp != null): + out.print(" guards ") + stamp.subPrintOn(out, priorities["pattern"]) + cuddle := false + if (parents.size() > 0): + printListOn(" extends ", parents, ", ", "", out, priorities["call"]) + cuddle := false + if (auditors.size() > 0): + printListOn(" implements ", auditors, ", ", "", out, priorities["call"]) + cuddle := false + if (!cuddle): + out.print(" ") + printListOn("(", messageDesc.getParams(), ", ", ")", out, priorities["pattern"]) + if (messageDesc.getResultGuard() != null): + out.print(" :") + messageDesc.getResultGuard().subPrintOn(out, priorities["call"]) + if (docstring != null): + def bracey := priorities["braceExpr"] <= priority + def indentOut := out.indent(INDENT) + if (bracey): + indentOut.print(" {") + else: + indentOut.print(":") + printDocstringOn(docstring, indentOut, bracey) + if (bracey): + out.print("}") + out.print("\n") + return astWrapper(functionInterfaceExpr, makeFunctionInterfaceExpr, [docstring, name, stamp, parents, auditors, messageDesc], span, + scope, term`FunctionInterfaceExpr`, fn f {[docstring, name.transform(f), maybeTransform(stamp, f), transformAll(parents, f), transformAll(auditors, f), messageDesc.transform(f)]}) + +def makeCatchExpr(body, pattern, catcher, span): + def scope := body.getStaticScope().hide() + (pattern.getStaticScope() + catcher.getStaticScope()).hide() + object catchExpr: + to getBody(): + return body + to getPattern(): + return pattern + to getCatcher(): + return catcher + to subPrintOn(out, priority): + printExprSuiteOn(fn {out.print("try")}, body, false, out, priority) + printExprSuiteOn(fn { + out.print("catch ") + pattern.subPrintOn(out, priorities["pattern"]) + }, catcher, true, out, priority) + return astWrapper(catchExpr, makeCatchExpr, [body, pattern, catcher], span, + scope, term`CatchExpr`, fn f {[body.transform(f), pattern.transform(f), + catcher.transform(f)]}) + +def makeFinallyExpr(body, unwinder, span): + def scope := body.getStaticScope().hide() + unwinder.getStaticScope().hide() + object finallyExpr: + to getBody(): + return body + to getUnwinder(): + return unwinder + to subPrintOn(out, priority): + printExprSuiteOn(fn {out.print("try")}, body, false, out, priority) + printExprSuiteOn(fn {out.print("finally")}, unwinder, true, out, + priority) + return astWrapper(finallyExpr, makeFinallyExpr, [body, unwinder], span, + scope, term`FinallyExpr`, fn f {[body.transform(f), unwinder.transform(f)]}) + +def makeTryExpr(body, catchers, finallyBlock, span): + def baseScope := (body.getStaticScope() + sumScopes(catchers)).hide() + def scope := if (finallyBlock == null) { + baseScope + } else { + baseScope + finallyBlock.getStaticScope().hide() + } + object tryExpr: + to getBody(): + return body + to getCatchers(): + return catchers + to getFinally(): + return finallyBlock + to subPrintOn(out, priority): + printExprSuiteOn(fn {out.print("try")}, body, false, out, priority) + for m in catchers: + m.subPrintOn(out, priority) + if (finallyBlock != null): + printExprSuiteOn(fn {out.print("finally")}, + finallyBlock, true, out, priority) + return astWrapper(tryExpr, makeTryExpr, [body, catchers, finallyBlock], span, + scope, term`TryExpr`, fn f {[body.transform(f), transformAll(catchers, f),maybeTransform(finallyBlock, f)]}) + +def makeEscapeExpr(ejectorPattern, body, catchPattern, catchBody, span): + def baseScope := (ejectorPattern.getStaticScope() + body.getStaticScope()).hide() + def scope := if (catchPattern == null) { + baseScope + } else { + baseScope + (catchPattern.getStaticScope() + catchBody.getStaticScope()).hide() + } + object escapeExpr: + to getEjectorPattern(): + return ejectorPattern + to getBody(): + return body + to getCatchPattern(): + return catchPattern + to getCatchBody(): + return catchBody + to subPrintOn(out, priority): + printExprSuiteOn(fn { + out.print("escape ") + ejectorPattern.subPrintOn(out, priorities["pattern"]) + }, body, false, out, priority) + if (catchPattern != null): + printExprSuiteOn(fn { + out.print("catch ") + catchPattern.subPrintOn(out, priorities["pattern"]) + }, catchBody, true, out, priority) + return astWrapper(escapeExpr, makeEscapeExpr, + [ejectorPattern, body, catchPattern, catchBody], span, + scope, term`EscapeExpr`, + fn f {[ejectorPattern.transform(f), body.transform(f), + maybeTransform(catchPattern, f), maybeTransform(catchBody, f)]}) + +def makeSwitchExpr(specimen, matchers, span): + def scope := specimen.getStaticScope() + sumScopes(matchers) + object switchExpr: + to getSpecimen(): + return specimen + to getMatchers(): + return matchers + to subPrintOn(out, priority): + out.print("switch (") + specimen.subPrintOn(out, priorities["braceExpr"]) + out.print(")") + def indentOut := out.indent(INDENT) + if (priorities["braceExpr"] <= priority): + indentOut.print(" {") + else: + indentOut.print(":") + for m in matchers: + m.subPrintOn(indentOut, priority) + indentOut.print("\n") + if (priorities["braceExpr"] <= priority): + out.print("}") + return astWrapper(switchExpr, makeSwitchExpr, [specimen, matchers], span, + scope, term`SwitchExpr`, fn f {[specimen.transform(f), transformAll(matchers, f)]}) + +def makeWhenExpr(args, body, catchers, finallyBlock, span): + def scope := sumScopes(args + [body]).hide() + sumScopes(catchers) + scopeMaybe(finallyBlock).hide() + object whenExpr: + to getArgs(): + return args + to getBody(): + return body + to getCatchers(): + return catchers + to getFinally(): + return finallyBlock + to subPrintOn(out, priority): + printListOn("when (", args, ", ", ") ->", out, priorities["braceExpr"]) + def indentOut := out.indent(INDENT) + if (priorities["braceExpr"] <= priority): + indentOut.println(" {") + else: + indentOut.println("") + body.subPrintOn(indentOut, priority) + if (priorities["braceExpr"] <= priority): + out.println("") + out.print("}") + for c in catchers: + c.subPrintOn(out, priority) + if (finallyBlock != null): + printExprSuiteOn(fn { + out.print("finally") + }, finallyBlock, true, out, priority) + return astWrapper(whenExpr, makeWhenExpr, [args, body, catchers, finallyBlock], span, + scope, term`WhenExpr`, fn f {[transformAll(args, f), body.transform(f), transformAll(catchers, f), maybeTransform(finallyBlock, f)]}) + +def makeIfExpr(test, consq, alt, span): + def baseScope := test.getStaticScope() + consq.getStaticScope().hide() + def scope := if (alt == null) { + baseScope + } else { + baseScope + alt.getStaticScope().hide() + } + object ifExpr: + to getTest(): + return test + to getThen(): + return consq + to getElse(): + return alt + to subPrintOn(out, priority): + printExprSuiteOn(fn { + out.print("if (") + test.subPrintOn(out, priorities["braceExpr"]) + out.print(")") + }, consq, false, out, priority) + if (alt != null): + if (alt.getNodeName() == "IfExpr"): + if (priorities["braceExpr"] <= priority): + out.print(" ") + else: + out.println("") + out.print("else ") + alt.subPrintOn(out, priority) + else: + printExprSuiteOn(fn {out.print("else")}, alt, true, out, priority) + + return astWrapper(ifExpr, makeIfExpr, [test, consq, alt], span, + scope, term`IfExpr`, fn f {[test.transform(f), consq.transform(f), maybeTransform(alt, f)]}) + +def makeWhileExpr(test, body, catcher, span): + def scope := sumScopes([test, body, catcher]) + object whileExpr: + to getTest(): + return test + to getBody(): + return body + to getCatcher(): + return catcher + to subPrintOn(out, priority): + printExprSuiteOn(fn { + out.print("while (") + test.subPrintOn(out, priorities["braceExpr"]) + out.print(")") + }, body, false, out, priority) + if (catcher != null): + catcher.subPrintOn(out, priority) + return astWrapper(whileExpr, makeWhileExpr, [test, body, catcher], span, + scope, term`WhileExpr`, fn f {[test.transform(f), body.transform(f), maybeTransform(catcher, f)]}) + +def makeHideExpr(body, span): + def scope := body.getStaticScope().hide() + object hideExpr: + to getBody(): + return body + to subPrintOn(out, priority): + def indentOut := out.indent(INDENT) + indentOut.println("{") + body.subPrintOn(indentOut, priorities["braceExpr"]) + out.println("") + out.print("}") + + return astWrapper(hideExpr, makeHideExpr, [body], span, + scope, term`HideExpr`, fn f {[body.transform(f)]}) + +def makeValueHoleExpr(index, span): + def scope := null + object valueHoleExpr: + to getIndex(): + return index + to subPrintOn(out, priority): + out.print("${value-hole ") + out.print(index) + out.print("}") + return astWrapper(valueHoleExpr, makeValueHoleExpr, [index], span, + scope, term`ValueHoleExpr`, fn f {[index]}) + +def makePatternHoleExpr(index, span): + def scope := null + object patternHoleExpr: + to getIndex(): + return index + to subPrintOn(out, priority): + out.print("${pattern-hole ") + out.print(index) + out.print("}") + return astWrapper(patternHoleExpr, makePatternHoleExpr, [index], span, + scope, term`PatternHoleExpr`, fn f {[index]}) + +def makeValueHolePattern(index, span): + def scope := null + object valueHolePattern: + to getIndex(): + return index + to subPrintOn(out, priority): + out.print("@{value-hole ") + out.print(index) + out.print("}") + return astWrapper(valueHolePattern, makeValueHolePattern, [index], span, + scope, term`ValueHolePattern`, fn f {[index]}) + +def makePatternHolePattern(index, span): + def scope := null + object patternHolePattern: + to getIndex(): + return index + to subPrintOn(out, priority): + out.print("@{pattern-hole ") + out.print(index) + out.print("}") + return astWrapper(patternHolePattern, makePatternHolePattern, [index], span, + scope, term`PatternHolePattern`, fn f {[index]}) + +def makeFinalPattern(noun, guard, span): + def gs := scopeMaybe(guard) + if (gs.namesUsed().maps(noun.getName())): + throw("Kernel guard cycle not allowed") + def scope := makeStaticScope([], [], [noun.getName()], [], false) + gs + object finalPattern: + to getNoun(): + return noun + to getGuard(): + return guard + to subPrintOn(out, priority): + noun.subPrintOn(out, priority) + if (guard != null): + out.print(" :") + guard.subPrintOn(out, priorities["order"]) + return astWrapper(finalPattern, makeFinalPattern, [noun, guard], span, + scope, term`FinalPattern`, + fn f {[noun.transform(f), maybeTransform(guard, f)]}) + +def makeSlotPattern(noun, guard, span): + def gs := scopeMaybe(guard) + if (gs.namesUsed().maps(noun.getName())): + throw("Kernel guard cycle not allowed") + def scope := makeStaticScope([], [], [noun.getName()], [], false) + gs + object slotPattern: + to getNoun(): + return noun + to subPrintOn(out, priority): + out.print("&") + noun.subPrintOn(out, priority) + if (guard != null): + out.print(" :") + guard.subPrintOn(out, priorities["order"]) + return astWrapper(slotPattern, makeSlotPattern, [noun, guard], span, + scope, term`SlotPattern`, fn f {[noun.transform(f), maybeTransform(guard, f)]}) + +def makeBindingPattern(noun, span): + def scope := makeStaticScope([], [], [noun.getName()], [], false) + object bindingPattern: + to getNoun(): + return noun + to subPrintOn(out, priority): + out.print("&&") + noun.subPrintOn(out, priority) + return astWrapper(bindingPattern, makeBindingPattern, [noun], span, + scope, term`BindingPattern`, fn f {[noun.transform(f)]}) + +def makeIgnorePattern(guard, span): + def scope := scopeMaybe(guard) + object ignorePattern: + to getGuard(): + return guard + to subPrintOn(out, priority): + out.print("_") + if (guard != null): + out.print(" :") + guard.subPrintOn(out, priorities["order"]) + return astWrapper(ignorePattern, makeIgnorePattern, [guard], span, + scope, term`IgnorePattern`, fn f {[maybeTransform(guard, f)]}) + +def makeListPattern(patterns, tail, span): + def scope := sumScopes(patterns + [tail]) + object listPattern: + to getPatterns(): + return patterns + to getTail(): + return tail + to subPrintOn(out, priority): + printListOn("[", patterns, ", ", "]", out, priorities["pattern"]) + if (tail != null): + out.print(" + ") + tail.subPrintOn(out, priorities["pattern"]) + return astWrapper(listPattern, makeListPattern, [patterns, tail], span, + scope, term`ListPattern`, fn f {[transformAll(patterns, f), maybeTransform(tail, f)]}) + +def makeMapPatternAssoc(key, value, span): + def scope := key.getStaticScope() + value.getStaticScope() + object mapPatternAssoc: + to getKey(): + return key + to getValue(): + return value + to subPrintOn(out, priority): + if (key._uncall()[0] == makeLiteralExpr): + key.subPrintOn(out, priority) + else: + out.print("(") + key.subPrintOn(out, priorities["braceExpr"]) + out.print(")") + out.print(" => ") + value.subPrintOn(out, priority) + return astWrapper(mapPatternAssoc, makeMapPatternAssoc, [key, value], span, + scope, term`MapPatternAssoc`, fn f {[key.transform(f), value.transform(f)]}) + +def makeMapPatternImport(pattern, span): + def scope := pattern.getStaticScope() + object mapPatternImport: + to getPattern(): + return pattern + to subPrintOn(out, priority): + out.print("=> ") + pattern.subPrintOn(out, priority) + return astWrapper(mapPatternImport, makeMapPatternImport, [pattern], span, + scope, term`MapPatternImport`, fn f {[pattern.transform(f)]}) + +def makeMapPatternRequired(keyer, span): + def scope := keyer.getStaticScope() + object mapPatternRequired: + to getKeyer(): + return keyer + to getDefault(): + return null + to subPrintOn(out, priority): + keyer.subPrintOn(out, priority) + return astWrapper(mapPatternRequired, makeMapPatternRequired, [keyer], span, + scope, term`MapPatternRequired`, fn f {[keyer.transform(f)]}) + +def makeMapPatternDefault(keyer, default, span): + def scope := keyer.getStaticScope() + default.getStaticScope() + object mapPatternDefault: + to getKeyer(): + return keyer + to getDefault(): + return default + to subPrintOn(out, priority): + keyer.subPrintOn(out, priority) + out.print(" := (") + default.subPrintOn(out, priorities["braceExpr"]) + out.print(")") + return astWrapper(mapPatternDefault, makeMapPatternDefault, [keyer, default], span, + scope, term`MapPatternDefault`, fn f {[keyer.transform(f), default.transform(f)]}) + +def makeMapPattern(patterns, tail, span): + def scope := sumScopes(patterns + [tail]) + object mapPattern: + to getPatterns(): + return patterns + to getTail(): + return tail + to subPrintOn(out, priority): + printListOn("[", patterns, ", ", "]", out, priorities["pattern"]) + if (tail != null): + out.print(" | ") + tail.subPrintOn(out, priorities["pattern"]) + return astWrapper(mapPattern, makeMapPattern, [patterns, tail], span, + scope, term`MapPattern`, fn f {[transformAll(patterns, f), maybeTransform(tail, f)]}) + +def makeViaPattern(expr, subpattern, span): + def scope := expr.getStaticScope() + subpattern.getStaticScope() + object viaPattern: + to getExpr(): + return expr + to getPattern(): + return subpattern + to subPrintOn(out, priority): + out.print("via (") + expr.subPrintOn(out, priorities["braceExpr"]) + out.print(") ") + subpattern.subPrintOn(out, priority) + return astWrapper(viaPattern, makeViaPattern, [expr, subpattern], span, + scope, term`ViaPattern`, fn f {[expr.transform(f), subpattern.transform(f)]}) + +def makeSuchThatPattern(subpattern, expr, span): + def scope := expr.getStaticScope() + subpattern.getStaticScope() + object suchThatPattern: + to getExpr(): + return expr + to getPattern(): + return subpattern + to subPrintOn(out, priority): + subpattern.subPrintOn(out, priority) + out.print(" ? (") + expr.subPrintOn(out, priorities["braceExpr"]) + out.print(")") + return astWrapper(suchThatPattern, makeSuchThatPattern, [subpattern, expr], span, + scope, term`SuchThatPattern`, fn f {[subpattern.transform(f), expr.transform(f)]}) + +def makeSamePattern(value, direction, span): + def scope := value.getStaticScope() + object samePattern: + to getValue(): + return value + to getDirection(): + return direction + to subPrintOn(out, priority): + if (direction): + out.print("==") + else: + out.print("!=") + value.subPrintOn(out, priorities["call"]) + return astWrapper(samePattern, makeSamePattern, [value, direction], span, + scope, term`SamePattern`, fn f {[value.transform(f), direction]}) + +def makeQuasiText(text, span): + def scope := emptyScope + object quasiText: + to getText(): + return text + to subPrintOn(out, priority): + out.print(text) + return astWrapper(quasiText, makeQuasiText, [text], span, + scope, term`QuasiText`, fn f {[text]}) + +def makeQuasiExprHole(expr, span): + def scope := expr.getStaticScope() + object quasiExprHole: + to getExpr(): + return expr + to subPrintOn(out, priority): + out.print("$") + if (priorities["braceExpr"] < priority): + if (expr._uncall()[0] == makeNounExpr && isIdentifier(expr.getName())): + expr.subPrintOn(out, priority) + return + out.print("{") + expr.subPrintOn(out, priorities["braceExpr"]) + out.print("}") + return astWrapper(quasiExprHole, makeQuasiExprHole, [expr], span, + scope, term`QuasiExprHole`, fn f {[expr.transform(f)]}) + + +def makeQuasiPatternHole(pattern, span): + def scope := pattern.getStaticScope() + object quasiPatternHole: + to getPattern(): + return pattern + to subPrintOn(out, priority): + out.print("@") + if (priorities["braceExpr"] < priority): + if (pattern._uncall()[0] == makeFinalPattern): + if (pattern.getGuard() == null && isIdentifier(pattern.getNoun().getName())): + pattern.subPrintOn(out, priority) + return + out.print("{") + pattern.subPrintOn(out, priority) + out.print("}") + return astWrapper(quasiPatternHole, makeQuasiPatternHole, [pattern], span, + scope, term`QuasiPatternHole`, fn f {[pattern.transform(f)]}) + +def quasiPrint(name, quasis, out, priority): + if (name != null): + out.print(name) + out.print("`") + for i => q in quasis: + var p := priorities["prim"] + if (i + 1 < quasis.size()): + def next := quasis[i + 1] + if (next._uncall()[0] == makeQuasiText): + if (next.getText().size() > 0 && idPart(next.getText()[0])): + p := priorities["braceExpr"] + q.subPrintOn(out, p) + out.print("`") + +def makeQuasiParserExpr(name, quasis, span): + def scope := if (name == null) {emptyScope} else {makeStaticScope([name + "__quasiParser"], [], [], [], false)} + sumScopes(quasis) + object quasiParserExpr: + to getName(): + return name + to getQuasis(): + return quasis + to subPrintOn(out, priority): + quasiPrint(name, quasis, out, priority) + return astWrapper(quasiParserExpr, makeQuasiParserExpr, [name, quasis], span, + scope, term`QuasiParserExpr`, fn f {[name, transformAll(quasis, f)]}) + +def makeQuasiParserPattern(name, quasis, span): + def scope := if (name == null) {emptyScope} else {makeStaticScope([name + "__quasiParser"], [], [], [], false)} + sumScopes(quasis) + object quasiParserPattern: + to getName(): + return name + to getQuasis(): + return quasis + to subPrintOn(out, priority): + quasiPrint(name, quasis, out, priority) + return astWrapper(quasiParserPattern, makeQuasiParserPattern, [name, quasis], span, + scope, term`QuasiParserPattern`, fn f {[name, transformAll(quasis, f)]}) + +object astBuilder: + to LiteralExpr(value, span): + return makeLiteralExpr(value, span) + to NounExpr(name, span): + return makeNounExpr(name, span) + to TempNounExpr(namePrefix, span): + return makeTempNounExpr(namePrefix, span) + to SlotExpr(name, span): + return makeSlotExpr(name, span) + to MetaContextExpr(span): + return makeMetaContextExpr(span) + to MetaStateExpr(span): + return makeMetaStateExpr(span) + to BindingExpr(name, span): + return makeBindingExpr(name, span) + to SeqExpr(exprs, span): + return makeSeqExpr(exprs, span) + to "Module"(imports, exports, body, span): + return makeModule(imports, exports, body, span) + to MethodCallExpr(rcvr, verb, arglist, span): + return makeMethodCallExpr(rcvr, verb, arglist, span) + to FunCallExpr(receiver, args, span): + return makeFunCallExpr(receiver, args, span) + to SendExpr(rcvr, verb, arglist, span): + return makeSendExpr(rcvr, verb, arglist, span) + to FunSendExpr(receiver, args, span): + return makeFunSendExpr(receiver, args, span) + to GetExpr(receiver, indices, span): + return makeGetExpr(receiver, indices, span) + to AndExpr(left, right, span): + return makeAndExpr(left, right, span) + to OrExpr(left, right, span): + return makeOrExpr(left, right, span) + to BinaryExpr(left, op, right, span): + return makeBinaryExpr(left, op, right, span) + to CompareExpr(left, op, right, span): + return makeCompareExpr(left, op, right, span) + to RangeExpr(left, op, right, span): + return makeRangeExpr(left, op, right, span) + to SameExpr(left, right, direction, span): + return makeSameExpr(left, right, direction, span) + to MatchBindExpr(specimen, pattern, span): + return makeMatchBindExpr(specimen, pattern, span) + to MismatchExpr(specimen, pattern, span): + return makeMismatchExpr(specimen, pattern, span) + to PrefixExpr(op, receiver, span): + return makePrefixExpr(op, receiver, span) + to CoerceExpr(specimen, guard, span): + return makeCoerceExpr(specimen, guard, span) + to CurryExpr(receiver, verb, isSend, span): + return makeCurryExpr(receiver, verb, isSend, span) + to ExitExpr(name, value, span): + return makeExitExpr(name, value, span) + to ForwardExpr(name, span): + return makeForwardExpr(name, span) + to VarPattern(noun, guard, span): + return makeVarPattern(noun, guard, span) + to DefExpr(pattern, exit_, expr, span): + return makeDefExpr(pattern, exit_, expr, span) + to AssignExpr(lvalue, rvalue, span): + return makeAssignExpr(lvalue, rvalue, span) + to VerbAssignExpr(verb, lvalue, rvalues, span): + return makeVerbAssignExpr(verb, lvalue, rvalues, span) + to AugAssignExpr(op, lvalue, rvalue, span): + return makeAugAssignExpr(op, lvalue, rvalue, span) + to "Method"(docstring, verb, patterns, resultGuard, body, span): + return makeMethod(docstring, verb, patterns, resultGuard, body, span) + to "To"(docstring, verb, patterns, resultGuard, body, span): + return makeTo(docstring, verb, patterns, resultGuard, body, span) + to Matcher(pattern, body, span): + return makeMatcher(pattern, body, span) + to Catcher(pattern, body, span): + return makeCatcher(pattern, body, span) + to Script(extend, methods, matchers, span): + return makeScript(extend, methods, matchers, span) + to FunctionScript(patterns, resultGuard, body, span): + return makeFunctionScript(patterns, resultGuard, body, span) + to FunctionExpr(patterns, body, span): + return makeFunctionExpr(patterns, body, span) + to ListExpr(items, span): + return makeListExpr(items, span) + to ListComprehensionExpr(iterable, filter, key, value, body, span): + return makeListComprehensionExpr(iterable, filter, key, value, body, span) + to MapExprAssoc(key, value, span): + return makeMapExprAssoc(key, value, span) + to MapExprExport(value, span): + return makeMapExprExport(value, span) + to MapExpr(pairs, span): + return makeMapExpr(pairs, span) + to MapComprehensionExpr(iterable, filter, key, value, bodyk, bodyv, span): + return makeMapComprehensionExpr(iterable, filter, key, value, bodyk, bodyv, span) + to ForExpr(iterable, key, value, body, catchPattern, catchBlock, span): + return makeForExpr(iterable, key, value, body, catchPattern, catchBlock, span) + to ObjectExpr(docstring, name, asExpr, auditors, script, span): + return makeObjectExpr(docstring, name, asExpr, auditors, script, span) + to ParamDesc(name, guard, span): + return makeParamDesc(name, guard, span) + to MessageDesc(docstring, verb, params, resultGuard, span): + return makeMessageDesc(docstring, verb, params, resultGuard, span) + to InterfaceExpr(docstring, name, stamp, parents, auditors, messages, span): + return makeInterfaceExpr(docstring, name, stamp, parents, auditors, messages, span) + to FunctionInterfaceExpr(docstring, name, stamp, parents, auditors, messageDesc, span): + return makeFunctionInterfaceExpr(docstring, name, stamp, parents, auditors, messageDesc, span) + to CatchExpr(body, pattern, catcher, span): + return makeCatchExpr(body, pattern, catcher, span) + to FinallyExpr(body, unwinder, span): + return makeFinallyExpr(body, unwinder, span) + to TryExpr(body, catchers, finallyBlock, span): + return makeTryExpr(body, catchers, finallyBlock, span) + to EscapeExpr(ejectorPattern, body, catchPattern, catchBody, span): + return makeEscapeExpr(ejectorPattern, body, catchPattern, catchBody, span) + to SwitchExpr(specimen, matchers, span): + return makeSwitchExpr(specimen, matchers, span) + to WhenExpr(args, body, catchers, finallyBlock, span): + return makeWhenExpr(args, body, catchers, finallyBlock, span) + to IfExpr(test, consq, alt, span): + return makeIfExpr(test, consq, alt, span) + to WhileExpr(test, body, catcher, span): + return makeWhileExpr(test, body, catcher, span) + to HideExpr(body, span): + return makeHideExpr(body, span) + to ValueHoleExpr(index, span): + return makeValueHoleExpr(index, span) + to PatternHoleExpr(index, span): + return makePatternHoleExpr(index, span) + to ValueHolePattern(index, span): + return makeValueHolePattern(index, span) + to PatternHolePattern(index, span): + return makePatternHolePattern(index, span) + to FinalPattern(noun, guard, span): + return makeFinalPattern(noun, guard, span) + to SlotPattern(noun, guard, span): + return makeSlotPattern(noun, guard, span) + to BindingPattern(noun, span): + return makeBindingPattern(noun, span) + to BindPattern(noun, span): + return makeBindPattern(noun, span) + to IgnorePattern(guard, span): + return makeIgnorePattern(guard, span) + to ListPattern(patterns, tail, span): + return makeListPattern(patterns, tail, span) + to MapPatternAssoc(key, value, span): + return makeMapPatternAssoc(key, value, span) + to MapPatternImport(value, span): + return makeMapPatternImport(value, span) + to MapPatternRequired(keyer, span): + return makeMapPatternRequired(keyer, span) + to MapPatternDefault(keyer, default, span): + return makeMapPatternDefault(keyer, default, span) + to MapPattern(patterns, tail, span): + return makeMapPattern(patterns, tail, span) + to ViaPattern(expr, subpattern, span): + return makeViaPattern(expr, subpattern, span) + to SuchThatPattern(subpattern, expr, span): + return makeSuchThatPattern(subpattern, expr, span) + to SamePattern(value, direction, span): + return makeSamePattern(value, direction, span) + to QuasiText(text, span): + return makeQuasiText(text, span) + to QuasiExprHole(expr, span): + return makeQuasiExprHole(expr, span) + to QuasiPatternHole(pattern, span): + return makeQuasiPatternHole(pattern, span) + to QuasiParserExpr(name, quasis, span): + return makeQuasiParserExpr(name, quasis, span) + to QuasiParserPattern(name, quasis, span): + return makeQuasiParserPattern(name, quasis, span) + +def test_literalExpr(assert): + def expr := makeLiteralExpr("one", null) + assert.equal(expr._uncall(), [makeLiteralExpr, "run", ["one", null]]) + assert.equal(M.toString(expr), "\"one\"") + assert.equal(expr.asTerm(), term`LiteralExpr("one")`) + +def test_nounExpr(assert): + def expr := makeNounExpr("foo", null) + assert.equal(expr._uncall(), [makeNounExpr, "run", ["foo", null]]) + assert.equal(M.toString(expr), "foo") + assert.equal(expr.asTerm(), term`NounExpr("foo")`) + assert.equal(M.toString(makeNounExpr("unwind-protect", null)), + "::\"unwind-protect\"") + +def test_tempNounExpr(assert): + def expr := makeTempNounExpr("foo", null) + assert.equal(M.toString(expr), "$") + assert.equal(expr.asTerm(), term`TempNounExpr("foo")`) + +def test_slotExpr(assert): + def noun := makeNounExpr("foo", null) + def expr := makeSlotExpr(noun, null) + assert.equal(expr._uncall(), [makeSlotExpr, "run", [noun, null]]) + assert.equal(M.toString(expr), "&foo") + assert.equal(expr.asTerm(), term`SlotExpr(NounExpr("foo"))`) + assert.equal(M.toString(makeSlotExpr(makeNounExpr("unwind-protect", null), null)), + "&::\"unwind-protect\"") + +def test_bindingExpr(assert): + def noun := makeNounExpr("foo", null) + def expr := makeBindingExpr(noun, null) + assert.equal(expr._uncall(), [makeBindingExpr, "run", [noun, null]]) + assert.equal(M.toString(expr), "&&foo") + assert.equal(expr.asTerm(), term`BindingExpr(NounExpr("foo"))`) + assert.equal(M.toString(makeBindingExpr(makeNounExpr("unwind-protect", null), null)), + "&&::\"unwind-protect\"") + +def test_metaContextExpr(assert): + def expr := makeMetaContextExpr(null) + assert.equal(expr._uncall(), [makeMetaContextExpr, "run", [null]]) + assert.equal(M.toString(expr), "meta.context()") + assert.equal(expr.asTerm(), term`MetaContextExpr()`) + +def test_metaStateExpr(assert): + def expr := makeMetaStateExpr(null) + assert.equal(expr._uncall(), [makeMetaStateExpr, "run", [null]]) + assert.equal(M.toString(expr), "meta.getState()") + assert.equal(expr.asTerm(), term`MetaStateExpr()`) + +def test_seqExpr(assert): + def exprs := [makeLiteralExpr(3, null), makeLiteralExpr("four", null)] + def expr := makeSeqExpr(exprs, null) + assert.equal(expr._uncall(), [makeSeqExpr, "run", [exprs, null]]) + assert.equal(M.toString(expr), "3\n\"four\"") + assert.equal(expr.asTerm(), term`SeqExpr([LiteralExpr(3), LiteralExpr("four")])`) + +def test_module(assert): + def body := makeLiteralExpr(3, null) + def imports := [makeFinalPattern(makeNounExpr("a", null), null, null), makeFinalPattern(makeNounExpr("b", null), null, null)] + def exports := [makeNounExpr("c", null)] + def expr := makeModule(imports, exports, body, null) + assert.equal(expr._uncall(), [makeModule, "run", [imports, exports, body, null]]) + assert.equal(M.toString(expr), "module a, b\nexport (c)\n3") + assert.equal(expr.asTerm(), term`Module([FinalPattern(NounExpr("a"), null), FinalPattern(NounExpr("b"), null)], [NounExpr("c")], LiteralExpr(3))`) + +def test_methodCallExpr(assert): + def args := [makeLiteralExpr(1, null), makeLiteralExpr("two", null)] + def receiver := makeNounExpr("foo", null) + def expr := makeMethodCallExpr(receiver, "doStuff", + args, null) + assert.equal(expr._uncall(), [makeMethodCallExpr, "run", [receiver, "doStuff", args, null]]) + assert.equal(M.toString(expr), "foo.doStuff(1, \"two\")") + assert.equal(expr.asTerm(), term`MethodCallExpr(NounExpr("foo"), "doStuff", [LiteralExpr(1), LiteralExpr("two")])`) + def fcall := makeMethodCallExpr(makeNounExpr("foo", null), "run", + [makeNounExpr("a", null)], null) + assert.equal(M.toString(fcall), "foo.run(a)") + assert.equal(M.toString(makeMethodCallExpr(makeNounExpr("a", null), "+", + [makeNounExpr("b", null)], null)), + "a.\"+\"(b)") + +def test_funCallExpr(assert): + def args := [makeLiteralExpr(1, null), makeLiteralExpr("two", null)] + def receiver := makeNounExpr("foo", null) + def expr := makeFunCallExpr(receiver, args, null) + assert.equal(expr._uncall(), [makeFunCallExpr, "run", [receiver, args, null]]) + assert.equal(M.toString(expr), "foo(1, \"two\")") + assert.equal(expr.asTerm(), term`FunCallExpr(NounExpr("foo"), [LiteralExpr(1), LiteralExpr("two")])`) + +def test_sendExpr(assert): + def args := [makeLiteralExpr(1, null), makeLiteralExpr("two", null)] + def receiver := makeNounExpr("foo", null) + def expr := makeSendExpr(receiver, "doStuff", + args, null) + assert.equal(expr._uncall(), [makeSendExpr, "run", [receiver, "doStuff", args, null]]) + assert.equal(M.toString(expr), "foo <- doStuff(1, \"two\")") + assert.equal(expr.asTerm(), term`SendExpr(NounExpr("foo"), "doStuff", [LiteralExpr(1), LiteralExpr("two")])`) + def fcall := makeSendExpr(makeNounExpr("foo", null), "run", + [makeNounExpr("a", null)], null) + assert.equal(M.toString(fcall), "foo <- run(a)") + assert.equal(M.toString(makeSendExpr(makeNounExpr("a", null), "+", + [makeNounExpr("b", null)], null)), + "a <- \"+\"(b)") + +def test_funSendExpr(assert): + def args := [makeLiteralExpr(1, null), makeLiteralExpr("two", null)] + def receiver := makeNounExpr("foo", null) + def expr := makeFunSendExpr(receiver, args, null) + assert.equal(expr._uncall(), [makeFunSendExpr, "run", [receiver, args, null]]) + assert.equal(M.toString(expr), "foo <- (1, \"two\")") + assert.equal(expr.asTerm(), term`FunSendExpr(NounExpr("foo"), [LiteralExpr(1), LiteralExpr("two")])`) + +def test_compareExpr(assert): + def [left, right] := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeCompareExpr(left, ">=", right, null) + assert.equal(expr._uncall(), [makeCompareExpr, "run", [left, ">=", right, null]]) + assert.equal(M.toString(expr), "a >= b") + assert.equal(expr.asTerm(), term`CompareExpr(NounExpr("a"), ">=", NounExpr("b"))`) + +def test_rangeExpr(assert): + def [left, right] := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeRangeExpr(left, "..!", right, null) + assert.equal(expr._uncall(), [makeRangeExpr, "run", [left, "..!", right, null]]) + assert.equal(M.toString(expr), "a..!b") + assert.equal(expr.asTerm(), term`RangeExpr(NounExpr("a"), "..!", NounExpr("b"))`) + +def test_sameExpr(assert): + def [left, right] := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeSameExpr(left, right, true, null) + assert.equal(expr._uncall(), [makeSameExpr, "run", [left, right, true, null]]) + assert.equal(M.toString(expr), "a == b") + assert.equal(M.toString(makeSameExpr(left, right, false, null)), "a != b") + assert.equal(expr.asTerm(), term`SameExpr(NounExpr("a"), NounExpr("b"), true)`) + +def test_getExpr(assert): + def body := makeNounExpr("a", null) + def indices := [makeNounExpr("b", null), makeNounExpr("c", null)] + def expr := makeGetExpr(body, indices, null) + assert.equal(M.toString(expr), "a[b, c]") + assert.equal(expr.asTerm(), term`GetExpr(NounExpr("a"), [NounExpr("b"), NounExpr("c")])`) + +def test_andExpr(assert): + def [left, right] := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeAndExpr(left, right, null) + assert.equal(expr._uncall(), [makeAndExpr, "run", [left, right, null]]) + assert.equal(M.toString(expr), "a && b") + assert.equal(expr.asTerm(), term`AndExpr(NounExpr("a"), NounExpr("b"))`) + +def test_orExpr(assert): + def [left, right] := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeOrExpr(left, right, null) + assert.equal(expr._uncall(), [makeOrExpr, "run", [left, right, null]]) + assert.equal(M.toString(expr), "a || b") + assert.equal(expr.asTerm(), term`OrExpr(NounExpr("a"), NounExpr("b"))`) + +def test_matchBindExpr(assert): + def [spec, patt] := [makeNounExpr("a", null), makeFinalPattern(makeNounExpr("b", null), null, null)] + def expr := makeMatchBindExpr(spec, patt, null) + assert.equal(expr._uncall(), [makeMatchBindExpr, "run", [spec, patt, null]]) + assert.equal(M.toString(expr), "a =~ b") + assert.equal(expr.asTerm(), term`MatchBindExpr(NounExpr("a"), FinalPattern(NounExpr("b"), null))`) + +def test_mismatchExpr(assert): + def [spec, patt] := [makeNounExpr("a", null), makeFinalPattern(makeNounExpr("b", null), null, null)] + def expr := makeMismatchExpr(spec, patt, null) + assert.equal(expr._uncall(), [makeMismatchExpr, "run", [spec, patt, null]]) + assert.equal(M.toString(expr), "a !~ b") + assert.equal(expr.asTerm(), term`MismatchExpr(NounExpr("a"), FinalPattern(NounExpr("b"), null))`) + +def test_binaryExpr(assert): + def [left, right] := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeBinaryExpr(left, "+", right, null) + assert.equal(expr._uncall(), [makeBinaryExpr, "run", [left, "+", right, null]]) + assert.equal(M.toString(expr), "a + b") + assert.equal(expr.asTerm(), term`BinaryExpr(NounExpr("a"), "+", NounExpr("b"))`) + +def test_prefixExpr(assert): + def val := makeNounExpr("a", null) + def expr := makePrefixExpr("!", val, null) + assert.equal(expr._uncall(), [makePrefixExpr, "run", ["!", val, null]]) + assert.equal(M.toString(expr), "!a") + assert.equal(expr.asTerm(), term`PrefixExpr("!", NounExpr("a"))`) + +def test_coerceExpr(assert): + def [specimen, guard] := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeCoerceExpr(specimen, guard, null) + assert.equal(expr._uncall(), [makeCoerceExpr, "run", [specimen, guard, null]]) + assert.equal(M.toString(expr), "a :b") + assert.equal(expr.asTerm(), term`CoerceExpr(NounExpr("a"), NounExpr("b"))`) + +def test_curryExpr(assert): + def receiver := makeNounExpr("a", null) + def expr := makeCurryExpr(receiver, "foo", false, null) + assert.equal(expr._uncall(), [makeCurryExpr, "run", [receiver, "foo", false, null]]) + assert.equal(M.toString(expr), "a.foo") + assert.equal(M.toString(makeCurryExpr(receiver, "foo", true, null)), "a <- foo") + assert.equal(expr.asTerm(), term`CurryExpr(NounExpr("a"), "foo", false)`) + +def test_exitExpr(assert): + def val := makeNounExpr("a", null) + def expr := makeExitExpr("continue", val, null) + assert.equal(expr._uncall(), [makeExitExpr, "run", ["continue", val, null]]) + assert.equal(M.toString(expr), "continue a") + assert.equal(expr.asTerm(), term`ExitExpr("continue", NounExpr("a"))`) + assert.equal(M.toString(makeExitExpr("break", null, null)), "break") + +def test_forwardExpr(assert): + def patt := makeFinalPattern(makeNounExpr("a", null), null, null) + def expr := makeForwardExpr(patt, null) + assert.equal(expr._uncall(), [makeForwardExpr, "run", [patt, null]]) + assert.equal(M.toString(expr), "def a") + assert.equal(expr.asTerm(), term`ForwardExpr(FinalPattern(NounExpr("a"), null))`) + +def test_defExpr(assert): + def patt := makeFinalPattern(makeNounExpr("a", null), null, null) + def ej := makeNounExpr("ej", null) + def body := makeLiteralExpr(1, null) + def expr := makeDefExpr(patt, ej, body, null) + assert.equal(expr._uncall(), [makeDefExpr, "run", [patt, ej, body, null]]) + assert.equal(M.toString(expr), "def a exit ej := 1") + assert.equal(M.toString(makeDefExpr(patt, null, body, null)), "def a := 1") + assert.equal(M.toString(makeDefExpr(makeVarPattern(makeNounExpr("a", null), null, null), null, body, null)), "var a := 1") + assert.equal(expr.asTerm(), term`DefExpr(FinalPattern(NounExpr("a"), null), NounExpr("ej"), LiteralExpr(1))`) + +def test_assignExpr(assert): + def lval := makeNounExpr("a", null) + def body := makeLiteralExpr(1, null) + def expr := makeAssignExpr(lval, body, null) + assert.equal(expr._uncall(), [makeAssignExpr, "run", [lval, body, null]]) + assert.equal(M.toString(expr), "a := 1") + assert.equal(expr.asTerm(), term`AssignExpr(NounExpr("a"), LiteralExpr(1))`) + assert.equal(M.toString(makeAssignExpr(makeGetExpr(lval, [makeLiteralExpr(0, null)], null), body, null)), "a[0] := 1") + + +def test_verbAssignExpr(assert): + def lval := makeNounExpr("a", null) + def body := makeLiteralExpr(1, null) + def expr := makeVerbAssignExpr("blee", lval, [body], null) + assert.equal(expr._uncall(), [makeVerbAssignExpr, "run", ["blee", lval, [body], null]]) + assert.equal(M.toString(expr), "a blee= (1)") + assert.equal(expr.asTerm(), term`VerbAssignExpr("blee", NounExpr("a"), [LiteralExpr(1)])`) + assert.equal(M.toString(makeVerbAssignExpr("blee", makeGetExpr(lval, [makeLiteralExpr(0, null)], null), [body], null)), "a[0] blee= (1)") + +def test_augAssignExpr(assert): + def lval := makeNounExpr("a", null) + def body := makeLiteralExpr(1, null) + def expr := makeAugAssignExpr("+", lval, body, null) + assert.equal(expr._uncall(), [makeAugAssignExpr, "run", ["+", lval, body, null]]) + assert.equal(M.toString(expr), "a += 1") + assert.equal(expr.asTerm(), term`AugAssignExpr("+", NounExpr("a"), LiteralExpr(1))`) + assert.equal(M.toString(makeAugAssignExpr(">>", makeGetExpr(lval, [makeLiteralExpr(0, null)], null), body, null)), "a[0] >>= 1") + +def test_ifExpr(assert): + def [test, consq, alt] := [makeNounExpr("a", null), makeNounExpr("b", null), makeNounExpr("c", null)] + def expr := makeIfExpr(test, consq, alt, null) + assert.equal(expr._uncall(), [makeIfExpr, "run", [test, consq, alt, null]]) + assert.equal(M.toString(expr), "if (a):\n b\nelse:\n c") + assert.equal(M.toString(makeIfExpr(test, consq, null, null)), "if (a):\n b") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := if (a) {\n b\n} else {\n c\n}") + assert.equal(expr.asTerm(), term`IfExpr(NounExpr("a"), NounExpr("b"), NounExpr("c"))`) + +def test_catchExpr(assert): + def [attempt, pattern, catcher] := [makeNounExpr("a", null), makeFinalPattern(makeNounExpr("b", null), null, null), makeNounExpr("c", null)] + def expr := makeCatchExpr(attempt, pattern, catcher, null) + assert.equal(expr._uncall(), [makeCatchExpr, "run", [attempt, pattern, catcher, null]]) + assert.equal(M.toString(expr), "try:\n a\ncatch b:\n c") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := try {\n a\n} catch b {\n c\n}") + assert.equal(expr.asTerm(), term`CatchExpr(NounExpr("a"), FinalPattern(NounExpr("b"), null), NounExpr("c"))`) + +def test_finallyExpr(assert): + def [attempt, catcher] := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeFinallyExpr(attempt, catcher, null) + assert.equal(expr._uncall(), [makeFinallyExpr, "run", [attempt, catcher, null]]) + assert.equal(M.toString(expr), "try:\n a\nfinally:\n b") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := try {\n a\n} finally {\n b\n}") + assert.equal(expr.asTerm(), term`FinallyExpr(NounExpr("a"), NounExpr("b"))`) + +def test_tryExpr(assert): + def [body, catchers, fin] := [makeNounExpr("a", null), + [makeCatcher(makeFinalPattern(makeNounExpr("b", null), null, null), + makeNounExpr("c", null), null), + makeCatcher(makeFinalPattern(makeNounExpr("d", null), null, null), + makeNounExpr("e", null), null)], + makeNounExpr("f", null)] + def expr := makeTryExpr(body, catchers, fin, null) + assert.equal(expr._uncall(), [makeTryExpr, "run", [body, catchers, fin, null]]) + assert.equal(M.toString(expr), "try:\n a\ncatch b:\n c\ncatch d:\n e\nfinally:\n f") + assert.equal(M.toString(makeTryExpr(body, catchers, null, null)), "try:\n a\ncatch b:\n c\ncatch d:\n e") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := try {\n a\n} catch b {\n c\n} catch d {\n e\n} finally {\n f\n}") + assert.equal(expr.asTerm(), term`TryExpr(NounExpr("a"), [Catcher(FinalPattern(NounExpr("b"), null), NounExpr("c")), Catcher(FinalPattern(NounExpr("d"), null), NounExpr("e"))], NounExpr("f"))`) + +def test_escapeExpr(assert): + def [ejPatt, body, catchPattern, catchBlock] := [makeFinalPattern(makeNounExpr("a", null), null, null), makeNounExpr("b", null), makeFinalPattern(makeNounExpr("c", null), null, null), makeNounExpr("d", null)] + def expr := makeEscapeExpr(ejPatt, body, catchPattern, catchBlock, null) + assert.equal(expr._uncall(), [makeEscapeExpr, "run", [ejPatt, body, catchPattern, catchBlock, null]]) + assert.equal(M.toString(expr), "escape a:\n b\ncatch c:\n d") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := escape a {\n b\n} catch c {\n d\n}") + assert.equal(M.toString(makeEscapeExpr(ejPatt, body, null, null, null)), "escape a:\n b") + assert.equal(expr.asTerm(), term`EscapeExpr(FinalPattern(NounExpr("a"), null), NounExpr("b"), FinalPattern(NounExpr("c"), null), NounExpr("d"))`) + +def test_switchExpr(assert): + def matchers := [ + makeMatcher(makeFinalPattern(makeNounExpr("b", null), makeNounExpr("c", null), null), + makeLiteralExpr(1, null), null), + makeMatcher(makeFinalPattern(makeNounExpr("d", null), null, null), + makeLiteralExpr(2, null), null)] + def specimen := makeNounExpr("a", null) + def expr := makeSwitchExpr(specimen, matchers, null) + assert.equal(expr._uncall(), [makeSwitchExpr, "run", [specimen, matchers, null]]) + assert.equal(M.toString(expr), "switch (a):\n match b :c:\n 1\n\n match d:\n 2\n") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := switch (a) {\n match b :c {\n 1\n }\n\n match d {\n 2\n }\n}") + assert.e + +def test_whenExpr(assert): + def args := [makeNounExpr("a", null), makeNounExpr("b", null)] + def body := makeNounExpr("c", null) + def catchers := [makeCatcher(makeFinalPattern(makeNounExpr("d", null), null, null), + makeNounExpr("e", null), null), + makeCatcher(makeFinalPattern(makeNounExpr("f", null), null, null), + makeNounExpr("g", null), null)] + def finallyBlock := makeNounExpr("h", null) + + def expr := makeWhenExpr(args, body, catchers, finallyBlock, null) + assert.equal(expr._uncall(), [makeWhenExpr, "run", [args, body, catchers, finallyBlock, null]]) + assert.equal(M.toString(expr), "when (a, b) ->\n c\ncatch d:\n e\ncatch f:\n g\nfinally:\n h") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := when (a, b) -> {\n c\n} catch d {\n e\n} catch f {\n g\n} finally {\n h\n}") + assert.equal(expr.asTerm(), term`WhenExpr([NounExpr("a"), NounExpr("b")], NounExpr("c"), [Catcher(FinalPattern(NounExpr("d"), null), NounExpr("e")), Catcher(FinalPattern(NounExpr("f"), null), NounExpr("g"))], NounExpr("h"))`) + +def test_whileExpr(assert): + def a := makeNounExpr("a", null) + def b := makeNounExpr("b", null) + def catcher := makeCatcher(makeFinalPattern(makeNounExpr("c", null), null, null), makeNounExpr("d", null), null) + def expr := makeWhileExpr(a, b, catcher, null) + assert.equal(expr._uncall(), [makeWhileExpr, "run", [a, b, catcher, null]]) + assert.equal(M.toString(expr), "while (a):\n b\ncatch c:\n d") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := while (a) {\n b\n} catch c {\n d\n}") + assert.equal(expr.asTerm(), term`WhileExpr(NounExpr("a"), NounExpr("b"), Catcher(FinalPattern(NounExpr("c"), null), NounExpr("d")))`) + +def test_hideExpr(assert): + def body := makeNounExpr("a", null) + def expr := makeHideExpr(body, null) + assert.equal(expr._uncall(), [makeHideExpr, "run", [body, null]]) + assert.equal(M.toString(expr), "{\n a\n}") + assert.equal(expr.asTerm(), term`HideExpr(NounExpr("a"))`) + +def test_listExpr(assert): + def items := [makeNounExpr("a", null), makeNounExpr("b", null)] + def expr := makeListExpr(items, null) + assert.equal(expr._uncall(), [makeListExpr, "run", [items, null]]) + assert.equal(M.toString(expr), "[a, b]") + assert.equal(expr.asTerm(), term`ListExpr([NounExpr("a"), NounExpr("b")])`) + +def test_listComprehensionExpr(assert): + def iterable := makeNounExpr("a", null) + def filter := makeNounExpr("b", null) + def [k, v] := [makeFinalPattern(makeNounExpr("k", null), null, null), makeFinalPattern(makeNounExpr("v", null), null, null)] + def body := makeNounExpr("c", null) + def expr := makeListComprehensionExpr(iterable, filter, k, v, body, null) + assert.equal(expr._uncall(), [makeListComprehensionExpr, "run", [iterable, filter, k, v, body, null]]) + assert.equal(M.toString(expr), "[for k => v in (a) if (b) c]") + assert.equal(M.toString(makeListComprehensionExpr(iterable, null, null, v, body, null)), + "[for v in (a) c]") + assert.equal(expr.asTerm(), term`ListComprehensionExpr(NounExpr("a"), NounExpr("b"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), NounExpr("c"))`) + +def test_mapExpr(assert): + def k := makeNounExpr("k", null) + def v := makeNounExpr("v", null) + def exprt := makeNounExpr("a", null) + def pair1 := makeMapExprAssoc(k, v, null) + def pair2 := makeMapExprExport(exprt, null) + def expr := makeMapExpr([pair1, pair2], null) + assert.equal(expr._uncall(), [makeMapExpr, "run", [[pair1, pair2], null]]) + assert.equal(M.toString(expr), "[k => v, => a]") + assert.equal(expr.asTerm(), term`MapExpr([MapExprAssoc(NounExpr("k"), NounExpr("v")), MapExprExport(NounExpr("a"))])`) + +def test_mapComprehensionExpr(assert): + def iterable := makeNounExpr("a", null) + def filter := makeNounExpr("b", null) + def [k, v] := [makeFinalPattern(makeNounExpr("k", null), null, null), makeFinalPattern(makeNounExpr("v", null), null, null)] + def bodyk := makeNounExpr("k1", null) + def bodyv := makeNounExpr("v1", null) + def expr := makeMapComprehensionExpr(iterable, filter, k, v, bodyk, bodyv, null) + assert.equal(expr._uncall(), [makeMapComprehensionExpr, "run", [iterable, filter, k, v, bodyk, bodyv, null]]) + assert.equal(M.toString(expr), "[for k => v in (a) if (b) k1 => v1]") + assert.equal(expr.asTerm(), term`MapComprehensionExpr(NounExpr("a"), NounExpr("b"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), NounExpr("k1"), NounExpr("v1"))`) + assert.equal(M.toString(makeMapComprehensionExpr(iterable, null, null, v, bodyk, bodyv, null)), + "[for v in (a) k1 => v1]") + +def test_forExpr(assert): + def iterable := makeNounExpr("a", null) + def [k, v] := [makeFinalPattern(makeNounExpr("k", null), null, null), makeFinalPattern(makeNounExpr("v", null), null, null)] + def body := makeNounExpr("b", null) + def expr := makeForExpr(iterable, k, v, body, null, null, null) + assert.equal(expr._uncall(), [makeForExpr, "run", [iterable, k, v, body, null, null, null]]) + assert.equal(M.toString(expr), "for k => v in a:\n b") + assert.equal(M.toString(makeForExpr(iterable, null, v, body, null, null, null)), + "for v in a:\n b") + assert.equal(M.toString(makeForExpr(iterable, null, v, body, makeFinalPattern(makeNounExpr("p", null), null, null), makeLiteralExpr(1, null), null)), + "for v in a:\n b\ncatch p:\n 1") + assert.equal(expr.asTerm(), term`ForExpr(NounExpr("a"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), NounExpr("b"), null, null)`) + +def test_objectExpr(assert): + def objName := makeFinalPattern(makeNounExpr("a", null), null, null) + def asExpr := makeNounExpr("x", null) + def auditors := [makeNounExpr("b", null), makeNounExpr("c", null)] + def methodParams := [makeFinalPattern(makeNounExpr("e", null), null, null), + makeFinalPattern(makeNounExpr("f", null), null, null)] + def methGuard := makeNounExpr("g", null) + def methBody := makeNounExpr("h", null) + def method1 := makeMethod("method d", "d", methodParams, methGuard, methBody, null) + def method2 := makeTo(null, "i", [], null, makeNounExpr("j", null), null) + def matchPatt := makeFinalPattern(makeNounExpr("k", null), null, null) + def matchBody := makeNounExpr("l", null) + def matcher := makeMatcher(matchPatt, matchBody, null) + def script := makeScript(null, [method1, method2], [matcher], null) + def expr := makeObjectExpr("blee", objName, asExpr, auditors, script, null) + assert.equal(expr._uncall(), + [makeObjectExpr, "run", ["blee", objName, asExpr, auditors, script, null]]) + assert.equal(script._uncall(), + [makeScript, "run", [null, [method1, method2], [matcher], null]]) + assert.equal(method1._uncall(), + [makeMethod, "run", ["method d", "d", methodParams, methGuard, methBody, null]]) + assert.equal(matcher._uncall(), + [makeMatcher, "run", [matchPatt, matchBody, null]]) + assert.equal(M.toString(expr), "object a as x implements b, c:\n \"blee\"\n method d(e, f) :g:\n \"method d\"\n h\n\n to i():\n j\n\n match k:\n l\n") + def noDoco := makeObjectExpr(null, objName, asExpr, auditors, script, null) + assert.equal(M.toString(noDoco), "object a as x implements b, c:\n method d(e, f) :g:\n \"method d\"\n h\n\n to i():\n j\n\n match k:\n l\n") + assert.equal(expr.asTerm(), + term`ObjectExpr("blee", + FinalPattern(NounExpr("a"), null), + NounExpr("x"), + [NounExpr("b"), NounExpr("c")], + Script(null, + [Method("method d", "d", + [FinalPattern(NounExpr("e"), null), FinalPattern(NounExpr("f"), null)], + NounExpr("g"), NounExpr("h")), + To(null, "i", [], null, + NounExpr("j"))], + [Matcher(FinalPattern(NounExpr("k"), null), NounExpr("l"))]))`) + +def test_functionScript(assert): + def funName := makeFinalPattern(makeNounExpr("a", null), null, null) + def asExpr := makeNounExpr("x", null) + def auditors := [makeNounExpr("b", null), makeNounExpr("c", null)] + def patterns := [makeFinalPattern(makeNounExpr("d", null), null, null), + makeFinalPattern(makeNounExpr("e", null), null, null)] + def guard := makeNounExpr("g", null) + def body := makeNounExpr("f", null) + def funBody := makeFunctionScript(patterns, guard, body, null) + def expr := makeObjectExpr("bloo", funName, asExpr, auditors, funBody, null) + assert.equal(funBody._uncall(), [makeFunctionScript, "run", [patterns, guard, body, null]]) + assert.equal(M.toString(expr), "def a(d, e) :g as x implements b, c:\n \"bloo\"\n f\n") + assert.equal(expr.asTerm(), term`ObjectExpr("bloo", FinalPattern(NounExpr("a"), null), NounExpr("x"), [NounExpr("b"), NounExpr("c")], FunctionScript([FinalPattern(NounExpr("d"), null), FinalPattern(NounExpr("e"), null)], NounExpr("g"), NounExpr("f")))`) + +def test_functionExpr(assert): + def patterns := [makeFinalPattern(makeNounExpr("a", null), null, null), + makeFinalPattern(makeNounExpr("b", null), null, null)] + def body := makeNounExpr("c", null) + def expr := makeFunctionExpr(patterns, body, null) + assert.equal(expr._uncall(), [makeFunctionExpr, "run", [patterns, body, null]]) + assert.equal(M.toString(expr), "fn a, b {\n c\n}") + assert.equal(M.toString(makeDefExpr(makeIgnorePattern(null, null), null, expr, null)), + "def _ := fn a, b {\n c\n}") + + +def test_interfaceExpr(assert): + def name := makeFinalPattern(makeNounExpr("IA", null), null, null) + def guard := makeNounExpr("B", null) + def paramA := makeParamDesc("a", guard, null) + def paramC := makeParamDesc("c", null, null) + def messageD := makeMessageDesc("foo", "d", [paramA, paramC], guard, null) + def messageJ := makeMessageDesc(null, "j", [], null, null) + def stamp := makeFinalPattern(makeNounExpr("h", null), null, null) + def [e, f] := [makeNounExpr("e", null), makeNounExpr("f", null)] + def [ib, ic] := [makeNounExpr("IB", null), makeNounExpr("IC", null)] + def expr := makeInterfaceExpr("blee", name, stamp, [ib, ic], [e, f], [messageD, messageJ], null) + assert.equal(paramA._uncall(), [makeParamDesc, "run", ["a", guard, null]]) + assert.equal(messageD._uncall(), [makeMessageDesc, "run", ["foo", "d", [paramA, paramC], guard, null]]) + assert.equal(expr._uncall(), [makeInterfaceExpr, "run", ["blee", name, stamp, [ib, ic], [e, f], [messageD, messageJ], null]]) + assert.equal(M.toString(expr), "interface IA guards h extends IB, IC implements e, f:\n \"blee\"\n to d(a :B, c) :B:\n \"foo\"\n\n to j()\n") + assert.equal(expr.asTerm(), term`InterfaceExpr("blee", FinalPattern(NounExpr("IA"), null), FinalPattern(NounExpr("h"), null), [NounExpr("IB"), NounExpr("IC")], [NounExpr("e"), NounExpr("f")], [MessageDesc("foo", "d", [ParamDesc("a", NounExpr("B")), ParamDesc("c", null)], NounExpr("B")), MessageDesc(null, "j", [], null)])`) + +def test_functionInterfaceExpr(assert): + def name := makeFinalPattern(makeNounExpr("IA", null), null, null) + def guard := makeNounExpr("B", null) + def paramA := makeParamDesc("a", guard, null) + def paramC := makeParamDesc("c", null, null) + def stamp := makeFinalPattern(makeNounExpr("d", null), null, null) + def [e, f] := [makeNounExpr("e", null), makeNounExpr("f", null)] + def [ib, ic] := [makeNounExpr("IB", null), makeNounExpr("IC", null)] + def msg := makeMessageDesc(null, "run", [paramA, paramC], guard, null) + def expr := makeFunctionInterfaceExpr("blee", name, stamp, [ib, ic], [e, f], msg, null) + assert.equal(expr._uncall(), [makeFunctionInterfaceExpr, "run", ["blee", name, stamp, [ib, ic], [e, f], msg, null]]) + assert.equal(M.toString(expr), "interface IA guards d extends IB, IC implements e, f (a :B, c) :B:\n \"blee\"\n") + assert.equal(expr.asTerm(), term`FunctionInterfaceExpr("blee", FinalPattern(NounExpr("IA"), null), FinalPattern(NounExpr("d"), null), [NounExpr("IB"), NounExpr("IC")], [NounExpr("e"), NounExpr("f")], MessageDesc(null, "run", [ParamDesc("a", NounExpr("B")), ParamDesc("c", null)], NounExpr("B")))`) + +def test_quasiParserExpr(assert): + def hole1 := makeQuasiExprHole(makeNounExpr("a", null), null) + def hole2 := makeQuasiExprHole(makeBinaryExpr(makeLiteralExpr(3, null), "+", makeLiteralExpr(4, null), null), null) + def hole3 := makeQuasiPatternHole(makeFinalPattern(makeNounExpr("b", null), null, null), null) + def text1 := makeQuasiText("hello ", null) + def text2 := makeQuasiText(", your number is ", null) + def text3 := makeQuasiText(". Also, ", null) + def expr := makeQuasiParserExpr("blee", [text1, hole1, text2, hole2, text3, hole3], null) + assert.equal(expr._uncall(), [makeQuasiParserExpr, "run", ["blee", [text1, hole1, text2, hole2, text3, hole3], null]]) + assert.equal(M.toString(expr), "blee`hello $a, your number is ${3 + 4}. Also, @b`") + assert.equal(M.toString(makeQuasiParserExpr("blee", [makeQuasiExprHole(makeNounExpr("a", null), null), makeQuasiText("b", null)], null)), "blee`${a}b`") + assert.equal(expr.asTerm(), term`QuasiParserExpr("blee", [QuasiText("hello "), QuasiExprHole(NounExpr("a")), QuasiText(", your number is "), QuasiExprHole(BinaryExpr(LiteralExpr(3), "+", LiteralExpr(4))), QuasiText(". Also, "), QuasiPatternHole(FinalPattern(NounExpr("b"), null))])`) + +def test_valueHoleExpr(assert): + def expr := makeValueHoleExpr(2, null) + assert.equal(expr._uncall(), [makeValueHoleExpr, "run", [2, null]]) + assert.equal(M.toString(expr), "${value-hole 2}") + assert.equal(expr.asTerm(), term`ValueHoleExpr(2)`) + +def test_patternHoleExpr(assert): + def expr := makePatternHoleExpr(2, null) + assert.equal(expr._uncall(), [makePatternHoleExpr, "run", [2, null]]) + assert.equal(M.toString(expr), "${pattern-hole 2}") + assert.equal(expr.asTerm(), term`PatternHoleExpr(2)`) + +def test_patternHolePattern(assert): + def expr := makePatternHolePattern(2, null) + assert.equal(expr._uncall(), [makePatternHolePattern, "run", [2, null]]) + assert.equal(M.toString(expr), "@{pattern-hole 2}") + assert.equal(expr.asTerm(), term`PatternHolePattern(2)`) + +def test_valueHolePattern(assert): + def expr := makeValueHolePattern(2, null) + assert.equal(expr._uncall(), [makeValueHolePattern, "run", [2, null]]) + assert.equal(M.toString(expr), "@{value-hole 2}") + assert.equal(expr.asTerm(), term`ValueHolePattern(2)`) + +def test_finalPattern(assert): + def [name, guard] := [makeNounExpr("blee", null), makeNounExpr("Int", null)] + assert.raises(fn {makeFinalPattern(name, name, null)}) + def patt := makeFinalPattern(name, guard, null) + assert.equal(patt._uncall(), [makeFinalPattern, "run", [name, guard, null]]) + assert.equal(M.toString(patt), "blee :Int") + assert.equal(patt.asTerm(), term`FinalPattern(NounExpr("blee"), NounExpr("Int"))`) + +def test_bindPattern(assert): + def name := makeNounExpr("blee", null) + def patt := makeBindPattern(name, null) + assert.equal(patt._uncall(), [makeBindPattern, "run", [name, null]]) + assert.equal(M.toString(patt), "bind blee") + assert.equal(patt.asTerm(), term`BindPattern(NounExpr("blee")))`) + +def test_bindingPattern(assert): + def name := makeNounExpr("blee", null) + def patt := makeBindingPattern(name, null) + assert.equal(patt._uncall(), [makeBindingPattern, "run", [name, null]]) + assert.equal(M.toString(patt), "&&blee") + assert.equal(patt.asTerm(), term`BindingPattern(NounExpr("blee"))`) + +def test_slotPattern(assert): + def name := makeNounExpr("blee", null) + def guard := makeNounExpr("FinalSlot", null) + def patt := makeSlotPattern(name, guard, null) + assert.equal(patt._uncall(), [makeSlotPattern, "run", [name, guard, null]]) + assert.equal(M.toString(patt), "&blee :FinalSlot") + assert.equal(patt.asTerm(), term`SlotPattern(NounExpr("blee"), NounExpr("FinalSlot"))`) + +def test_ignorePattern(assert): + def guard := makeNounExpr("List", null) + def patt := makeIgnorePattern(guard, null) + assert.equal(patt._uncall(), [makeIgnorePattern, "run", [guard, null]]) + assert.equal(M.toString(patt), "_ :List") + assert.equal(patt.asTerm(), term`IgnorePattern(NounExpr("List"))`) + assert.equal(M.toString(makeIgnorePattern(null, null)), "_") + +def test_varPattern(assert): + def [name, guard] := [makeNounExpr("blee", null), makeNounExpr("Int", null)] + def patt := makeVarPattern(name, guard, null) + assert.equal(patt._uncall(), [makeVarPattern, "run", [name, guard, null]]) + assert.equal(M.toString(patt), "var blee :Int") + assert.equal(patt.asTerm(), term`VarPattern(NounExpr("blee"), NounExpr("Int"))`) + +def test_listPattern(assert): + def patts := [makeFinalPattern(makeNounExpr("a", null), null, null), makeVarPattern(makeNounExpr("b", null), null, null)] + def tail := makeFinalPattern(makeNounExpr("tail", null), null, null) + def patt := makeListPattern(patts, tail, null) + assert.equal(patt._uncall(), [makeListPattern, "run", [patts, tail, null]]) + assert.equal(M.toString(patt), "[a, var b] + tail") + assert.equal(M.toString(makeListPattern(patts, null, null)), "[a, var b]") + assert.equal(patt.asTerm(), term`ListPattern([FinalPattern(NounExpr("a"), null), VarPattern(NounExpr("b"), null)], FinalPattern(NounExpr("tail"), null))`) + +def test_mapPattern(assert): + def k1 := makeLiteralExpr("a", null) + def v1 := makeFinalPattern(makeNounExpr("b", null), null, null) + def k2 := makeNounExpr("c", null) + def v2 := makeFinalPattern(makeNounExpr("d", null), null, null) + def default := makeNounExpr("e", null) + def v3 := makeFinalPattern(makeNounExpr("f", null), null, null) + def pair1 := makeMapPatternRequired(makeMapPatternAssoc(k1, v1, null), null) + def pair2 := makeMapPatternDefault(makeMapPatternAssoc(k2, v2, null), default, null) + def pair3 := makeMapPatternRequired(makeMapPatternImport(v3, null), null) + def tail := makeFinalPattern(makeNounExpr("tail", null), null, null) + def patt := makeMapPattern([pair1, pair2, pair3], tail, null) + assert.equal(patt._uncall(), [makeMapPattern, "run", [[pair1, pair2, pair3], tail, null]]) + assert.equal(M.toString(patt), "[\"a\" => b, (c) => d := (e), => f] | tail") + assert.equal(patt.asTerm(), + term`MapPattern([ + MapPatternRequired(MapPatternAssoc(LiteralExpr("a"), FinalPattern(NounExpr("b"), null))), + MapPatternDefault(MapPatternAssoc(NounExpr("c"), FinalPattern(NounExpr("d"), null)), NounExpr("e")), + MapPatternRequired(MapPatternImport(FinalPattern(NounExpr("f"), null)))], + FinalPattern(NounExpr("tail"), null))`) + +def test_viaPattern(assert): + def subpatt := makeFinalPattern(makeNounExpr("a", null), null, null) + def expr := makeNounExpr("b", null) + def patt := makeViaPattern(expr, subpatt, null) + assert.equal(patt._uncall(), [makeViaPattern, "run", [expr, subpatt, null]]) + assert.equal(M.toString(patt), "via (b) a") + assert.equal(patt.asTerm(), term`ViaPattern(NounExpr("b"), FinalPattern(NounExpr("a"), null))`) + +def test_suchThatPattern(assert): + def subpatt := makeFinalPattern(makeNounExpr("a", null), null, null) + def expr := makeNounExpr("b", null) + def patt := makeSuchThatPattern(subpatt, expr, null) + assert.equal(patt._uncall(), [makeSuchThatPattern, "run", [subpatt, expr, null]]) + assert.equal(M.toString(patt), "a ? (b)") + assert.equal(patt.asTerm(), term`SuchThatPattern(FinalPattern(NounExpr("a"), null), NounExpr("b"))`) + +def test_samePattern(assert): + def expr := makeNounExpr("a", null) + def patt := makeSamePattern(expr, true, null) + assert.equal(patt._uncall(), [makeSamePattern, "run", [expr, true, null]]) + assert.equal(M.toString(patt), "==a") + assert.equal(M.toString(makeSamePattern(expr, false, null)), "!=a") + assert.equal(patt.asTerm(), term`SamePattern(NounExpr("a"), true)`) + +def test_quasiParserPattern(assert): + def hole1 := makeQuasiPatternHole(makeFinalPattern(makeNounExpr("a", null), null, null), null) + def hole2 := makeQuasiPatternHole(makeListPattern([makeFinalPattern(makeNounExpr("b", null), null, null), makeFinalPattern(makeNounExpr("c", null), null, null)], null, null), null) + def hole3 := makeQuasiExprHole(makeNounExpr("d", null), null) + def text1 := makeQuasiText("hello ", null) + def text2 := makeQuasiText(", your number is ", null) + def text3 := makeQuasiText(". Also, ", null) + def expr := makeQuasiParserPattern("blee", [text1, hole1, text2, hole2, text3, hole3], null) + assert.equal(expr._uncall(), [makeQuasiParserPattern, "run", ["blee", [text1, hole1, text2, hole2, text3, hole3], null]]) + assert.equal(M.toString(expr), "blee`hello @a, your number is @{[b, c]}. Also, $d`") + assert.equal(M.toString(makeQuasiParserPattern("blee", [makeQuasiPatternHole(makeFinalPattern(makeNounExpr("a", null), null, null), null), makeQuasiText("b", null)], null)), "blee`@{a}b`") + assert.equal(expr.asTerm(), term`QuasiParserPattern("blee", [QuasiText("hello "), QuasiPatternHole(FinalPattern(NounExpr("a"), null)), QuasiText(", your number is "), QuasiPatternHole(ListPattern([FinalPattern(NounExpr("b"), null), FinalPattern(NounExpr("c"), null)], null)), QuasiText(". Also, "), QuasiExprHole(NounExpr("d"))])`) + +unittest([test_literalExpr, test_nounExpr, test_tempNounExpr, test_bindingExpr, + test_slotExpr, test_metaContextExpr, test_metaStateExpr, + test_seqExpr, test_module, test_defExpr, test_methodCallExpr, + test_funCallExpr, test_compareExpr, test_listExpr, + test_listComprehensionExpr, test_mapExpr, test_mapComprehensionExpr, + test_forExpr, test_functionScript, test_functionExpr, + test_sendExpr, test_funSendExpr, + test_interfaceExpr, test_functionInterfaceExpr, + test_assignExpr, test_verbAssignExpr, test_augAssignExpr, + test_andExpr, test_orExpr, test_matchBindExpr, test_mismatchExpr, + test_switchExpr, test_whenExpr, test_whileExpr, + test_binaryExpr, test_quasiParserExpr, test_rangeExpr, test_sameExpr, + test_ifExpr, test_catchExpr, test_finallyExpr, test_tryExpr, + test_escapeExpr, test_hideExpr, test_objectExpr, test_forwardExpr, + test_valueHoleExpr, test_patternHoleExpr, test_getExpr, + test_prefixExpr, test_coerceExpr, test_curryExpr, test_exitExpr, + test_finalPattern, test_ignorePattern, test_varPattern, + test_listPattern, test_mapPattern, test_bindingPattern, + test_slotPattern, test_samePattern, test_quasiParserPattern, + test_viaPattern, test_suchThatPattern, test_bindPattern, + test_valueHolePattern, test_patternHolePattern]) diff --git a/monte/src/monte_expander.mt b/monte/src/monte_expander.mt new file mode 100644 index 0000000..291bcc8 --- /dev/null +++ b/monte/src/monte_expander.mt @@ -0,0 +1,1351 @@ +module parseModule, makeMonteLexer, astBuilder, unittest +export (expand) + +/** Maybe Python isn't so bad after all. */ +object zip: + match [=="run", iterables]: + def _its := [].diverge() + for it in iterables: + _its.push(it._makeIterator()) + def its := _its.snapshot() + object ziperator: + to _makeIterator(): + return ziperator + to next(ej): + def ks := [].diverge() + def vs := [].diverge() + for it in its: + def [k, v] := it.next(ej) + ks.push(k) + vs.push(v) + return [ks.snapshot(), vs.snapshot()] + + +def reversed(it): + def items := __makeList.fromIterable(it) + return items.reversed() + +def buildQuasi(builder, name, inputs): + def parts := ["parts" => [].diverge(), + "expr" => [].diverge(), + "patt" => [].diverge()] + for [typ, node, span] in inputs: + if (typ == "expr"): + parts["parts"].push(builder.MethodCallExpr( + builder.NounExpr(name, span), + "valueHole", + [builder.LiteralExpr(parts["expr"].size(), span)], + span)) + parts["expr"].push(node) + else if (typ == "patt"): + parts["parts"].push(builder.MethodCallExpr( + builder.NounExpr(name, span), + "patternHole", + [builder.LiteralExpr(parts["patt"].size(), span)], + span)) + parts["patt"].push(node) + else if (typ == "text"): + parts["parts"].push(node) + return [p.snapshot() for p in parts] + +def putVerb(verb, fail, span): + switch (verb): + match =="get": + return "put" + match =="run": + return "setRun" + match _: + fail(["Unsupported verb for assignment", span]) + +def renameCycles(node, renamings, builder): + def renamer(node, maker, args, span): + return switch (node.getNodeName()) { + match =="NounExpr" { + renamings.fetch(args[0], fn {node}) + } + match _ { + M.call(maker, "run", args + [span]) + } + } + return node.transform(renamer) + +def expand(node, builder, fail): + def emitList(items, span): + return builder.MethodCallExpr( + builder.NounExpr("__makeList", span), + "run", items, span) + + def makeSlotPatt(n, span): + return builder.ViaPattern(builder.NounExpr("__slotToBinding", span), + builder.BindingPattern(n, span), span) + + def expandMatchBind(args, span, fail): + def [spec, patt] := args + def pattScope := patt.getStaticScope() + def specScope := spec.getStaticScope() + if ((pattScope.outNames() & specScope.namesUsed()).size() > 0): + fail(["Use on left isn't really in scope of matchbind pattern: ${conflicts.getKeys()}", span]) + def [sp, ejector, result, problem, broken] := [builder.TempNounExpr(n, span) + for n in ["sp", "fail", "ok", "problem", "broken"]] + + def patternNouns := [builder.NounExpr(n, span) for n in pattScope.outNames()] + return builder.SeqExpr([ + builder.DefExpr(builder.FinalPattern(sp, null, span), null, spec, span), + builder.DefExpr(builder.ListPattern([builder.FinalPattern(result, null, span)] + + [builder.BindingPattern(n, span) for n in patternNouns], null, span), + null, + builder.EscapeExpr( + builder.FinalPattern(ejector, null, span), + builder.SeqExpr([ + builder.DefExpr(patt, ejector, sp, span), + emitList([builder.NounExpr("true", span)] + + [builder.BindingExpr(n, span) for n in patternNouns], + span)], span), + builder.FinalPattern(problem, null, span), + builder.SeqExpr([ + builder.DefExpr(builder.ViaPattern( + builder.NounExpr("__slotToBinding", span), + builder.BindingPattern(broken, span), span), + null, + builder.MethodCallExpr(builder.NounExpr("Ref", span), + "broken", [problem], span), span), + emitList([builder.NounExpr("false", span)] + + [builder.BindingExpr(broken, span)] * patternNouns.size(), span)], + span), + span), + span), + result], + span) + + def expandLogical(leftNames, rightNames, f, span): + def both := [builder.NounExpr(n, span) for n in leftNames | rightNames] + def result := builder.TempNounExpr("ok", span) + def success := emitList([builder.NounExpr("true", span)] + + [builder.BindingExpr(n, span) for n in both], span) + def failure := builder.MethodCallExpr(builder.NounExpr("__booleanFlow", span), + "failureList", [builder.LiteralExpr(both.size(), span)], span) + return builder.SeqExpr([ + builder.DefExpr( + builder.ListPattern([builder.FinalPattern(result, null, span)] + + [builder.BindingPattern(n, span) for n in both], null, span), + null, + f(success, failure), span), + result], span) + + def expandCallAssign([rcvr, verb, margs], right, fail, span): + def ares := builder.TempNounExpr("ares", span) + return builder.SeqExpr([ + builder.MethodCallExpr(rcvr, putVerb(verb, fail, span), + margs + [builder.DefExpr(builder.FinalPattern(ares, + null, span), + null, right, span)], span), + ares], span) + + def expandVerbAssign(verb, target, vargs, fail, span): + def [_, _, leftargs] := target._uncall() + switch (target.getNodeName()): + match =="NounExpr": + return builder.AssignExpr(target, builder.MethodCallExpr(target, verb, vargs, span), span) + match =="MethodCallExpr": + def [rcvr, methverb, margs, lspan] := leftargs + def recip := builder.TempNounExpr("recip", lspan) + def seq := [builder.DefExpr(builder.FinalPattern(recip, + null, lspan), + null, rcvr, lspan)].diverge() + def setArgs := [].diverge() + for arg in margs: + def a := builder.TempNounExpr("arg", span) + seq.push(builder.DefExpr(builder.FinalPattern(a, null, lspan), + null, arg, lspan)) + setArgs.push(a) + seq.extend(expandCallAssign([recip, methverb, setArgs.snapshot()], builder.MethodCallExpr(builder.MethodCallExpr(recip, methverb, setArgs.snapshot(), span), verb, vargs, span), fail, span).getExprs()) + return builder.SeqExpr(seq.snapshot(), span) + match =="QuasiLiteralExpr": + fail(["Can't use update-assign syntax on a \"$\"-hole. Use explicit \":=\" syntax instead.", span]) + match =="QuasiPatternExpr": + fail(["Can't use update-assign syntax on a \"@\"-hole. Use explicit \":=\" syntax instead.", span]) + match _: + fail(["Can only update-assign nouns and calls", span]) + + def expandMessageDesc(doco, verb, paramDescs, resultGuard, span): + def docExpr := if (doco == null) {builder.NounExpr("null", span)} else {builder.LiteralExpr(doco, span)} + def guardExpr := if (resultGuard == null) {builder.NounExpr("Any", span)} else { + resultGuard} + return builder.HideExpr(builder.MethodCallExpr(builder.NounExpr("__makeMessageDesc", span), + "run", [docExpr, builder.LiteralExpr(verb, span), + emitList(paramDescs, span), guardExpr], + span), span) + + def expandObject(doco, name, asExpr, auditors, [xtends, methods, matchers], span): + if (xtends == null): + return builder.ObjectExpr(doco, name, asExpr, auditors, builder.Script(null, methods, matchers, span), + span) + def p := builder.TempNounExpr("pair", span) + def superExpr := if (xtends.getNodeName() == "NounExpr") { + builder.DefExpr(builder.BindingPattern(builder.NounExpr("super", span), span), null, + builder.BindingExpr(xtends, span), span) + } else { + builder.DefExpr(builder.FinalPattern(builder.NounExpr("super", span), null, span), null, xtends, span) + } + return builder.DefExpr(name, null, builder.HideExpr(builder.SeqExpr([superExpr, + builder.ObjectExpr(doco, name, asExpr, auditors, builder.Script(null, methods, + matchers + [builder.Matcher(builder.FinalPattern(p, null, span), + builder.MethodCallExpr(builder.NounExpr("M", span), "callWithPair", + [builder.NounExpr("super", span), p], span), span)], span), span)], span), + span), span) + + def expandInterface(doco, name, guard, xtends, mplements, messages, span): + def verb := if (guard == null) {"run"} else {"makePair"} + def docExpr := if (doco == null) { builder.NounExpr("null", span) } else {builder.LiteralExpr(doco, span)} + def ifaceExpr := builder.HideExpr(builder.MethodCallExpr( + builder.NounExpr("__makeProtocolDesc", span), verb, + [docExpr, builder.MethodCallExpr( + builder.MethodCallExpr( + builder.MetaContextExpr(span), + "getFQNPrefix", [], span), + "add", [builder.LiteralExpr(name.getNoun().getName() + "__T", span)], span), + emitList(xtends, span), + emitList(mplements, span), + emitList(messages, span)], span), span) + if (guard == null): + return builder.DefExpr(name, null, ifaceExpr, span) + else: + return builder.MethodCallExpr( + builder.DefExpr(builder.ListPattern([builder.FinalPattern(name, null, span), guard], + null, span), + null, ifaceExpr, span), + "get", [builder.LiteralExpr(0)], span) + + def validateFor(left, right, fail, span): + if ((left.outNames() & right.namesUsed()).size() > 0): + fail(["Use on right isn't really in scope of definition", span]) + if ((right.outNames() & left.namesUsed()).size() > 0): + fail(["Use on left would get captured by definition on right", span]) + + def expandFor(optKey, value, coll, block, catchPatt, catchBlock, span): + def key := if (optKey == null) {builder.IgnorePattern(null, span)} else {optKey} + validateFor(key.getStaticScope() + value.getStaticScope(), + coll.getStaticScope(), fail, span) + def fTemp := builder.TempNounExpr("validFlag", span) + def kTemp := builder.TempNounExpr("key", span) + def vTemp := builder.TempNounExpr("value", span) + def obj := builder.ObjectExpr("For-loop body", + builder.IgnorePattern(null, span), null, [], builder.Script( + null, + [builder."Method"(null, "run", + [builder.FinalPattern(kTemp, null, span), + builder.FinalPattern(vTemp, null, span)], + null, + builder.SeqExpr([ + builder.MethodCallExpr( + builder.NounExpr("__validateFor", span), + "run", [fTemp], span), + builder.EscapeExpr( + builder.FinalPattern(builder.NounExpr("__continue", span), null, span), + builder.SeqExpr([ + builder.DefExpr(key, null, kTemp, span), + builder.DefExpr(value, null, vTemp, span), + block, + builder.NounExpr("null", span) + ], span), + null, null, span), + ], span), span)], + [], span), span) + return builder.EscapeExpr( + builder.FinalPattern(builder.NounExpr("__break", span), null, span), + builder.SeqExpr([ + builder.DefExpr(builder.VarPattern(fTemp, null, span), null, + builder.NounExpr("true", span), span), + builder.FinallyExpr( + builder.MethodCallExpr(builder.NounExpr("__loop", span), + "run", [coll, obj], span), + builder.AssignExpr(fTemp, builder.NounExpr("false", span), span), span), + builder.NounExpr("null", span) + ], span), + catchPatt, + catchBlock, + span) + + def expandComprehension(optKey, value, coll, filter, exp, collector, span): + def key := if (optKey == null) {builder.IgnorePattern(null, span)} else {optKey} + validateFor(exp.getStaticScope(), coll.getStaticScope(), fail, span) + validateFor(key.getStaticScope() + value.getStaticScope(), coll.getStaticScope(), fail, span) + def fTemp := builder.TempNounExpr("validFlag", span) + def kTemp := builder.TempNounExpr("key", span) + def vTemp := builder.TempNounExpr("value", span) + def skip := builder.TempNounExpr("skip", span) + def kv := [] + def maybeFilterExpr := if (filter != null) { + builder.IfExpr(filter, exp, builder.MethodCallExpr(skip, "run", [], span), span) + } else {exp} + def obj := builder.ObjectExpr("For-loop body", + builder.IgnorePattern(null, span), null, [], builder.Script( + null, + [builder."Method"(null, "run", + [builder.FinalPattern(kTemp, null, span), + builder.FinalPattern(vTemp, null, span), + builder.FinalPattern(skip, null, span)], + null, + builder.SeqExpr([ + builder.MethodCallExpr( + builder.NounExpr("__validateFor", span), + "run", [fTemp], span), + builder.DefExpr(key, null, kTemp, span), + builder.DefExpr(value, null, vTemp, span), + maybeFilterExpr + ], span), span)], + [], span), span) + return builder.SeqExpr([ + builder.DefExpr(builder.VarPattern(fTemp, null, span), null, + builder.NounExpr("true", span), span), + builder.FinallyExpr( + builder.MethodCallExpr(builder.NounExpr(collector, span), + "run", [coll, obj], span), + builder.AssignExpr(fTemp, builder.NounExpr("false", span), span), span), + ], span) + + def expandTransformer(node, maker, args, span): + switch (node.getNodeName()): + match =="LiteralExpr": + return maker(args[0], span) + + match =="NounExpr": + def [name] := args + return maker(name, span) + match =="SlotExpr": + def [noun] := args + return builder.MethodCallExpr(builder.BindingExpr(noun, span), "get", [], span) + match =="BindingExpr": + def [noun] := args + return builder.BindingExpr(noun, span) + match =="MethodCallExpr": + def [rcvr, verb, arglist] := args + return builder.MethodCallExpr(rcvr, verb, arglist, span) + + match =="ListExpr": + def [items] := args + return emitList(items, span) + match =="MapExpr": + def [assocs] := args + return builder.MethodCallExpr( + builder.NounExpr("__makeMap", span), "fromPairs", + [emitList([emitList(a, span) for a in assocs], span)], + span) + match =="MapExprAssoc": + return args + match =="MapExprExport": + def [subnode] := args + def [submaker, subargs, subspan] := subnode._uncall() + def n := node.getValue() + switch (n.getNodeName()): + match =="NounExpr": + return [builder.LiteralExpr(n.getName(), span), subnode] + match =="SlotExpr": + return [builder.LiteralExpr("&" + n.getNoun().getName(), span), subnode] + match =="BindingExpr": + return [builder.LiteralExpr("&&" + n.getNoun().getName(), span), subnode] + + match =="QuasiText": + def [text] := args + return ["text", builder.LiteralExpr(text, span), span] + match =="QuasiExprHole": + def [expr] := args + return ["expr", expr, span] + match =="QuasiPatternHole": + def [patt] := args + return ["patt", patt, span] + match =="QuasiParserExpr": + def [name, quasis] := args + def qprefix := if (name == null) {"simple"} else {name} + def qname := qprefix + "__quasiParser" + def [parts, exprs, _] := buildQuasi(builder, qname, quasis) + return builder.MethodCallExpr( + builder.MethodCallExpr( + builder.NounExpr(qname, span), "valueMaker", + [emitList(parts, span)], span), + "substitute", + [emitList(exprs, span)], span) + match =="Module": + def [imports, exports, expr] := args + return builder."Module"(imports, exports, expr, span) + match =="SeqExpr": + def [exprs] := args + #XXX some parsers have emitted nested SeqExprs, should that + #flattening be done here or in the parser? + return builder.SeqExpr(exprs, span) + match =="VerbCurryExpr": + def [receiver, verb] := args + return builder.MethodCallExpr( + builder.NounExpr("__makeVerbFacet", span), + "curryCall", + [receiver, builder.LiteralExpr(verb, span)], + span) + match =="GetExpr": + def [receiver, index] := args + return builder.MethodCallExpr(receiver, "get", index, span) + match =="FunCallExpr": + def [receiver, fargs] := args + return builder.MethodCallExpr(receiver, "run", fargs, span) + match =="FunctionSendExpr": + def [receiver, fargs] := args + return builder.MethodCallExpr(builder.NounExpr("M", span), + "send", [receiver, "run", fargs], span) + match =="SendExpr": + def [receiver, verb, margs] := args + return builder.MethodCallExpr(builder.NounExpr("M", span), + "send", [receiver, builder.LiteralExpr(verb, span), + emitList(margs, span)], + span) + match =="SendCurryExpr": + def [receiver, verb] := args + return builder.MethodCallExpr( + builder.NounExpr("__makeVerbFacet", span), + "currySend", [receiver, builder.LiteralExpr(verb, span)], + span) + match =="PrefixExpr": + return builder.MethodCallExpr(args[1], node.getOpName(), [], span) + match =="BinaryExpr": + return builder.MethodCallExpr(args[0], node.getOpName(), [args[2]], span) + match =="RangeExpr": + return builder.MethodCallExpr(builder.NounExpr("__makeOrderedSpace", span), + "op__" + node.getOpName(), [args[0], args[2]], span) + match =="CompareExpr": + return builder.MethodCallExpr(builder.NounExpr("__comparer", span), + node.getOpName(), [args[0], args[2]], span) + match =="CoerceExpr": + def [spec, guard] := args + return builder.MethodCallExpr( + builder.MethodCallExpr( + builder.NounExpr("ValueGuard", span), + "coerce", + [guard, builder.NounExpr("throw", span)], span), + "coerce", [spec, builder.NounExpr("throw", span)], span) + match =="MatchBindExpr": + return expandMatchBind(args, span, fail) + match =="MismatchExpr": + return builder.MethodCallExpr(expandMatchBind(args, span, fail), "not", [], span) + match =="SameExpr": + def [left, right, same] := args + if (same): + return builder.MethodCallExpr(builder.NounExpr("__equalizer", span), "sameEver", + [left, right], span) + else: + return builder.MethodCallExpr(builder.MethodCallExpr(builder.NounExpr("__equalizer", span), "sameEver", [left, right], span), "not", [], span) + match =="AndExpr": + def [left, right] := args + return expandLogical( + left.getStaticScope().outNames(), + right.getStaticScope().outNames(), + fn s, f {builder.IfExpr(left, builder.IfExpr(right, s, f, span), f, span)}, + span) + match =="OrExpr": + def [left, right] := args + + def leftmap := left.getStaticScope().outNames() + def rightmap := right.getStaticScope().outNames() + def partialFail(failed, s, broken): + return builder.SeqExpr([ + builder.DefExpr(builder.BindingPattern(n, span), null, broken, span) + for n in failed] + [s], span) + return expandLogical( + leftmap, rightmap, + fn s, f { + def broken := builder.MethodCallExpr( + builder.NounExpr("__booleanFlow", span), + "broken", [], span) + def rightOnly := [builder.NounExpr(n, span) for n in rightmap - leftmap] + def leftOnly := [builder.NounExpr(n, span) for n in leftmap - rightmap] + builder.IfExpr(left, partialFail(rightOnly, s, broken), + builder.IfExpr(right, partialFail(leftOnly, s, broken), f, span), span)}, + span) + match =="DefExpr": + def [patt, ej, rval] := args + def pattScope := patt.getStaticScope() + def defPatts := pattScope.getDefNames() + def varPatts := pattScope.getVarNames() + def rvalScope := if (ej == null) { + rval.getStaticScope() + } else { + ej.getStaticScope() + rval.getStaticScope() + } + def rvalUsed := rvalScope.namesUsed() + if ((varPatts & rvalUsed).size() != 0): + fail(["Circular 'var' definition not allowed", span]) + if ((pattScope.namesUsed() & rvalScope.outNames()).size() != 0): + fail(["Pattern may not used var defined on the right", span]) + def conflicts := pattScope.outNames() & rvalUsed + if (conflicts.size() == 0): + return builder.DefExpr(patt, ej, rval, span) + else: + def promises := [].diverge() + def resolvers := [].diverge() + def renamings := [].asMap().diverge() + for oldname in conflicts: + def newname := builder.TempNounExpr(oldname, span) + def newnameR := builder.TempNounExpr(oldname + "R", span) + renamings[oldname] := newname + def pair := [builder.FinalPattern(newname, null, span), + builder.FinalPattern(newnameR, null, span)] + promises.push(builder.DefExpr(builder.ListPattern(pair, null, span), + null, builder.MethodCallExpr(builder.NounExpr("Ref", span), "promise", + [], span), span)) + resolvers.push(builder.MethodCallExpr(newnameR, "resolve", + [builder.NounExpr(oldname, span)], span)) + def resName := builder.TempNounExpr("value", span) + resolvers.push(resName) + def renamedEj := if (ej == null) {null} else {renameCycles(ej, renamings, builder)} + def renamedRval := renameCycles(rval, renamings, builder) + def resPatt := builder.FinalPattern(resName, null, span) + def resDef := builder.DefExpr(resPatt, null, + builder.DefExpr(patt, renamedEj, renamedRval, span), span) + return builder.SeqExpr(promises.snapshot() + [resDef] + resolvers.snapshot(), span) + match =="ForwardExpr": + def [patt] := args + def rname := builder.NounExpr(patt.getNoun().getName() + "__Resolver", span) + return builder.SeqExpr([ + builder.DefExpr(builder.ListPattern([ + patt, + builder.FinalPattern(rname, null, span)], + null, span), + null, + builder.MethodCallExpr(builder.NounExpr("Ref", span), "promise", [], span), span), + rname], span) + match =="AssignExpr": + def [left, right] := args + def [_, _, leftargs] := left._uncall() + switch (left.getNodeName()): + match =="NounExpr": + return builder.AssignExpr(left, right, span) + match =="MethodCallExpr": + return expandCallAssign(leftargs.slice(0, 3), right, fail, span) + match _: + fail(["Assignment can only be done to nouns and collection elements", + span]) + match =="VerbAssignExpr": + def [verb, target, vargs] := args + return expandVerbAssign(verb, target, vargs, fail, span) + match =="AugAssignExpr": + def [op, left, right] := args + return expandVerbAssign(node.getOpName(), left, [right], fail, span) + match =="ExitExpr": + if (args[1] == null): + return builder.MethodCallExpr(builder.NounExpr("__" + args[0], span), "run", [], span) + else: + return builder.MethodCallExpr(builder.NounExpr("__" + args[0], span), "run", [args[1]], span) + match =="IgnorePattern": + return builder.IgnorePattern(args[0], span) + match =="FinalPattern": + def [noun, guard] := args + return builder.FinalPattern(noun, guard, span) + match =="SamePattern": + def [value, isSame] := args + if (isSame): + return builder.ViaPattern( + builder.MethodCallExpr(builder.NounExpr("__matchSame", span), + "run", [value], span), + builder.IgnorePattern(null, span), span) + else: + return builder.ViaPattern( + builder.MethodCallExpr(builder.NounExpr("__matchSame", span), + "different", [value], span), + builder.IgnorePattern(null, span). span) + match =="VarPattern": + return builder.VarPattern(args[0], args[1], span) + match =="BindPattern": + def [noun, guard] := args + return builder.ViaPattern( + builder.MethodCallExpr(builder.NounExpr("__bind", span), + "run", [builder.NounExpr(noun.getName() + "__Resolver", span), guard], + span), + builder.BindingPattern(noun, span), span) + match =="SlotPattern": + def [noun, guard] := args + if (guard == null): + return builder.ViaPattern(builder.NounExpr("__slotToBinding", span), + builder.BindingPattern(noun, span), span) + else: + return builder.ViaPattern( + builder.MethodCallExpr(builder.NounExpr("__slotToBinding", span), + "run", [guard], + span), + builder.BindingPattern(noun, span), span) + match =="MapPattern": + def [assocs, tail] := args + var nub := if (tail == null) { + builder.IgnorePattern(builder.NounExpr("__mapEmpty", span), span) + } else {tail} + for [left, right] in assocs.reversed(): + nub := builder.ViaPattern( + left, + builder.ListPattern([right, nub], null, span), span) + return nub + match =="MapPatternAssoc": + return args + match =="MapPatternImport": + def [subnode] := args + switch (node.getPattern().getNodeName()): + match =="FinalPattern": + return [builder.LiteralExpr(node.getPattern().getNoun().getName(), span), subnode] + match =="SlotPattern": + return [builder.LiteralExpr("&" + node.getPattern().getNoun().getName(), span), subnode] + match =="BindingPattern": + return [builder.LiteralExpr("&&" + node.getPattern().getNoun().getName(), span), subnode] + match =="MapPatternOptional": + def [[k, v], default] := args + return [builder.MethodCallExpr(builder.NounExpr("__mapExtract", span), + "depr", [k, default], span), v] + match =="MapPatternRequired": + def [[k, v]] := args + return [builder.MethodCallExpr(builder.NounExpr("__mapExtract", span), + "run", [k], span), v] + match =="ListPattern": + def [patterns, tail] := args + if (tail == null): + return builder.ListPattern(patterns, null, span) + else: + return builder.ViaPattern( + builder.MethodCallExpr(builder.NounExpr("__splitList", span), "run", + [builder.LiteralExpr(patterns.size())], span), + builder.ListPattern(patterns + [tail], null, span), span) + match =="SuchThatPattern": + def [pattern, expr] := args + return builder.ViaPattern(builder.NounExpr("__suchThat", span), + builder.ListPattern([pattern, builder.ViaPattern( + builder.MethodCallExpr(builder.NounExpr("__suchThat", span), "run", + [expr], span), + builder.IgnorePattern(null, span), span)], null, span), span) + match =="QuasiParserPattern": + def [name, quasis] := args + def qprefix := if (name == null) {"simple"} else {name} + def qname := qprefix + "__quasiParser" + def [parts, exprs, patterns] := buildQuasi(builder, qname, quasis) + return builder.ViaPattern( + builder.MethodCallExpr( + builder.NounExpr("__quasiMatcher", span), "run", + [builder.MethodCallExpr(builder.NounExpr(qname, span), "matchMaker", + [emitList(parts, span), emitList(exprs, span)], span)], span), + builder.ListPattern(patterns, null, span), span) + match =="FunctionInterfaceExpr": + def [doco, name, guard, xtends, mplements, messageDesc] := args + return expandInterface(doco, name, guard, xtends, + mplements, [messageDesc], span) + match =="InterfaceExpr": + def [doco, name, guard, xtends, mplements, messages] := args + return expandInterface(doco, name, guard, xtends, + mplements, messages, span) + match =="MessageDesc": + def [doco, verb, params, resultGuard] := args + return expandMessageDesc(doco, verb, params, resultGuard, span) + match =="ParamDesc": + def [name, guard] := args + return builder.MethodCallExpr(builder.NounExpr("__makeParamDesc", span), + "run", [builder.LiteralExpr(name, span), + if (guard == null) {builder.NounExpr("Any", span)} else {guard}], span) + match =="FunctionExpr": + def [patterns, block] := args + return builder.ObjectExpr(null, builder.IgnorePattern(null, span), null, [], + builder.Script(null, + [builder."Method"(null, "run", patterns, null, block, span)], + span), span) + match =="ObjectExpr": + def [doco, patt, asExpr, auditors, script] := args + def [pattMaker, pattArgs, pattSpan] := patt._uncall() + def pattKind := patt.getNodeName() + if (pattKind == "BindPattern"): + def name := builder.FinalPattern(node.getName().getName(), null, span) + def o := expandObject(doco, name, asExpr, auditors, script, span) + return builder.DefExpr(patt, null, builder.HideExpr(o, span), span) + if (pattKind == "FinalPattern" || pattKind == "IgnorePattern"): + return expandObject(doco, patt, asExpr, auditors, script, span) + fail(["Unknown pattern type in object expr", patt.getSpan()]) + match =="Script": + #def [xtends, methods, matchers] := args + return args + match =="FunctionScript": + def [params, guard, block] := args + return [null, [builder."Method"(null, "run", params, guard, + builder.EscapeExpr(builder.FinalPattern(builder.NounExpr("__return", span), null, span), + builder.SeqExpr([block, builder.NounExpr("null", span)], span), null, null, span), + span)], []] + match =="To": + def [doco, verb, params, guard, block] := args + return builder."Method"(doco, verb, params, guard, + builder.EscapeExpr(builder.FinalPattern(builder.NounExpr("__return", span), null, span), + builder.SeqExpr([block, builder.NounExpr("null", span)], span), null, null, span), + span) + match =="Method": + def [doco, verb, params, guard, block] := args + return builder."Method"(doco, verb, params, guard, block, span) + match =="ForExpr": + def [coll, key, value, block, catchPatt, catchBlock] := args + return expandFor(key, value, coll, block, catchPatt, catchBlock, span) + match =="ListComprehensionExpr": + def [coll, filter, key, value, exp] := args + return expandComprehension(key, value, coll, filter, exp, "__accumulateList", span) + match =="MapComprehensionExpr": + def [coll, filter, key, value, kExp, vExp] := args + return expandComprehension(key, value, coll, filter, + emitList([kExp, vExp], span), "__accumulateMap", span) + match =="SwitchExpr": + def [expr, matchers] := args + def sp := builder.TempNounExpr("specimen", span) + def failures := [builder.TempNounExpr("failure", span) for _ in matchers] + def ejs := [builder.TempNounExpr("ej", span) for _ in matchers] + var block := builder.MethodCallExpr(builder.NounExpr("__switchFailed", span), "run", + [sp] + failures, span) + for [m, fail, ej] in reversed(zip(matchers, failures, ejs)): + block := builder.EscapeExpr( + builder.FinalPattern(ej, null, span), + builder.SeqExpr([ + builder.DefExpr(m.getPattern(), ej, sp, span), + m.getBody()], span), + builder.FinalPattern(fail, null, span), + block, span) + return builder.HideExpr(builder.SeqExpr([ + builder.DefExpr(builder.FinalPattern(sp, null, span), null, expr, span), + block], span), span) + match =="TryExpr": + def [tryblock, catchers, finallyblock] := args + var block := tryblock + for cat in catchers: + block := builder.CatchExpr(block, cat.getPattern(), cat.getBody(), span) + if (finallyblock != null): + block := builder.FinallyExpr(block, finallyblock, span) + return block + match =="WhileExpr": + def [test, block, catcher] := args + return builder.EscapeExpr( + builder.FinalPattern(builder.NounExpr("__break", span), null, span), + builder.MethodCallExpr(builder.NounExpr("__loop", span), "run", + [builder.MethodCallExpr(builder.NounExpr("__iterWhile", span), "run", + [builder.ObjectExpr(null, builder.IgnorePattern(null, span), null, [], + builder.Script(null, + [builder."Method"(null, "run", [], null, test, span)], + [], span), span)], span), + builder.ObjectExpr(null, builder.IgnorePattern(null, span), null, [], + builder.Script(null, + [builder."Method"(null, "run", + [builder.IgnorePattern(null, span), + builder.IgnorePattern(null, span)], + builder.NounExpr("Bool", span), + builder.SeqExpr([ + builder.EscapeExpr( + builder.FinalPattern( + builder.NounExpr("__continue", span), + null, span), + block, null, null, span), + builder.NounExpr("true", span)], span), span)], + [], span), span)], span), + if (catcher !=null) {catcher.getPattern()}, + if (catcher !=null) {catcher.getBody()}, span) + match =="WhenExpr": + def [var promiseExprs, var block, catchers, finallyblock] := args + def expr := if (promiseExprs.size() > 1) { + builder.MethodCallExpr(builder.NounExpr("promiseAllFulfilled", span), "run", + [emitList(args, span)], span) + } else {promiseExprs[0]} + def resolution := builder.TempNounExpr("resolution", span) + block := builder.IfExpr( + builder.MethodCallExpr(builder.NounExpr("Ref", span), "isBroken", + [resolution], span), + builder.MethodCallExpr(builder.NounExpr("Ref", span), "broken", + [builder.MethodCallExpr(builder.NounExpr("Ref", span), "optProblem", + [resolution], span)], span), block, span) + for cat in catchers: + block := builder.CatchExpr(block, cat.getPattern(), cat.getBody(), span) + if (finallyblock != null): + block := builder.FinallyExpr(block, finallyblock, span) + return builder.HideExpr(builder.MethodCallExpr(builder.NounExpr("Ref", span), + "whenResolved", [expr, + builder.ObjectExpr("when-catch 'done' function", + builder.IgnorePattern(null, span), null, [], + builder.Script(null, + [builder."Method"(null, "run", + [builder.FinalPattern(resolution, null, span)], + null, block, span)], [], span), + span)], span), span) + match _: + return M.call(maker, "run", args + [span]) + return node.transform(expandTransformer) + + +def tests := [].asMap().diverge() +def fixedPointSpecimens := [ + "x", + "x := y", + "x := y := z", + "foo.bar(x, y)", + "def [x, y] := z", + "def x :y exit z := w", + "def &&x := y", + "def via (x) y := z", + " + if (x): + y + else: + z", + " + if (x): + y + else if (z): + w", + " + object x: + method y(): + z + ", + " + object x: + match y: + z + ", +] + +def specimens := [ + ["x[i] := y", + " + x.put(i, def $ := y) + $"], + + ["x[i] := y; ares__1", + " + x.put(i, def $ := y) + $ + ares__1"], + + ["x foo= (y, z)", "x := x.foo(y, z)"], + + ["x[i] foo= (y)", + " + def $ := x + def $ := i + $.put($, def $ := $.get($).foo(y)) + $"], + ["x[i] += y", + " + def $ := x + def $ := i + $.put($, def $ := $.get($).add(y)) + $"], + + ["x + y", + "x.add(y)"], + + ["x - y", + "x.subtract(y)"], + + ["x * y", + "x.multiply(y)"], + + ["x / y", + "x.approxDivide(y)"], + + ["x // y", + "x.floorDivide(y)"], + + ["x % y", + "x.mod(y)"], + + ["x ** y", + "x.pow(y)"], + + ["x >> y", + "x.shiftRight(y)"], + + ["x << y", + "x.shiftLeft(y)"], + + ["x & y", + "x.and(y)"], + + ["x | y", + "x.or(y)"], + + ["x ^ y", + "x.xor(y)"], + + ["x += y", + "x := x.add(y)"], + + ["x -= y", + "x := x.subtract(y)"], + + ["x *= y", + "x := x.multiply(y)"], + + ["x /= y", + "x := x.approxDivide(y)"], + + ["x //= y", + "x := x.floorDivide(y)"], + + ["x %= y", + "x := x.mod(y)"], + + ["x **= y", + "x := x.pow(y)"], + + ["x >>= y", + "x := x.shiftRight(y)"], + + ["x <<= y", + "x := x.shiftLeft(y)"], + + ["x &= y", + "x := x.and(y)"], + + ["x |= y", + "x := x.or(y)"], + + ["x ^= y", + "x := x.xor(y)"], + + ["!x", "x.not()"], + ["-x", "x.negate()"], + ["~x", "x.complement()"], + + ["x < y", "__comparer.lessThan(x, y)"], + ["x <= y", "__comparer.leq(x, y)"], + ["x > y", "__comparer.greaterThan(x, y)"], + ["x >= y", "__comparer.geq(x, y)"], + ["x <=> y", "__comparer.asBigAs(x, y)"], + + ["x == y", "__equalizer.sameEver(x, y)"], + ["x != y", "__equalizer.sameEver(x, y).not()"], + + ["x..y", "__makeOrderedSpace.op__thru(x, y)"], + ["x..!y", "__makeOrderedSpace.op__till(x, y)"], + + ["foo <- bar(x, y)", + "M.send(foo, \"bar\", __makeList.run(x, y))"], + + ["def [x, y] := [1, x]", + " + def [$, $] := Ref.promise() + def $ := def [x, y] := __makeList.run(1, $) + $.resolve(x) + $"], + + ["def x", + " + def [x, x__Resolver] := Ref.promise() + x__Resolver"], + + ["x :y", + "ValueGuard.coerce(y, throw).coerce(x, throw)"], + + ["def &x := y", + "def via (__slotToBinding) &&x := y"], + + ["return", + "__return.run()"], + + ["return 1", + "__return.run(1)"], + + ["break", + "__break.run()"], + + ["break 1", + "__break.run(1)"], + + ["continue", + "__continue.run()"], + + ["continue 1", + "__continue.run(1)"], + + ["x && y", + " + def [$] := if (x) { + if (y) { + __makeList.run(true) + } else { + __booleanFlow.failureList(0) + } + } else { + __booleanFlow.failureList(0) + } + $"], + + ["(def x := 1) && (def y := 2)", + " + def [$, &&y, &&x] := if (def x := 1) { + if (def y := 2) { + __makeList.run(true, &&y, &&x) + } else { + __booleanFlow.failureList(2) + } + } else { + __booleanFlow.failureList(2) + } + $"], + + ["x || y", + " + def [$] := if (x) { + __makeList.run(true) + } else if (y) { + __makeList.run(true) + } else { + __booleanFlow.failureList(0) + } + $"], + + ["(def x := 1) || (def y := 2)", + " + def [$, &&y, &&x] := if (def x := 1) { + def &&y := __booleanFlow.broken() + __makeList.run(true, &&y, &&x) + } else if (def y := 2) { + def &&x := __booleanFlow.broken() + __makeList.run(true, &&y, &&x) + } else { + __booleanFlow.failureList(2) + } + $"], + + ["x =~ y", + " + def $ := x + def [$, &&y] := escape $ { + def y exit $ := $ + __makeList.run(true, &&y) + } catch $ { + def via (__slotToBinding) &&$ := Ref.broken($) + __makeList.run(false, &&$) + } + $"], + + ["def x ? (e) := z", + "def via (__suchThat) [x, via (__suchThat.run(e)) _] := z"], + + ["def x ? (f(x) =~ y) := z", + " + def via (__suchThat) [x, via (__suchThat.run(def $ := f.run(x) + def [$, &&y] := escape $ { + def y exit $ := $ + __makeList.run(true, &&y) + } catch $ { + def via (__slotToBinding) &&$ := Ref.broken($) + __makeList.run(false, &&$) + } + $)) _] := z"], + + [`def ["a" => b, "c" => d] := x`, + `def via (__mapExtract.run("a")) [b, via (__mapExtract.run("c")) [d, _ :__mapEmpty]] := x`], + + ["def [(a) => b] | c := x", + "def via (__mapExtract.run(a)) [b, c] := x"], + + ["def [=> b] := x", + "def via (__mapExtract.run(\"b\")) [b, _ :__mapEmpty] := x"], + + ["def [=> &b] := x", + "def via (__mapExtract.run(\"&b\")) [via (__slotToBinding) &&b, _ :__mapEmpty] := x"], + + [`["a" => b, "c" => d]`, + `__makeMap.fromPairs(__makeList.run(__makeList.run("a", b), __makeList.run("c", d)))`], + + [`[=> a, => &b]`, + `__makeMap.fromPairs(__makeList.run(__makeList.run("a", a), __makeList.run("&b", &&b.get())))`], + + [" + for x in y: + z + ", + " + escape __break: + var $ := true + try: + __loop.run(y, object _ { + \"For-loop body\" + method run($, $) { + __validateFor.run($) + escape __continue { + def _ := $ + def x := $ + z + null + } + } + + }) + finally: + $ := false + null"], + ["[for x in (y) if (a) z]", + " + var $ := true + try: + __accumulateList.run(y, object _ { + \"For-loop body\" + method run($, $, $) { + __validateFor.run($) + def _ := $ + def x := $ + if (a) { + z + } else { + $.run() + } + } + + }) + finally: + $ := false"], + + ["[for x in (y) if (a) k => v]", + " + var $ := true + try: + __accumulateMap.run(y, object _ { + \"For-loop body\" + method run($, $, $) { + __validateFor.run($) + def _ := $ + def x := $ + if (a) { + __makeList.run(k, v) + } else { + $.run() + } + } + + }) + finally: + $ := false"], + + [" + while (x): + y + ", + + " + escape __break: + __loop.run(__iterWhile.run(object _ { + method run() { + x + } + + }), object _ { + method run(_, _) :Bool { + escape __continue { + y + } + true + } + + })"], + [" + object foo extends (baz.get()): + pass + ", + " + def foo := { + def super := baz.get() + object foo { + match $ { + M.callWithPair(super, $) + } + + } + }"], + [" + object foo: + to baz(): + x + ", + " + object foo: + method baz(): + escape __return: + x + null + "], + [" + def foo(): + x + ", + " + object foo: + method run(): + escape __return: + x + null + "], + [" + switch (x): + match [a, b]: + c + match x: + y + ", + " + { + def $ := x + escape $ { + def [a, b] exit $ := $ + c + } catch $ { + escape $ { + def x exit $ := $ + y + } catch $ { + __switchFailed.run($, $, $) + } + } + }"], + [" + switch (x): + match ==2: + 'a' + ", + " + { + def $ := x + escape $ { + def via (__matchSame.run(2)) _ exit $ := $ + 'a' + } catch $ { + __switchFailed.run($, $) + } + }"], + [" + interface foo: + pass + ", + " + def foo := { + __makeProtocolDesc.run(null, meta.context().getFQNPrefix().add(\"foo__T\"), __makeList.run(), __makeList.run(), __makeList.run()) + }"], + [` + interface foo extends x, y implements a, b: + "yay" + to baz(c :Int): + "blee" + to boz(d) :Double + `, + ` + def foo := { + __makeProtocolDesc.run("yay", meta.context().getFQNPrefix().add("foo__T"), __makeList.run(x, y), __makeList.run(a, b), __makeList.run({ + __makeMessageDesc.run("blee", "baz", __makeList.run(__makeParamDesc.run("c", Int)), Any) + }, { + __makeMessageDesc.run(null, "boz", __makeList.run(__makeParamDesc.run("d", Any)), Double) + })) + }`], + [" + try: + x + catch p: + y + catch q: + z + ", + " + try: + try: + x + catch p: + y + catch q: + z"], + [" + try: + x + catch p: + y + finally: + z + ", + " + try: + try: + x + catch p: + y + finally: + z"], + [" + when (x) -> + y + ", + " + { + Ref.whenResolved(x, object _ { + \"when-catch 'done' function\" + method run($) { + if (Ref.isBroken($)) { + Ref.broken(Ref.optProblem($)) + } else { + y + } + } + + }) + }"], + [" + when (x) -> + y + catch p: + z + ", + " + { + Ref.whenResolved(x, object _ { + \"when-catch 'done' function\" + method run($) { + try { + if (Ref.isBroken($)) { + Ref.broken(Ref.optProblem($)) + } else { + y + } + } catch p { + z + } + } + + }) + }"], + ["`hello $x world`", + `simple__quasiParser.valueMaker(__makeList.run("hello ", simple__quasiParser.valueHole(0), " world")).substitute(__makeList.run(x))`], + ["def foo`(@x)` := 1", + `def via (__quasiMatcher.run(foo__quasiParser.matchMaker(__makeList.run("(", foo__quasiParser.patternHole(0), ")"), __makeList.run()))) [x] := 1`], + ["def foo`(@x:$y)` := 1", + `def via (__quasiMatcher.run(foo__quasiParser.matchMaker(__makeList.run("(", foo__quasiParser.patternHole(0), ":", foo__quasiParser.valueHole(0), ")"), __makeList.run(y)))) [x] := 1`], +] + +def trim(var s): + if (s.startsWith("\n")): + s := s.slice(1, s.size()) + def lines := s.split("\n") + var dent := 0 + for line in lines: + for i => c in line: + if (c != ' '): + dent := i + break + break + def trimmedLines := [].diverge() + for line in lines: + trimmedLines.push(line.slice(dent, line.size())) + return "\n".join(trimmedLines) + +def expandit(code): + def node := parseModule(makeMonteLexer(trim(code)), astBuilder, throw) + return expand(node, astBuilder, throw) + +for item in fixedPointSpecimens: + tests.put(item, fn assert { + traceln(`expanding $item`) + assert.equal(M.toString(expandit(item + "\n")), trim(item)) + }) + +for [specimen, result] in specimens: + tests.put(specimen, fn assert { + traceln(`expanding $specimen`) + assert.equal(M.toString(expandit(specimen)), trim(result)) + }) + +unittest(tests.snapshot()) diff --git a/monte/src/monte_lexer.mt b/monte/src/monte_lexer.mt new file mode 100644 index 0000000..845bc50 --- /dev/null +++ b/monte/src/monte_lexer.mt @@ -0,0 +1,990 @@ +module unittest +export (makeMonteLexer) + +object VALUE_HOLE {} +object PATTERN_HOLE {} +object EOF {} +def decimalDigits := '0'..'9' +def hexDigits := decimalDigits | 'a'..'f' | 'A'..'F' + +def idStart := 'a'..'z' | 'A'..'Z' | '_'..'_' +def idPart := idStart | '0'..'9' +def closers := ['(' => ')', '[' => ']', '{' => '}'] + +def isIdentifierPart(c): + if (c == EOF): + return false + return idPart(c) + +def MONTE_KEYWORDS := [ + "as", "bind", "break", "catch", "continue", "def", "else", "escape", + "exit", "extends", "export", "finally", "fn", "for", "guards", "if", + "implements", "in", "interface", "match", "meta", "method", "module", + "object", "pass", "pragma", "return", "switch", "to", "try", "var", + "via", "when", "while"] + +def composite(name, data, span): + return term__quasiParser.makeTerm(term__quasiParser.makeTag(null, name, Any), + data, [], span) + +def _makeMonteLexer(input, braceStack, var nestLevel): + + # The character under the cursor. + var currentChar := null + # Offset of the current character. + var position := -1 + # Start offset of the text for the token being created. + var startPos := -1 + + # Syntax error produced from most recent tokenization attempt. + var errorMessage := null + + var count := -1 + + var canStartIndentedBlock := false + def queuedTokens := [].diverge() + def indentPositionStack := [0].diverge() + + def atEnd(): + return position == input.size() + + def spanAtPoint(): + def inp := if (input.getSpan() == null) { + input.asFrom("") + } else { + input + } + return inp.slice(0.max(position - 1), 1.max(position)).getSpan() + + def advance(): + position += 1 + if (atEnd()): + currentChar := EOF + else: + currentChar := input[position] + return currentChar + + def peekChar(): + if (atEnd()): + throw("attempt to read past end of input") + if (position + 1 == input.size()): + return EOF + return input[position + 1] + + def pushBrace(opener, closer, indent, canNest): + if (canNest): + nestLevel += 1 + braceStack.push([opener, closer, indent, canNest]) + + def popBrace(closer, fail): + if (braceStack.size() <= 1): + throw.eject(fail, [`Unmatched closing character ${closer.quote()}`, spanAtPoint()]) + else if (braceStack.last()[1] != closer): + throw.eject(fail, [`Mismatch: ${closer.quote()} doesn't close ${braceStack.last()[0]}`, spanAtPoint()]) + def item := braceStack.pop() + if (item[3]): + nestLevel -= 1 + + def inStatementPosition(): + return ["INDENT", null].contains(braceStack.last()[0]) + + def skipSpaces(): + if (atEnd()): + return 0 + def oldPos := position + while (currentChar == ' '): + advance() + return position - oldPos + + def atLogicalEndOfLine(): + if (atEnd()): + return true + var i := position + while ((i < input.size()) && input[i] == ' '): + i += 1 + def endish := i == input.size() || ['\n', '#'].contains(input[i]) + return endish + + def offsetInLine(): + var i := 0 + while (i < position && input[position - i] != '\n'): + i += 1 + return i + + def startToken(): + if (startPos >= 0): + throw("Token already started") + startPos := position + + def endToken(): + def pos := position + def tok := input.slice(startPos, pos) + startPos := -1 + return tok + + def leaf(tok): + return composite(tok, null, endToken().getSpan()) + + def collectDigits(var digitset): + if (atEnd() || !digitset(currentChar)): + return false + digitset |= ('_'..'_') + while (!atEnd() && digitset(currentChar)): + advance() + return true + + def numberLiteral(fail): + var radix := 10 + var floating := false + if (currentChar == '0'): + advance() + if (currentChar == 'X' || currentChar == 'x'): + radix := 16 + advance() + if (radix == 16): + collectDigits(hexDigits) + else: + collectDigits(decimalDigits) + if (currentChar == '.'): + def pc := peekChar() + if (pc == EOF): + throw.eject(fail, ["Missing fractional part", spanAtPoint()]) + if (decimalDigits(pc)): + advance() + floating := true + collectDigits(decimalDigits) + if (currentChar == 'e' || currentChar == 'E'): + advance() + floating := true + if (currentChar == '-' || currentChar == '+'): + advance() + if (!collectDigits(decimalDigits)): + throw.eject(fail, ["Missing exponent", spanAtPoint()]) + def tok := endToken() + def s := tok.replace("_", "") + if (floating): + return composite(".float64.", __makeFloat(s), tok.getSpan()) + else: + if (radix == 16): + return composite(".int.", __makeInt(s.slice(2), 16), tok.getSpan()) + else: + return composite(".int.", __makeInt(s), tok.getSpan()) + + + def charConstant(fail): + if (currentChar == '\\'): + def nex := advance() + if (nex == 'U'): + def hexstr := __makeString.fromChars([advance(), advance(), advance(), advance(), advance(), advance(), advance(), advance(), advance()]) + def v + try: + bind v := __makeInt(hexstr, 16) + catch _: + throw.eject(fail, ["\\U escape must be eight hex digits", spanAtPoint()]) + advance() + return __makeCharacter(v) + if (nex == 'u'): + def hexstr := __makeString.fromChars([advance(), advance(), advance(), advance()]) + def v + try: + bind v := __makeInt(hexstr, 16) + catch _: + throw.eject(fail, ["\\u escape must be four hex digits", spanAtPoint()]) + advance() + return __makeCharacter(v) + else if (nex == 'x'): + def v + try: + bind v := __makeInt(__makeString.fromChars([advance(), advance()]), 16) + catch _: + throw.eject(fail, ["\\x escape must be two hex digits", spanAtPoint()]) + advance() + return __makeCharacter(v) + else if (nex == EOF): + throw.eject(fail, ["End of input in middle of literal", spanAtPoint()]) + def c := [ + 'b' => '\b', + 't' => '\t', + 'n' => '\n', + 'f' => '\f', + 'r' => '\r', + '"' => '"', + '\'' => '\'', + '\\' => '\\', + '\n' => null, + ].fetch(nex, fn{-1}) + if (c == -1): + throw.eject(fail, [`Unrecognized escape character ${nex.quote()}`, spanAtPoint()]) + else: + advance() + return c + if (currentChar == EOF): + throw.eject(fail, ["End of input in middle of literal", spanAtPoint()]) + else if (currentChar == '\t'): + throw.eject(fail, ["Quoted tabs must be written as \\t", spanAtPoint()]) + else: + def c := currentChar + advance() + return c + + def stringLiteral(fail): + def opener := currentChar + advance() + pushBrace(opener, '"', 0, false) + def buf := [].diverge() + while (currentChar != '"'): + if (atEnd()): + throw.eject(fail, ["Input ends inside string literal", spanAtPoint()]) + def cc := charConstant(fail) + if (cc != null): + buf.push(cc) + advance() + return __makeString.fromChars(buf.snapshot()) + + def charLiteral(fail): + advance() + var c := charConstant(fail) + while (c == null): + c := charConstant(fail) + if (currentChar != '\''): + throw.eject(fail, ["Character constant must end in \"'\"", spanAtPoint()]) + advance() + return composite(".char.", c, endToken().getSpan()) + + def identifier(fail): + while (isIdentifierPart(advance())): + pass + if (currentChar == '='): + def c := peekChar() + if (!['=', '>', '~'].contains(c)): + advance() + def chunk := endToken() + def token := chunk.slice(0, chunk.size() - 1) + if (MONTE_KEYWORDS.contains(token)): + throw.eject(fail, [`$token is a keyword`, spanAtPoint()]) + return composite("VERB_ASSIGN", token, chunk.getSpan()) + def token := endToken() + if (MONTE_KEYWORDS.contains(token.toLowerCase())): + return composite(token.toLowerCase(), token.toLowerCase(), token.getSpan()) + else: + return composite("IDENTIFIER", token, token.getSpan()) + + def quasiPart(fail): + def buf := [].diverge() + while (true): + while (!['@', '$', '`'].contains(currentChar)): + # stuff that doesn't start with @ or $ passes through + if (currentChar == EOF): + throw.eject(fail, ["File ends inside quasiliteral", spanAtPoint()]) + buf.push(currentChar) + advance() + if (peekChar() == currentChar): + buf.push(currentChar) + advance() + advance() + else if (currentChar == '`'): + # close backtick + advance() + popBrace('`', fail) + return composite("QUASI_CLOSE", __makeString.fromChars(buf.snapshot()), + endToken().getSpan()) + else if (currentChar == '$' && peekChar() == '\\'): + # it's a character constant like $\u2603 or a line continuation like $\ + advance() + def cc := charConstant(fail) + if (cc != null): + buf.push(cc) + else: + def opener := endToken() + pushBrace(opener, "hole", nestLevel * 4, true) + return composite("QUASI_OPEN", __makeString.fromChars(buf.snapshot()), + opener.getSpan()) + + + def openBracket(closer, var opener, fail): + if (opener == null): + advance() + opener := endToken() + if (atLogicalEndOfLine()): + pushBrace(opener, closer, nestLevel * 4, true) + else: + pushBrace(opener, closer, offsetInLine(), true) + return composite(opener, null, opener.getSpan()) + + def closeBracket(fail): + advance() + def closer := endToken() + popBrace(closer, fail) + return composite(closer, null, closer.getSpan()) + + def consumeComment(): + while (!['\n', EOF].contains(currentChar)): + advance() + def comment := endToken() + return composite("#", comment.slice(1), comment.getSpan()) + + def consumeWhitespaceAndComments(): + var spaces := skipSpaces() + while (currentChar == '\n'): + queuedTokens.insert(0, composite("EOL", null, input.slice(position, position + 1).getSpan())) + advance() + spaces := skipSpaces() + if (currentChar == '#'): + queuedTokens.insert(0, consumeComment()) + startToken() + spaces := null + return spaces + + + def getNextToken(fail): + if (queuedTokens.size() > 0): + return queuedTokens.pop() + + if (braceStack.last()[1] == '`'): + startToken() + return quasiPart(fail) + + skipSpaces() + startToken() + + def cur := currentChar + if (cur == EOF): + throw.eject(fail, null) + if (cur == '\n'): + def c := advance() + if (canStartIndentedBlock): + def spaces := consumeWhitespaceAndComments() + if (!inStatementPosition()): + throw.eject(fail, + ["Indented blocks only allowed in statement position", spanAtPoint()]) + if (spaces > indentPositionStack.last()): + indentPositionStack.push(spaces) + openBracket("DEDENT", "INDENT", fail) + canStartIndentedBlock := false + queuedTokens.insert(0, composite("INDENT", null, null)) + return leaf("EOL") + else: + throw.eject(fail, ["Expected an indented block", spanAtPoint()]) + if (!inStatementPosition()): + return leaf("EOL") + else: + queuedTokens.insert(0, leaf("EOL")) + startToken() + def spaces := consumeWhitespaceAndComments() + if (spaces > indentPositionStack.last()): + throw.eject(fail, ["Unexpected indent", spanAtPoint()]) + if (atEnd()): + while (indentPositionStack.size() > 1): + indentPositionStack.pop() + popBrace("DEDENT", fail) + queuedTokens.push(composite("DEDENT", null, null)) + return queuedTokens.pop() + while (spaces < indentPositionStack.last()): + if (!indentPositionStack.contains(spaces)): + throw.eject(fail, ["unindent does not match any outer indentation level", spanAtPoint()]) + indentPositionStack.pop() + popBrace("DEDENT", fail) + queuedTokens.push(composite("DEDENT", null, null)) + return queuedTokens.pop() + + + if ([';', ',', '~', '?'].contains(cur)): + advance() + return leaf(__makeString.fromChars([cur])) + + if (cur == '('): + return openBracket(")", null, fail) + if (cur == '['): + return openBracket("]", null, fail) + if (cur == '{'): + return openBracket("}", null, fail) + + if (cur == '}'): + def result := closeBracket(fail) + if (braceStack.last()[1] == "hole"): + popBrace("hole", fail) + return result + if (cur == ']'): + return closeBracket(fail) + if (cur == ')'): + return closeBracket(fail) + + if (cur == '$'): + def nex := advance() + if (nex == '{'): + # quasi hole of form ${blah} + return openBracket("}", null, fail) + else if (nex != EOF && idStart(nex)): + # quasi hole of form $blee + var cc := advance() + while (isIdentifierPart(cc)): + cc := advance() + def name := endToken() + def key := name.slice(1) + if (MONTE_KEYWORDS.contains(key.toLowerCase())): + advance() + throw.eject(fail, [`$key is a keyword`, spanAtPoint()]) + if (braceStack.last()[1] == "hole"): + popBrace("hole", fail) + return composite("DOLLAR_IDENT", key, name.getSpan()) + else if (nex == '$'): + return leaf("$") + else: + throw.eject(fail, [`Unrecognized $$-escape "$$$nex"`, spanAtPoint()]) + + if (cur == '@'): + def nex := advance() + if (nex == '{'): + # quasi hole of the form @{blee} + return openBracket("}", null, fail) + else if (nex != EOF && idStart(nex)): + # quasi hole of the form @blee + var cc := advance() + while (isIdentifierPart(cc)): + cc := advance() + def name := endToken() + def key := name.slice(1) + if (MONTE_KEYWORDS.contains(key.toLowerCase())): + advance() + throw.eject(fail, [`$key is a keyword`, spanAtPoint()]) + if (braceStack.last()[1] == "hole"): + popBrace("hole", fail) + return composite("AT_IDENT", key, name.getSpan()) + else if (nex == '@'): + return leaf("@") + else: + throw.eject(fail, [`Unrecognized @@-escape "@@$nex"`, spanAtPoint()]) + + if (cur == '.'): + def nex := advance() + if (nex == '.'): + def nex2 := advance() + if (nex2 == '!'): + advance() + return leaf("..!") + return leaf("..") + return leaf(".") + + if (cur == '^'): + def nex := advance() + if (nex == '='): + advance() + return leaf("^=") + return leaf("^") + + if (cur == '+'): + def nex := advance() + if (nex == '+'): + advance() + throw.eject(fail, ["++? lol no", spanAtPoint()]) + if (nex == '='): + advance() + return leaf("+=") + return leaf("+") + + if (cur == '-'): + def nex := advance() + if (nex == '-'): + advance() + throw.eject(fail, ["--? lol no", spanAtPoint()]) + if (nex == '='): + advance() + return leaf("-=") + if (nex == '>'): + advance() + if (atLogicalEndOfLine()): + # this is an arrow ending a line, and should be + # followed by an indent + canStartIndentedBlock := true + return leaf("->") + return leaf("-") + if (cur == ':'): + def nex := advance() + if (nex == ':'): + advance() + return leaf("::") + if (nex == '='): + advance() + return leaf(":=") + if (atLogicalEndOfLine()): + # this is a colon ending a line, and should be + # followed by an indent + canStartIndentedBlock := true + return leaf(":") + + if (cur == '<'): + def nex := advance() + if (nex == '-'): + advance() + return leaf("<-") + if (nex == '='): + def nex2 := advance() + if (nex2 == '>'): + advance() + return leaf("<=>") + return leaf("<=") + + if (nex == '<'): + def nex2 := advance() + if (nex2 == '='): + advance() + return leaf("<<=") + return leaf("<<") + return leaf("<") + + if (cur == '>'): + def nex := advance() + if (nex == '='): + advance() + return leaf(">=") + if (nex == '>'): + def nex2 := advance() + if (nex2 == '='): + advance() + return leaf(">>=") + return leaf(">>") + return leaf(">") + + if (cur == '*'): + def nex := advance() + if (nex == '*'): + def nex2 := advance() + if (nex2 == '='): + advance() + return leaf("**=") + return leaf("**") + if (nex == '='): + advance() + return leaf("*=") + return leaf("*") + + if (cur == '/'): + def nex := advance() + if (nex == '/'): + def nex2 := advance() + if (nex2 == '='): + advance() + return leaf("//=") + return leaf("//") + if (nex == '='): + advance() + return leaf("/=") + return leaf("/") + + if (cur == '#'): + return consumeComment() + + if (cur == '%'): + def nex := advance() + if (nex == '='): + advance() + return leaf("%=") + return leaf("%") + + if (cur == '!'): + def nex := advance() + if (nex == '='): + advance() + return leaf("!=") + if (nex == '~'): + advance() + return leaf("!~") + return leaf("!") + + if (cur == '='): + def nex := advance() + if (nex == '='): + advance() + return leaf("==") + if (nex == '>'): + advance() + return leaf("=>") + if (nex == '~'): + advance() + return leaf("=~") + throw.eject(fail, ["Use := for assignment or == for equality", spanAtPoint()]) + if (cur == '&'): + def nex := advance() + if (nex == '&'): + advance() + return leaf("&&") + if (nex == '='): + advance() + return leaf("&=") + if (nex == '!'): + advance() + return leaf("&!") + return leaf("&") + + if (cur == '|'): + def nex := advance() + if (nex == '='): + advance() + return leaf("|=") + if (nex == '|'): + advance() + return leaf("||") + return leaf("|") + + if (cur == '"'): + def s := stringLiteral(fail) + def closer := endToken() + popBrace('"', fail) + + return composite(".String.", s, closer.getSpan()) + + if (cur == '\''): + return charLiteral(fail) + + if (cur == '`'): + advance() + pushBrace('`', '`', 0, false) + def part := quasiPart(fail) + if (part == null): + def next := getNextToken(fail) + if (next == EOF): + throw.eject(fail, ["File ends in quasiliteral", spanAtPoint()]) + return next + return part + + if (decimalDigits(cur)): + return numberLiteral(fail) + + if (cur == '_'): + def pc := peekChar() + if (pc != EOF && idStart(pc)): + return identifier(fail) + advance() + return leaf("_") + + if (cur == '\t'): + throw.eject(fail, ["Tab characters are not permitted in Monte source.", spanAtPoint()]) + if (idStart(cur)): + return identifier(fail) + + throw.eject(fail, [`Unrecognized character ${cur.quote()}`, spanAtPoint()]) + + advance() + return object monteLexer: + + to _makeIterator(): + return monteLexer + + to getSyntaxError(): + return errorMessage + + to valueHole(): + return VALUE_HOLE + + to patternHole(): + return PATTERN_HOLE + + to next(ej): + try: + def errorStartPos := position + escape e: + def t := getNextToken(e) + return [count += 1, t] + catch msg: + errorMessage := msg + if (msg == null && !atEnd()): + throw.eject(ej, [`Trailing garbage: ${input.slice(position, input.size())}`, spanAtPoint()]) + throw.eject(ej, msg) + finally: + startPos := -1 + + to lexerForNextChunk(chunk): + return _makeMonteLexer(chunk, braceStack, nestLevel) + + +object makeMonteLexer: + to run(input): + # State for paired delimiters like "", {}, (), [] + def braceStack := [[null, null, 0, true]].diverge() + return _makeMonteLexer(input, braceStack, 0) + + to holes(): + return [VALUE_HOLE, PATTERN_HOLE] + + +def lex(s): + def l := makeMonteLexer(s) + def toks := __makeList.fromIterable(l) + if ((def err := l.getSyntaxError()) != null): + throw(err) + if (toks.size() > 0 && toks.last().getTag().getName() == "EOL"): + return toks.slice(0, toks.size() - 1) + return toks + +def tt(tagname, data): + return term__quasiParser.makeTerm(term__quasiParser.makeTag(null, tagname, Any), + data, [], null) + +def test_ident(assert): + assert.equal(lex("foo_bar9"), [tt("IDENTIFIER", "foo_bar9")]) + assert.equal(lex("foo"), [tt("IDENTIFIER", "foo")]) + +def test_char(assert): + assert.equal(lex("'z'"), [tt(".char.", 'z')]) + assert.equal(lex("'\\n'"), [tt(".char.", '\n')]) + assert.equal(lex("'\\u0061'"), [tt(".char.", 'a')]) + assert.equal(lex("'\\x61'"), [tt(".char.", 'a')]) + +def test_string(assert): + assert.equal(lex(`"foo\$\nbar"`), [tt(".String.", "foobar")]) + assert.equal(lex(`"foo"`), [tt(".String.", "foo")]) + assert.equal(lex(`"foo bar 9"`), [tt(".String.", "foo bar 9")]) + assert.equal(lex(`"foo\nbar"`), [tt(".String.", "foo\nbar")]) + +def test_integer(assert): + assert.equal(lex("0"), [tt(".int.", 0)]) + assert.equal(lex("7"), [tt(".int.", 7)]) + assert.equal(lex("3_000"), [tt(".int.", 3000)]) + assert.equal(lex("0xABad1dea"), [tt(".int.", 2880249322)]) + +def test_float(assert): + assert.equal(lex("1e9"), [tt(".float64.", 1e9)]) + assert.equal(lex("3.1415E17"), [tt(".float64.", 3.1415E17)]) + assert.equal(lex("0.91"), [tt(".float64.", 0.91)]) + assert.equal(lex("3e-2"), [tt(".float64.", 3e-2)]) + +def test_holes(assert): + assert.equal(lex("${"), [tt("${", null)]) + assert.equal(lex("$blee"), [tt("DOLLAR_IDENT", "blee")]) + assert.equal(lex("@{"), [tt("@{", null)]) + assert.equal(lex("@blee"), [tt("AT_IDENT", "blee")]) + assert.equal(lex("@_fred"), [tt("AT_IDENT", "_fred")]) + assert.equal(lex("@_"), [tt("AT_IDENT", "_")]) + +def test_braces(assert): + assert.equal(lex("[a, 1]"), + [tt("[", null), + tt("IDENTIFIER", "a"), + tt(",", null), + tt(".int", 1), + tt("]", null)]) + assert.equal(lex("{1}"), + [tt("{", null), + tt(".int.", 1), + tt("}", null)]) + assert.equal(lex("(a)"), + [tt("(", null), + tt("IDENTIFIER", "a"), + tt(")", null)]) + +def test_dot(assert): + assert.equal(lex("."), [tt(".", null)]) + assert.equal(lex(".."), [tt("..", null)]) + assert.equal(lex("..!"), [tt("..!", null)]) + +def test_caret(assert): + assert.equal(lex("^"), [tt("^", null)]) + assert.equal(lex("^="), [tt("^=", null)]) + +def test_plus(assert): + assert.equal(lex("+"), [tt("+", null)]) + assert.equal(lex("+="), [tt("+=", null)]) + +def test_minus(assert): + assert.equal(lex("-"), [tt("-", null)]) + assert.equal(lex("-="), [tt("-=", null)]) + assert.equal(lex("-> {"), [tt("->", null), tt("{", null)]) + +def test_colon(assert): + assert.equal(lex(":x"), [tt(":", null), tt("IDENTIFIER", "x")]) + assert.equal(lex(":="), [tt(":=", null)]) + assert.equal(lex("::"), [tt("::", null)]) + +def test_crunch(assert): + assert.equal(lex("<"), [tt("<", null)]) + assert.equal(lex("<-"), [tt("<-", null)]) + assert.equal(lex("<="), [tt("<=", null)]) + assert.equal(lex("<<="), [tt("<<=", null)]) + assert.equal(lex("<=>"), [tt("<=>", null)]) + +def test_zap(assert): + assert.equal(lex(">"), [tt(">", null)]) + assert.equal(lex(">="), [tt(">=", null)]) + assert.equal(lex(">>="), [tt(">>=", null)]) + +def test_star(assert): + assert.equal(lex("*"), [tt("*", null)]) + assert.equal(lex("*="), [tt("*=", null)]) + assert.equal(lex("**"), [tt("**", null)]) + assert.equal(lex("**="), [tt("**=", null)]) + +def test_slash(assert): + assert.equal(lex("/"), [tt("/", null)]) + assert.equal(lex("/="), [tt("/=", null)]) + assert.equal(lex("//"), [tt("//", null)]) + assert.equal(lex("//="), [tt("//=", null)]) + +def test_mod(assert): + assert.equal(lex("%"), [tt("%", null)]) + assert.equal(lex("%="), [tt("%=", null)]) + +def test_comment(assert): + assert.equal(lex("# yes\n1"), [tt("#", " yes"), tt("EOL", null), + tt(".int.", 1)]) + +def test_bang(assert): + assert.equal(lex("!"), [tt("!", null)]) + assert.equal(lex("!="), [tt("!=", null)]) + assert.equal(lex("!~"), [tt("!~", null)]) + +def test_eq(assert): + assert.equal(lex("=="), [tt("==", null)]) + assert.equal(lex("=~"), [tt("=~", null)]) + assert.equal(lex("=>"), [tt("=>", null)]) + +def test_and(assert): + assert.equal(lex("&"), [tt("&", null)]) + assert.equal(lex("&="), [tt("&=", null)]) + assert.equal(lex("&!"), [tt("&!", null)]) + assert.equal(lex("&&"), [tt("&&", null)]) + +def test_or(assert): + assert.equal(lex("|"), [tt("|", null)]) + assert.equal(lex("|="), [tt("|=", null)]) + + +def SIMPLE_INDENT := " +foo: + baz + + +" + +def ARROW_INDENT := " +foo -> + baz + + +" + +def SIMPLE_DEDENT := " +foo: + baz +blee +" + +def VERTICAL_SPACE := " +foo: + + baz + + +blee +" + +def HORIZ_SPACE := " +foo: + baz +blee +" + +def MULTI_INDENT := " +foo: + baz: + biz +blee +" + +def UNBALANCED := " +foo: + baz: + biz + blee +" + +def UNBALANCED2 := " +foo: + baz + blee +" + +def PARENS := " +(foo, + baz: + blee + ) +" + +#TODO decide whether to follow python's "no indent tokens inside +#parens" strategy or have ways to jump in/out of indentation-awareness +def CONTINUATION := " +foo ( + baz + biz + ) +blee +" +def test_indent_simple(assert): + assert.equal( + lex(SIMPLE_INDENT), + [tt("EOL", null), tt("IDENTIFIER", "foo"), tt(":", null), tt("EOL", null), + tt("INDENT", null), tt("IDENTIFIER", "baz"), tt("DEDENT", null), + tt("EOL", null), tt("EOL", null)]) + +def test_indent_arrow(assert): + assert.equal( + lex(ARROW_INDENT), + [tt("EOL", null), tt("IDENTIFIER", "foo"), tt("->", null), tt("EOL", null), + tt("INDENT", null), tt("IDENTIFIER", "baz"), tt("DEDENT", null), + tt("EOL", null), tt("EOL", null)]) + +def test_indent_dedent(assert): + assert.equal( + lex(SIMPLE_DEDENT), + [tt("EOL", null), tt("IDENTIFIER", "foo"), tt(":", null), tt("EOL", null), + tt("INDENT", null), tt("IDENTIFIER", "baz"), tt("DEDENT", null), + tt("EOL", null), tt("IDENTIFIER", "blee")]) + +def test_indent_vertical(assert): + assert.equal( + lex(VERTICAL_SPACE), + [tt("EOL", null), tt("IDENTIFIER", "foo"), tt(":", null), tt("EOL", null), + tt("EOL", null), tt("INDENT", null), tt("IDENTIFIER", "baz"), + tt("DEDENT", null), tt("EOL", null), tt("EOL", null), tt("EOL", null), + tt("IDENTIFIER", "blee")]) + +def test_indent_horiz(assert): + assert.equal( + lex(HORIZ_SPACE), + [tt("EOL", null), tt("IDENTIFIER", "foo"), tt(":", null), tt("EOL", null), + tt("INDENT", null), tt("IDENTIFIER", "baz"), tt("DEDENT", null), + tt("EOL", null), tt("IDENTIFIER", "blee")]) + + +def test_indent_multi(assert): + assert.equal( + lex(MULTI_INDENT), + [tt("EOL", null), tt("IDENTIFIER", "foo"), tt(":", null), + tt("EOL", null), tt("INDENT", null), tt("IDENTIFIER", "baz"), + tt(":", null), tt("EOL", null), tt("INDENT", null), + tt("IDENTIFIER", "biz"), tt("DEDENT", null), tt("DEDENT", null), + tt("EOL", null), tt("IDENTIFIER", "blee")]) + +def test_indent_unbalanced(assert): + assert.raises(fn {lex(UNBALANCED)}) + assert.raises(fn {lex(UNBALANCED2)}) + +def test_indent_inexpr(assert): + assert.raises(fn {lex(PARENS)}) + +def test_indent_continuation(assert): + assert.equal( + lex(CONTINUATION), + [tt("EOL", null), tt("IDENTIFIER", "foo"), tt("(", null), + tt("EOL", null), tt("IDENTIFIER", "baz"), tt("EOL", null), + tt("IDENTIFIER", "biz"), tt("EOL", null), tt(")", null), + tt("IDENTIFIER", "blee"), tt("EOL", null)]) + +# unittest([test_ident, test_char, test_string, test_integer, test_float, +# test_holes, test_braces, test_dot, test_caret, test_plus, test_minus, +# test_colon, test_crunch, test_zap, test_star, test_slash, test_mod, +# test_comment, test_bang, test_eq, test_and, test_or, + +# test_indent_simple, test_indent_arrow, test_indent_dedent, +# test_indent_vertical, test_indent_horiz, test_indent_multi, +# test_indent_unbalanced, test_indent_inexpr, test_indent_continuation]) diff --git a/monte/src/monte_parser.mt b/monte/src/monte_parser.mt new file mode 100644 index 0000000..c4092f6 --- /dev/null +++ b/monte/src/monte_parser.mt @@ -0,0 +1,1673 @@ +module unittest, makeMonteLexer, astBuilder +export (parseModule, parseExpression, parsePattern) +def spanCover(left, right): + if (left == null || right == null): + return null + return left.combine(right) + +# # XXX dupe from term parser module +# def makeQuasiTokenChain(makeLexer, template): +# var i := -1 +# var current := makeLexer("", qBuilder) +# var lex := current +# def [VALUE_HOLE, PATTERN_HOLE] := makeLexer.holes() +# var j := 0 +# return object chainer: +# to _makeIterator(): +# return chainer + +# to valueHole(): +# return VALUE_HOLE + +# to patternHole(): +# return PATTERN_HOLE + +# to next(ej): +# if (i >= template.size()): +# throw.eject(ej, null) +# j += 1 +# if (current == null): +# if (template[i] == VALUE_HOLE || template[i] == PATTERN_HOLE): +# def hol := template[i] +# i += 1 +# return [j, hol] +# else: +# current := lex.lexerForNextChunk(template[i])._makeIterator() +# lex := current +# escape e: +# def t := current.next(e)[1] +# return [j, t] +# catch z: +# i += 1 +# current := null +# return chainer.next(ej) + +def parseMonte(lex, builder, mode, err): + def [VALUE_HOLE, PATTERN_HOLE] := [lex.valueHole(), lex.patternHole()] + def _toks := [].diverge() + while (true): + _toks.push(lex.next(__break)[1]) + catch p: + if (p != null): + throw.eject(err, p) + def tokens := _toks.snapshot() + traceln(`tokens: $tokens ${tokens.size()}`) + var dollarHoleValueIndex := -1 + var atHoleValueIndex := -1 + var position := -1 + + def spanHere(): + if (position + 1 >= tokens.size()): + return null + return tokens[position.max(0)].getSpan() + + def spanFrom(start): + return spanCover(start, spanHere()) + + def advance(ej): + position += 1 + if (position >= tokens.size()): + throw.eject(ej, ["hit EOF", tokens.last().getSpan()]) + return tokens[position] + + def advanceTag(ej): + def t := advance(ej) + def isHole := t == VALUE_HOLE || t == PATTERN_HOLE + if (isHole): + return t + else: + return t.getTag().getName() + + def acceptTag(tagname, fail): + def t := advance(fail) + def specname := t.getTag().getName() + if (specname != tagname): + position -= 1 + throw.eject(fail, [`expected $tagname, got $specname`, spanHere()]) + return t + + def acceptEOLs(): + while (true): + if ((position + 1) >= tokens.size()): + return + def t := tokens[position + 1] + def isHole := t == VALUE_HOLE || t == PATTERN_HOLE + if (isHole || !["EOL", "#"].contains(t.getTag().getName())): + return + position += 1 + + def peek(): + if (position + 1 >= tokens.size()): + return null + return tokens[position + 1] + + def opt(rule, ej): + escape e: + return rule(e) + catch _: + return null + + def peekTag(): + if (position + 1 >= tokens.size()): + return null + return tokens[position + 1].getTag().getName() + + def matchEOLsThenTag(indent, tagname): + def origPosition := position + if (indent): + acceptEOLs() + if (position + 1 >= tokens.size()): + position := origPosition + return false + if (tokens[position + 1].getTag().getName() == tagname): + position += 1 + return true + else: + position := origPosition + return false + + def acceptList(rule): + acceptEOLs() + def items := [].diverge() + escape e: + items.push(rule(e)) + while (true): + acceptTag(",", __break) + acceptEOLs() + items.push(rule(__break)) + return items.snapshot() + + def acceptListOrMap(ruleList, ruleMap): + var isMap := false + def items := [].diverge() + def startpos := position + acceptEOLs() + escape em: + items.push(ruleMap(em)) + isMap := true + catch _: + escape e: + position := startpos + items.push(ruleList(e)) + isMap := false + catch _: + return [[], false] + while (true): + acceptTag(",", __break) + acceptEOLs() + if (isMap): + items.push(ruleMap(__break)) + else: + items.push(ruleList(__break)) + return [items.snapshot(), isMap] + + def expr + def order + def blockExpr + def prim + def pattern + def assign + def quasiliteral(id, isPattern, ej): + def spanStart := if (id == null) {spanHere()} else {id.getSpan()} + def name := if (id == null) {null} else {id.getData()} + def parts := [].diverge() + while (true): + def t := advance(ej) + def tname := t.getTag().getName() + if (tname == "QUASI_OPEN" && t.getData() != ""): + parts.push(builder.QuasiText(t.getData(), t.getSpan())) + else if (tname == "QUASI_CLOSE"): + parts.push(builder.QuasiText(t.getData(), t.getSpan())) + break + else if (tname == "DOLLAR_IDENT"): + parts.push(builder.QuasiExprHole( + builder.NounExpr(t.getData(), t.getSpan()), + t.getSpan())) + else if (tname == "${"): + def subexpr := expr(ej) + parts.push(builder.QuasiExprHole(subexpr, subexpr.getSpan())) + else if (tname == "AT_IDENT"): + parts.push(builder.QuasiPatternHole( + builder.FinalPattern( + builder.NounExpr(t.getData(), t.getSpan()), + null, t.getSpan()), + t.getSpan())) + else if (tname == "@{"): + def subpatt := pattern(ej) + parts.push(builder.QuasiPatternHole(subpatt, subpatt.getSpan())) + if (isPattern): + return builder.QuasiParserPattern(name, parts, spanFrom(spanStart)) + else: + return builder.QuasiParserExpr(name, parts, spanFrom(spanStart)) + + def guard(ej): + def spanStart := spanHere() + if (peekTag() == "IDENTIFIER"): + def t := advance(ej) + def n := builder.NounExpr(t.getData(), t.getSpan()) + if (peekTag() == "["): + advance(ej) + def g := acceptList(expr) + acceptTag("]", ej) + return builder.GetExpr(n, g, spanFrom(spanStart)) + else: + return n + acceptTag("(", ej) + def e := expr(ej) + acceptTag(")", ej) + return e + + def noun(ej): + if (peekTag() == "IDENTIFIER"): + def t := advance(ej) + return builder.NounExpr(t.getData(), t.getSpan()) + else: + def spanStart := spanHere() + acceptTag("::", ej) + def t := acceptTag(".String.", ej) + return builder.NounExpr(t.getData(), spanFrom(spanStart)) + + def maybeGuard(): + def origPosition := position + if (peekTag() == ":"): + advance(null) + escape e: + return guard(e) + catch _: + # might be suite-starting colon + position := origPosition + return null + + def namePattern(ej, tryQuasi): + def spanStart := spanHere() + def nex := peekTag() + if (nex == "IDENTIFIER"): + def t := advance(ej) + def nex2 := peekTag() + if (nex2 == "QUASI_OPEN" || nex2 == "QUASI_CLOSE"): + if (tryQuasi): + return quasiliteral(t, true, ej) + else: + throw.eject(ej, [nex2, spanHere()]) + else: + def g := maybeGuard() + return builder.FinalPattern(builder.NounExpr(t.getData(), t.getSpan()), g, spanFrom(spanStart)) + else if (nex == "::"): + advance(ej) + def spanStart := spanHere() + def t := acceptTag(".String.", ej) + def g := maybeGuard() + return builder.FinalPattern(builder.NounExpr(t.getData(), t.getSpan()), g, spanFrom(spanStart)) + else if (nex == "var"): + advance(ej) + def n := noun(ej) + def g := maybeGuard() + return builder.VarPattern(n, g, spanFrom(spanStart)) + else if (nex == "&"): + advance(ej) + def n := noun(ej) + def g := maybeGuard() + return builder.SlotPattern(n, g, spanFrom(spanStart)) + else if (nex == "&&"): + advance(ej) + return builder.BindingPattern(noun(ej), spanFrom(spanStart)) + else if (nex == "bind"): + advance(ej) + return builder.BindPattern(noun(ej), spanFrom(spanStart)) + throw.eject(ej, [`Unrecognized name pattern $nex`, spanHere()]) + + def mapPatternItemInner(ej): + def spanStart := spanHere() + if (peekTag() == "=>"): + advance(ej) + def p := namePattern(ej, false) + return builder.MapPatternImport(p, spanFrom(spanStart)) + def k := if (peekTag() == "(") { + advance(ej) + def e := expr(ej) + acceptEOLs() + acceptTag(")", ej) + e + } else { + if ([".String.", ".int.", ".float64.", ".char."].contains(peekTag())) { + def t := advance(ej) + builder.LiteralExpr(t.getData(), t.getSpan()) + } else { + throw.eject(ej, ["Map pattern keys must be literals or expressions in parens", spanHere()]) + } + } + acceptTag("=>", ej) + return builder.MapPatternAssoc(k, pattern(ej), spanFrom(spanStart)) + + def mapPatternItem(ej): + def spanStart := spanHere() + def p := mapPatternItemInner(ej) + if (peekTag() == ":="): + advance(ej) + return builder.MapPatternDefault(p, order(ej), spanFrom(spanStart)) + else: + return builder.MapPatternRequired(p, spanFrom(spanStart)) + + def _pattern(ej): + escape e: + return namePattern(e, true) + # ... if namePattern fails, keep going + def spanStart := spanHere() + def nex := peekTag() + if (nex == "QUASI_OPEN" || nex == "QUASI_CLOSE"): + return quasiliteral(null, true, ej) + else if (nex == "=="): + def spanStart := spanHere() + advance(ej) + return builder.SamePattern(prim(ej), true, spanFrom(spanStart)) + else if (nex == "!="): + def spanStart := spanHere() + advance(ej) + return builder.SamePattern(prim(ej), false, spanFrom(spanStart)) + else if (nex == "_"): + advance(ej) + def spanStart := spanHere() + def g := if (peekTag() == ":" && tokens[position + 2].getTag().getName() != "EOL") { + advance(ej); guard(ej) + } else { + null + } + return builder.IgnorePattern(g, spanFrom(spanStart)) + else if (nex == "via"): + advance(ej) + def spanStart := spanHere() + acceptTag("(", ej) + def e := expr(ej) + acceptTag(")", ej) + return builder.ViaPattern(e, pattern(ej), spanFrom(spanStart)) + else if (nex == "["): + def spanStart := spanHere() + advance(ej) + def [items, isMap] := acceptListOrMap(pattern, mapPatternItem) + acceptEOLs() + acceptTag("]", ej) + if (isMap): + def tail := if (peekTag() == "|") {advance(ej); _pattern(ej)} + return builder.MapPattern(items, tail, spanFrom(spanStart)) + else: + def tail := if (peekTag() == "+") {advance(ej); _pattern(ej)} + return builder.ListPattern(items, tail, spanFrom(spanStart)) + throw.eject(ej, [`Invalid pattern $nex`, spanHere()]) + + bind pattern(ej): + def spanStart := spanHere() + def p := _pattern(ej) + if (peekTag() == "?"): + advance(ej) + acceptTag("(", ej) + def e := expr(ej) + acceptTag(")", ej) + return builder.SuchThatPattern(p, e, spanFrom(spanStart)) + else: + return p + "XXX buggy expander eats this line" + + def mapItem(ej): + def spanStart := spanHere() + if (peekTag() == "=>"): + advance(ej) + if (peekTag() == "&"): + advance(ej) + return builder.MapExprExport(builder.SlotExpr(noun(ej), spanFrom(spanStart)), spanFrom(spanStart)) + else if (peekTag() == "&&"): + advance(ej) + return builder.MapExprExport(builder.BindingExpr(noun(ej), spanFrom(spanStart)), spanFrom(spanStart)) + else: + return builder.MapExprExport(noun(ej), spanFrom(spanStart)) + def k := expr(ej) + acceptTag("=>", ej) + def v := expr(ej) + return builder.MapExprAssoc(k, v, spanFrom(spanStart)) + + def seqSep(ej): + if (![";", "#", "EOL"].contains(peekTag())): + ej(null) + advance(ej) + while (true): + if (![";", "#", "EOL"].contains(peekTag())): + break + advance(ej) + + def seq(indent, ej): + def ex := if (indent) {blockExpr} else {expr} + def start := spanHere() + def exprs := [ex(ej)].diverge() + while (true): + seqSep(__break) + exprs.push(ex(__break)) + opt(seqSep, ej) + if (exprs.size() == 1): + return exprs[0] + return builder.SeqExpr(exprs.snapshot(), spanFrom(start)) + + def block(indent, ej): + if (indent): + acceptTag(":", ej) + acceptEOLs() + acceptTag("INDENT", ej) + else: + acceptTag("{", ej) + acceptEOLs() + def contents := if (peekTag() == "pass") { + advance(ej) + acceptEOLs() + builder.SeqExpr([], null) + } else { + escape e { + seq(indent, ej) + } catch _ { + builder.SeqExpr([], null) + } + } + if (indent): + acceptTag("DEDENT", ej) + else: + acceptTag("}", ej) + return contents + + def suite(rule, indent, ej): + if (indent): + acceptTag(":", ej) + acceptEOLs() + acceptTag("INDENT", ej) + else: + acceptTag("{", ej) + acceptEOLs() + def content := rule(indent, ej) + acceptEOLs() + if (indent): + acceptTag("DEDENT", ej) + else: + acceptTag("}", ej) + return content + + def repeat(rule, indent, ej): + def contents := [].diverge() + while (true): + contents.push(rule(indent, __break)) + return contents.snapshot() + + def forExprHead(needParens, ej): + def p1 := pattern(ej) + def p2 := if (peekTag() == "=>") {advance(ej); pattern(ej) + } else {null} + if (needParens): + acceptEOLs() + acceptTag("in", ej) + if (needParens): + acceptEOLs() + acceptTag("(", ej) + acceptEOLs() + def it := order(ej) + if (needParens): + acceptEOLs() + acceptTag(")", ej) + acceptEOLs() + return if (p2 == null) {[null, p1, it]} else {[p1, p2, it]} + + def matchers(indent, ej): + def spanStart := spanHere() + acceptTag("match", ej) + def pp := pattern(ej) + def bl := block(indent, ej) + acceptEOLs() + return builder.Matcher(pp, bl, spanFrom(spanStart)) + + def catcher(indent, ej): + def spanStart := spanHere() + return builder.Catcher(pattern(ej), block(indent, ej), spanFrom(spanStart)) + + def methBody(indent, ej): + acceptEOLs() + def doco := if (peekTag() == ".String.") { + advance(ej).getData() + } else { + null + } + acceptEOLs() + def contents := escape e { + seq(indent, ej) + } catch _ { + builder.SeqExpr([], null) + } + return [doco, contents] + + def meth(indent, ej): + acceptEOLs() + def spanStart := spanHere() + def mknode := if (peekTag() == "to") { + advance(ej) + builder."To" + } else { + acceptTag("method", ej) + builder."Method" + } + def verb := if (peekTag() == ".String.") { + advance(ej) + } else { + def t := acceptTag("IDENTIFIER", ej) + __makeString.fromString(t.getData(), t.getSpan()) + } + acceptTag("(", ej) + def patts := acceptList(pattern) + acceptTag(")", ej) + def resultguard := if (peekTag() == ":") { + advance(ej) + if (peekTag() == "EOL") { + # Oops, end of indenty block. + position -= 1 + null + } else { + guard(ej) + } + } else { + null + } + def [doco, body] := suite(methBody, indent, ej) + return mknode(doco, verb, patts, resultguard, body, spanFrom(spanStart)) + + def objectScript(indent, ej): + def doco := if (peekTag() == ".String.") { + advance(ej).getData() + } else { + null + } + def meths := [].diverge() + while (true): + acceptEOLs() + if (peekTag() == "pass"): + advance(ej) + continue + meths.push(meth(indent, __break)) + def matchs := [].diverge() + while (true): + if (peekTag() == "pass"): + advance(ej) + continue + matchs.push(matchers(indent, __break)) + return [doco, meths.snapshot(), matchs.snapshot()] + + def oAuditors(ej): + return [ + if (peekTag() == "as") { + advance(ej) + order(ej) + } else { + null + }, + if (peekTag() == "implements") { + advance(ej) + acceptList(order) + } else { + [] + }] + + def blockLookahead(ej): + def origPosition := position + try: + acceptTag(":", ej) + acceptEOLs() + acceptTag("INDENT", ej) + finally: + position := origPosition + + def objectExpr(name, indent, tryAgain, ej, spanStart): + def oExtends := if (peekTag() == "extends") { + advance(ej) + order(ej) + } else { + null + } + def [oAs, oImplements] := oAuditors(ej) + if (indent): + blockLookahead(tryAgain) + def [doco, methods, matchers] := suite(objectScript, indent, ej) + def span := spanFrom(spanStart) + return builder.ObjectExpr(doco, name, oAs, oImplements, + builder.Script(oExtends, methods, matchers, span), span) + + def objectFunction(name, indent, tryAgain, ej, spanStart): + acceptTag("(", ej) + def patts := acceptList(pattern) + acceptTag(")", ej) + def resultguard := if (peekTag() == ":") { + advance(ej) + if (peekTag() == "EOL") { + # Oops, end of indenty block. + position -= 1 + null + } else { + guard(ej) + } + } else { + null + } + def [oAs, oImplements] := oAuditors(ej) + if (indent): + blockLookahead(tryAgain) + def [doco, body] := suite(methBody, indent, ej) + def span := spanFrom(spanStart) + return builder.ObjectExpr(doco, name, oAs, oImplements, + builder.FunctionScript(patts, resultguard, body, span), span) + + def paramDesc(ej): + def spanStart := spanHere() + def name := if (peekTag() == "_") { + advance(ej) + null + } else if (peekTag() == "IDENTIFIER") { + def t := advance(ej) + __makeString.fromString(t.getData(), t.getSpan()) + } else { + acceptTag("::", ej) + acceptTag(".String.", ej) + } + def g := if (peekTag() == ":") { + advance(ej) + guard(ej) + } else { + null + } + return builder.ParamDesc(name, g, spanFrom(spanStart)) + + def messageDescInner(indent, tryAgain, ej): + acceptTag("(", ej) + def params := acceptList(paramDesc) + acceptTag(")", ej) + def resultguard := if (peekTag() == ":") { + advance(ej) + if (peekTag() == "EOL") { + # Oops, end of indenty block. + position -= 1 + null + } else { + guard(ej) + } + } else { + null + } + def doco := if ([":", "{"].contains(peekTag())) { + if (indent) { + blockLookahead(tryAgain) + } + suite(fn i, j {acceptEOLs(); acceptTag(".String.", j).getData()}, indent, ej) + } else { + null + } + return [doco, params, resultguard] + + def messageDesc(indent, ej): + def spanStart := spanHere() + acceptTag("to", ej) + def verb := if (peekTag() == ".String.") { + advance(ej) + } else { + def t := acceptTag("IDENTIFIER", ej) + __makeString.fromString(t.getData(), t.getSpan()) + } + def [doco, params, resultguard] := messageDescInner(indent, ej, ej) + return builder.MessageDesc(doco, verb, params, resultguard, spanFrom(spanStart)) + + def interfaceBody(indent, ej): + def doco := if (peekTag() == ".String.") { + advance(ej).getData() + } else { + null + } + def msgs := [].diverge() + while (true): + acceptEOLs() + if (peekTag() == "pass"): + advance(ej) + continue + msgs.push(messageDesc(indent, __break)) + return [doco, msgs.snapshot()] + + def basic(indent, tryAgain, ej): + def origPosition := position + def tag := peekTag() + if (tag == "if"): + def spanStart := spanHere() + advance(ej) + acceptTag("(", ej) + def test := expr(ej) + acceptTag(")", ej) + if (indent): + blockLookahead(tryAgain) + def consq := block(indent, ej) + def maybeElseStart := position + if (indent): + acceptEOLs() + def alt := if (matchEOLsThenTag(indent, "else")) { + if (peekTag() == "if") { + basic(indent, ej, ej) + } else { + block(indent, ej) + }} else { + position := maybeElseStart + null + } + return builder.IfExpr(test, consq, alt, spanFrom(spanStart)) + if (tag == "escape"): + def spanStart := spanHere() + advance(ej) + def p1 := pattern(ej) + if (indent): + blockLookahead(tryAgain) + def e1 := block(indent, ej) + if (matchEOLsThenTag(indent, "catch")): + def p2 := pattern(ej) + def e2 := block(indent, ej) + return builder.EscapeExpr(p1, e1, p2, e2, spanFrom(spanStart)) + return builder.EscapeExpr(p1, e1, null, null, spanFrom(spanStart)) + if (tag == "for"): + def spanStart := spanHere() + advance(ej) + def [k, v, it] := forExprHead(false, ej) + if (indent): + blockLookahead(tryAgain) + def body := block(indent, ej) + def [catchPattern, catchBody] := if (matchEOLsThenTag(indent, "catch")) { + [pattern(ej), block(indent, ej)] + } else { + [null, null] + } + return builder.ForExpr(it, k, v, body, catchPattern, catchBody, spanFrom(spanStart)) + if (tag == "fn"): + def spanStart := spanHere() + advance(ej) + def patt := acceptList(pattern) + def body := block(false, ej) + return builder.FunctionExpr(patt, body, spanFrom(spanStart)) + if (tag == "switch"): + def spanStart := spanHere() + advance(ej) + acceptTag("(", ej) + def spec := expr(ej) + acceptTag(")", ej) + if (indent): + blockLookahead(tryAgain) + return builder.SwitchExpr( + spec, + suite(fn i, j {repeat(matchers, i, j)}, indent, ej), + spanFrom(spanStart)) + if (tag == "try"): + def spanStart := spanHere() + advance(ej) + if (indent): + blockLookahead(tryAgain) + def tryblock := block(indent, ej) + def catchers := [].diverge() + while (matchEOLsThenTag(indent, "catch")): + catchers.push(catcher(indent, ej)) + def origPosition := position + def finallyblock := if (matchEOLsThenTag(indent, "finally")) { + block(indent, ej) + } else { + null + } + return builder.TryExpr(tryblock, catchers.snapshot(), + finallyblock, spanFrom(spanStart)) + if (tag == "while"): + def spanStart := spanHere() + advance(ej) + acceptTag("(", ej) + def test := expr(ej) + acceptTag(")", ej) + if (indent): + blockLookahead(tryAgain) + def whileblock := block(indent, ej) + def catchblock := if (matchEOLsThenTag(indent, "catch")) { + catcher(indent, ej) + } else { + null + } + return builder.WhileExpr(test, whileblock, catchblock, spanFrom(spanStart)) + if (tag == "when"): + def spanStart := spanHere() + advance(ej) + acceptTag("(", ej) + def exprs := acceptList(expr) + acceptTag(")", ej) + acceptTag("->", ej) + if (indent): + acceptEOLs() + acceptTag("INDENT", tryAgain) + else: + acceptTag("{", ej) + def whenblock := escape e { + seq(indent, ej) + } catch _ { + builder.SeqExpr([], null) + } + if (indent): + acceptTag("DEDENT", ej) + else: + acceptTag("}", ej) + def catchers := [].diverge() + while (matchEOLsThenTag(indent, "catch")): + catchers.push(catcher(indent, ej)) + def finallyblock := if (matchEOLsThenTag(indent, "finally")) { + block(indent, ej) + } else { + null + } + return builder.WhenExpr(exprs, whenblock, catchers.snapshot(), + finallyblock, spanFrom(spanStart)) + if (tag == "bind"): + def spanStart := spanHere() + advance(ej) + def name := builder.BindPattern(noun(ej), spanFrom(spanStart)) + if (peekTag() == "("): + return objectFunction(name, indent, tryAgain, ej, spanStart) + else if (peekTag() == ":="): + position := origPosition + return assign(ej) + else: + return objectExpr(name, indent, tryAgain, ej, spanStart) + + if (tag == "object"): + def spanStart := spanHere() + advance(ej) + def name := if (peekTag() == "bind") { + advance(ej) + builder.BindPattern(noun(ej), spanFrom(spanStart)) + } else if (peekTag() == "_") { + advance(ej) + builder.IgnorePattern(null, spanHere()) + } else { + builder.FinalPattern(noun(ej), null, spanFrom(spanStart)) + } + return objectExpr(name, indent, tryAgain, ej, spanStart) + + if (tag == "def"): + def spanStart := spanHere() + advance(ej) + var isBind := false + if (!["IDENTIFIER", "::", "bind"].contains(peekTag())): + position := origPosition + return assign(ej) + def name := if (peekTag() == "bind") { + advance(ej) + isBind := true + builder.BindPattern(noun(ej), spanFrom(spanStart)) + } else { + builder.FinalPattern(noun(ej), null, spanFrom(spanStart)) + } + if (peekTag() == "("): + return objectFunction(name, indent, tryAgain, ej, spanStart) + else if (["exit", ":=", "QUASI_OPEN", "?", ":"].contains(peekTag())): + position := origPosition + return assign(ej) + else if (isBind): + throw.eject(ej, ["expected :=", spanHere()]) + else: + return builder.ForwardExpr(name, spanFrom(spanStart)) + + if (tag == "interface"): + def spanStart := spanHere() + advance(ej) + def name := namePattern(ej, false) + def guards_ := if (peekTag() == "guards") { + advance(ej) + pattern(ej) + } else { + null + } + def extends_ := if (peekTag() == "extends") { + advance(ej) + acceptList(order) + } else { + [] + } + def implements_ := if (peekTag() == "implements") { + advance(ej) + acceptList(order) + } else { + [] + } + if (peekTag() == "("): + def [doco, params, resultguard] := messageDescInner(indent, tryAgain, ej) + return builder.FunctionInterfaceExpr(doco, name, guards_, extends_, implements_, + builder.MessageDesc(doco, "run", params, resultguard, spanFrom(spanStart)), + spanFrom(spanStart)) + if (indent): + blockLookahead(tryAgain) + def [doco, msgs] := suite(interfaceBody, indent, ej) + return builder.InterfaceExpr(doco, name, guards_, extends_, implements_, msgs, + spanFrom(spanStart)) + if (peekTag() == "meta"): + def spanStart := spanHere() + acceptTag("meta", ej) + acceptTag(".", ej) + def verb := acceptTag("IDENTIFIER", ej) + if (verb.getData() == "context"): + acceptTag("(", ej) + acceptTag(")", ej) + return builder.MetaContextExpr(spanFrom(spanStart)) + if (verb.getData() == "getState"): + acceptTag("(", ej) + acceptTag(")", ej) + return builder.MetaStateExpr(spanFrom(spanStart)) + throw.eject(ej, [`Meta verbs are "context" or "getState"`, spanHere()]) + + if (indent && peekTag() == "pass"): + advance(ej) + return builder.SeqExpr([], advance(ej).getSpan()) + throw.eject(tryAgain, [`don't recognize $tag`, spanHere()]) + + bind blockExpr(ej): + def origPosition := position + escape e: + return basic(true, e, ej) + position := origPosition + return expr(ej) + "XXX buggy expander eats this line" + + bind prim(ej): + def tag := peekTag() + if ([".String.", ".int.", ".float64.", ".char."].contains(tag)): + def t := advance(ej) + return builder.LiteralExpr(t.getData(), t.getSpan()) + if (tag == "IDENTIFIER"): + def t := advance(ej) + def nex := peekTag() + if (nex == "QUASI_OPEN" || nex == "QUASI_CLOSE"): + return quasiliteral(t, false, ej) + else: + return builder.NounExpr(t.getData(), t.getSpan()) + if (tag == "::"): + def spanStart := spanHere() + advance(ej) + def t := acceptTag(".String.", ej) + return builder.NounExpr(t.getData(), t.getSpan()) + if (tag == "QUASI_OPEN" || tag == "QUASI_CLOSE"): + return quasiliteral(null, false, ej) + # paren expr + if (tag == "("): + advance(ej) + acceptEOLs() + def e := seq(false, ej) + acceptEOLs() + acceptTag(")", ej) + return e + # hideexpr + if (tag == "{"): + def spanStart := spanHere() + advance(ej) + acceptEOLs() + if (peekTag() == "}"): + advance(ej) + return builder.HideExpr(builder.SeqExpr([], null), spanFrom(spanStart)) + def e := seq(false, ej) + acceptEOLs() + acceptTag("}", ej) + return builder.HideExpr(e, spanFrom(spanStart)) + # list/map + if (tag == "["): + def spanStart := spanHere() + advance(ej) + acceptEOLs() + if (peekTag() == "for"): + advance(ej) + def [k, v, it] := forExprHead(true, ej) + def filt := if (peekTag() == "if") { + advance(ej) + acceptTag("(", ej) + acceptEOLs() + def e := expr(ej) + acceptEOLs() + acceptTag(")", ej) + e + } else { + null + } + acceptEOLs() + def body := expr(ej) + if (peekTag() == "=>"): + advance(ej) + acceptEOLs() + def vbody := expr(ej) + acceptTag("]", ej) + return builder.MapComprehensionExpr(it, filt, k, v, body, vbody, + spanFrom(spanStart)) + acceptTag("]", ej) + return builder.ListComprehensionExpr(it, filt, k, v, body, + spanFrom(spanStart)) + def [items, isMap] := acceptListOrMap(expr, mapItem) + acceptEOLs() + acceptTag("]", ej) + if (isMap): + return builder.MapExpr(items, spanFrom(spanStart)) + else: + return builder.ListExpr(items, spanFrom(spanStart)) + return basic(false, ej, ej) + "XXX buggy expander eats this line" + def call(ej): + def spanStart := spanHere() + def base := prim(ej) + def trailers := [].diverge() + + def callish(methodish, curryish): + def verb := if (peekTag() == ".String.") { + advance(ej).getData() + } else { + def t := acceptTag("IDENTIFIER", ej) + __makeString.fromString(t.getData(), t.getSpan()) + } + if (peekTag() == "("): + advance(ej) + def arglist := acceptList(expr) + acceptEOLs() + acceptTag(")", ej) + trailers.push([methodish, [verb, arglist, spanFrom(spanStart)]]) + return false + else: + trailers.push(["CurryExpr", [verb, curryish, spanFrom(spanStart)]]) + return true + + def funcallish(name): + acceptTag("(", ej) + def arglist := acceptList(expr) + acceptEOLs() + acceptTag(")", ej) + trailers.push([name, [arglist, spanFrom(spanStart)]]) + + while (true): + if (peekTag() == "."): + advance(ej) + if (callish("MethodCallExpr", false)): + break + else if (peekTag() == "("): + funcallish("FunCallExpr") + else if (peekTag() == "<-"): + advance(ej) + if (peekTag() == "("): + funcallish("FunSendExpr") + else: + if(callish("SendExpr", true)): + break + else if (peekTag() == "["): + advance(ej) + def arglist := acceptList(expr) + acceptEOLs() + acceptTag("]", ej) + trailers.push(["GetExpr", [arglist, spanFrom(spanStart)]]) + else: + break + var result := base + for tr in trailers: + result := M.call(builder, tr[0], [result] + tr[1]) + return result + + def prefix(ej): + def spanStart := spanHere() + def op := peekTag() + if (op == "-"): + advance(ej) + return builder.PrefixExpr("-", prim(ej), spanFrom(spanStart)) + if (["~", "!"].contains(op)): + advance(ej) + return builder.PrefixExpr(op, call(ej), spanFrom(spanStart)) + if (op == "&"): + advance(ej) + return builder.SlotExpr(noun(ej), spanFrom(spanStart)) + if (op == "&&"): + advance(ej) + return builder.BindingExpr(noun(ej), spanFrom(spanStart)) + def base := call(ej) + if (peekTag() == ":"): + advance(ej) + if (peekTag() == "EOL"): + # oops, a token too far + position -= 1 + return base + return builder.CoerceExpr(base, guard(ej), spanFrom(spanHere)) + return base + def operators := [ + "**" => 1, + "*" => 2, + "/" => 2, + "//" => 2, + "%" => 2, + "+" => 3, + "-" => 3, + "<<" => 4, + ">>" => 4, + ".." => 5, + "..!" => 5, + ">" => 6, + "<" => 6, + ">=" => 6, + "<=" => 6, + "<=>" => 6, + "=~" => 7, + "!~" => 7, + "==" => 7, + "!=" => 7, + "&!" => 7, + "^" => 7, + "&" => 8, + "|" => 8, + "&&" => 9, + "||" => 10] + + def leftAssociative := ["+", "-", ">>", "<<", "/", "*", "//", "%"] + def selfAssociative := ["|", "&"] + def convertInfix(maxPrec, ej): + def lhs := prefix(ej) + def output := [lhs].diverge() + def opstack := [].diverge() + def emitTop(): + def [_, opName] := opstack.pop() + def rhs := output.pop() + def lhs := output.pop() + def tehSpan := spanCover(lhs.getSpan(), rhs.getSpan()) + if (opName == "=="): + output.push(builder.SameExpr(lhs, rhs, true, tehSpan)) + else if (opName == "!="): + output.push(builder.SameExpr(lhs, rhs, false, tehSpan)) + else if (opName == "&&"): + output.push(builder.AndExpr(lhs, rhs, tehSpan)) + else if (opName == "||"): + output.push(builder.OrExpr(lhs, rhs, tehSpan)) + else if (["..", "..!"].contains(opName)): + output.push(builder.RangeExpr(lhs, opName, rhs, tehSpan)) + else if (opName == "=~"): + output.push(builder.MatchBindExpr(lhs, rhs, tehSpan)) + else if ([">", "<", ">=", "<=", "<=>"].contains(opName)): + output.push(builder.CompareExpr(lhs, opName, rhs, tehSpan)) + else: + output.push(builder.BinaryExpr(lhs, opName, rhs, tehSpan)) + + while (true): + def op := peekTag() + def nextPrec := operators.fetch(op, __break) + if (nextPrec > maxPrec): + break + advance(ej) + acceptEOLs() + # XXX buggy expander can't handle compound booleans + if (opstack.size() > 0): + def selfy := selfAssociative.contains(op) && (opstack.last()[1] == op) + def lefty := leftAssociative.contains(op) && opstack.last()[0] <= nextPrec + def b2 := lefty || selfy + if (opstack.last()[0] < nextPrec || b2): + emitTop() + opstack.push([operators[op], op]) + if (["=~", "!~"].contains(op)): + output.push(pattern(ej)) + else: + output.push(prefix(ej)) + while (opstack.size() > 0): + emitTop() + if (output.size() != 1): + throw(["Internal parser error", spanHere()]) + return output[0] + + bind order(ej): + return convertInfix(6, ej) + "XXX buggy expander eats this line" + + def infix(ej): + return convertInfix(10, ej) + + def _assign(ej): + def spanStart := spanHere() + def defStart := position + if (peekTag() == "def"): + advance(ej) + def patt := pattern(ej) + def ex := if (peekTag() == "exit") { + advance(ej) + order(ej) + } else { + null + } + # careful, this might be a trap + if (peekTag() == ":="): + advance(ej) + return builder.DefExpr(patt, ex, assign(ej), spanFrom(spanStart)) + else: + # bail out! + position := defStart + return basic(false, ej, ej) + if (["var", "bind"].contains(peekTag())): + def patt := pattern(ej) + if (peekTag() == ":="): + advance(ej) + return builder.DefExpr(patt, null, assign(ej), spanFrom(spanStart)) + else: + # curses, foiled again + position := defStart + return basic(false, ej, ej) + def lval := infix(ej) + if (peekTag() == ":="): + advance(ej) + def lt := lval.asTerm().getTag().getName() + if (["NounExpr", "GetExpr"].contains(lt)): + return builder.AssignExpr(lval, assign(ej), spanFrom(spanStart)) + throw.eject(ej, [`Invalid assignment target`, lt.getSpan()]) + if (peekTag() =~ `@op=`): + advance(ej) + def lt := lval.asTerm().getTag().getName() + if (["NounExpr", "GetExpr"].contains(lt)): + return builder.AugAssignExpr(op, lval, assign(ej), spanFrom(spanStart)) + throw.eject(ej, [`Invalid assignment target`, lt.getSpan()]) + if (peekTag() == "VERB_ASSIGN"): + def verb := advance(ej).getData() + def lt := lval.asTerm().getTag().getName() + if (["NounExpr", "GetExpr"].contains(lt)): + acceptTag("(", ej) + acceptEOLs() + def node := builder.VerbAssignExpr(verb, lval, acceptList(expr), + spanFrom(spanStart)) + acceptEOLs() + acceptTag(")", ej) + return node + throw.eject(ej, [`Invalid assignment target`, lt.getSpan()]) + return lval + bind assign := _assign + + def _expr(ej): + if (["continue", "break", "return"].contains(peekTag())): + def spanStart := spanHere() + def ex := advanceTag(ej) + if (peekTag() == "(" && tokens[position + 2].getTag().getName() == ")"): + position += 2 + return builder.ExitExpr(ex, null, spanFrom(spanStart)) + if (["EOL", "#", ";", "DEDENT", null].contains(peekTag())): + return builder.ExitExpr(ex, null, spanFrom(spanStart)) + def val := blockExpr(ej) + return builder.ExitExpr(ex, val, spanFrom(spanStart)) + return assign(ej) + + bind expr := _expr + + def module_(ej): + def start := spanHere() + def modKw := acceptTag("module", ej) + def imports := acceptList(pattern) + acceptEOLs() + def exports := if (peekTag() == "export") { + advance(ej) + acceptTag("(", ej) + def exports := acceptList(noun) + acceptTag(")", ej) + acceptEOLs() + exports + } + def body := seq(true, ej) + return builder."Module"(imports, exports, body, spanFrom(start)) + + def start(ej): + acceptEOLs() + if (peekTag() == "module"): + return module_(ej) + else: + return seq(true, ej) + if (mode == "module"): + def val := start(err) + acceptEOLs() + if (position < (tokens.size() - 1)): + throw.eject(err, `only got "$val". Trailing garbage: ${tokens.slice(position, tokens.size())}`) + return val + else if (mode == "expression"): + return blockExpr(err) + else if (mode == "pattern"): + return pattern(err) + return "broke" + +def parseExpression(lex, builder, err): + return parseMonte(lex, builder, "expression", err) + +def parseModule(lex, builder, err): + return parseMonte(lex, builder, "module", err) + +def parsePattern(lex, builder, err): + return parseMonte(lex, builder, "pattern", err) + +# object quasiMonteParser: +# to valueHole(n): +# return VALUE_HOLE +# to patternHole(n): +# return PATTERN_HOLE + +# to valueMaker(template): +# def chain := makeQuasiTokenChain(makeMonteLexer, template) +# def q := makeMonteParser(chain, astBuilder) +# return object qast extends q: +# to substitute(values): +# return q.transform(holeFiller) + +# to matchMaker(template): +# def chain := makeQuasiTokenChain(makeMonteLexer, template) +# def q := makeMonteParser(chain, astBuilder) +# return object qast extends q: +# to matchBind(values, specimen, ej): +# escape ej: +# def holeMatcher := makeHoleMatcher(ej) +# q.transform(holeMatcher) +# return holeMatcher.getBindings() +# catch blee: +# ej(`$q doesn't match $specimen: $blee`) + +# Tests. + +def expr(s): + return parseExpression(makeMonteLexer(s + "\n"), astBuilder, throw).asTerm() + +def pattern(s): + return parsePattern(makeMonteLexer(s), astBuilder, throw).asTerm() + +def test_Literal(assert): + assert.equal(expr("\"foo bar\""), term`LiteralExpr("foo bar")`) + assert.equal(expr("'z'"), term`LiteralExpr('z')`) + assert.equal(expr("7"), term`LiteralExpr(7)`) + assert.equal(expr("(7)"), term`LiteralExpr(7)`) + assert.equal(expr("0.5"), term`LiteralExpr(0.5)`) + +def test_Noun(assert): + assert.equal(expr("foo"), term`NounExpr("foo")`) + assert.equal(expr("::\"object\""), term`NounExpr("object")`) + +def test_QuasiliteralExpr(assert): + assert.equal(expr("`foo`"), term`QuasiParserExpr(null, [QuasiText("foo")])`) + assert.equal(expr("bob`foo`"), term`QuasiParserExpr("bob", [QuasiText("foo")])`) + assert.equal(expr("bob`foo`` $x baz`"), term`QuasiParserExpr("bob", [QuasiText("foo`` "), QuasiExprHole(NounExpr("x")), QuasiText(" baz")])`) + assert.equal(expr("`($x)`"), term`QuasiParserExpr(null, [QuasiText("("), QuasiExprHole(NounExpr("x")), QuasiText(")")])`) + +def test_Hide(assert): + assert.equal(expr("{}"), term`HideExpr(SeqExpr([]))`) + assert.equal(expr("{1}"), term`HideExpr(LiteralExpr(1))`) + +def test_List(assert): + assert.equal(expr("[]"), term`ListExpr([])`) + assert.equal(expr("[a, b]"), term`ListExpr([NounExpr("a"), NounExpr("b")])`) + +def test_Map(assert): + assert.equal(expr("[k => v, => a]"), + term`MapExpr([MapExprAssoc(NounExpr("k"), NounExpr("v")), + MapExprExport(NounExpr("a"))])`) + assert.equal(expr("[=> b, k => v]"), + term`MapExpr([MapExprExport(NounExpr("b")), + MapExprAssoc(NounExpr("k"), NounExpr("v"))])`) + +def test_ListComprehensionExpr(assert): + assert.equal(expr("[for k => v in (a) if (b) c]"), term`ListComprehensionExpr(NounExpr("a"), NounExpr("b"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), NounExpr("c"))`) + assert.equal(expr("[for v in (a) c]"), term`ListComprehensionExpr(NounExpr("a"), null, null, FinalPattern(NounExpr("v"), null), NounExpr("c"))`) + +def test_MapComprehensionExpr(assert): + assert.equal(expr("[for k => v in (a) if (b) k1 => v1]"), term`MapComprehensionExpr(NounExpr("a"), NounExpr("b"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), NounExpr("k1"), NounExpr("v1"))`) + assert.equal(expr("[for v in (a) k1 => v1]"), term`MapComprehensionExpr(NounExpr("a"), null, null, FinalPattern(NounExpr("v"), null), NounExpr("k1"), NounExpr("v1"))`) + +def test_IfExpr(assert): + assert.equal(expr("if (1) {2} else if (3) {4} else {5}"), + term`IfExpr(LiteralExpr(1), LiteralExpr(2), IfExpr(LiteralExpr(3), LiteralExpr(4), LiteralExpr(5)))`) + assert.equal(expr("if (1) {2} else {3}"), term`IfExpr(LiteralExpr(1), LiteralExpr(2), LiteralExpr(3))`) + assert.equal(expr("if (1) {2}"), term`IfExpr(LiteralExpr(1), LiteralExpr(2), null)`) + assert.equal(expr("if (1):\n 2\nelse if (3):\n 4\nelse:\n 5"), + term`IfExpr(LiteralExpr(1), LiteralExpr(2), IfExpr(LiteralExpr(3), LiteralExpr(4), LiteralExpr(5)))`) + assert.equal(expr("if (1):\n 2\nelse:\n 3"), term`IfExpr(LiteralExpr(1), LiteralExpr(2), LiteralExpr(3))`) + assert.equal(expr("if (1):\n 2"), term`IfExpr(LiteralExpr(1), LiteralExpr(2), null)`) + + +def test_EscapeExpr(assert): + assert.equal(expr("escape e {1} catch p {2}"), + term`EscapeExpr(FinalPattern(NounExpr("e"), null), LiteralExpr(1), FinalPattern(NounExpr("p"), null), LiteralExpr(2))`) + assert.equal(expr("escape e {1}"), + term`EscapeExpr(FinalPattern(NounExpr("e"), null), LiteralExpr(1), null, null)`) + assert.equal(expr("escape e:\n 1\ncatch p:\n 2"), + term`EscapeExpr(FinalPattern(NounExpr("e"), null), LiteralExpr(1), FinalPattern(NounExpr("p"), null), LiteralExpr(2))`) + assert.equal(expr("escape e:\n 1"), + term`EscapeExpr(FinalPattern(NounExpr("e"), null), LiteralExpr(1), null, null)`) + +def test_ForExpr(assert): + assert.equal(expr("for v in foo {1}"), term`ForExpr(NounExpr("foo"), null, FinalPattern(NounExpr("v"), null), LiteralExpr(1), null, null)`) + assert.equal(expr("for k => v in foo {1}"), term`ForExpr(NounExpr("foo"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), LiteralExpr(1), null, null)`) + assert.equal(expr("for k => v in foo {1} catch p {2}"), term`ForExpr(NounExpr("foo"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), LiteralExpr(1), FinalPattern(NounExpr("p"), null), LiteralExpr(2))`) + assert.equal(expr("for v in foo:\n 1"), term`ForExpr(NounExpr("foo"), null, FinalPattern(NounExpr("v"), null), LiteralExpr(1), null, null)`) + assert.equal(expr("for k => v in foo:\n 1"), term`ForExpr(NounExpr("foo"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), LiteralExpr(1), null, null)`) + assert.equal(expr("for k => v in foo:\n 1\ncatch p:\n 2"), term`ForExpr(NounExpr("foo"), FinalPattern(NounExpr("k"), null), FinalPattern(NounExpr("v"), null), LiteralExpr(1), FinalPattern(NounExpr("p"), null), LiteralExpr(2))`) + + +def test_FunctionExpr(assert): + assert.equal(expr("fn {1}"), term`FunctionExpr([], LiteralExpr(1))`) + assert.equal(expr("fn a, b {1}"), term`FunctionExpr([FinalPattern(NounExpr("a"), null), FinalPattern(NounExpr("b"), null)], LiteralExpr(1))`) + +def test_SwitchExpr(assert): + assert.equal(expr("switch (1) {match p {2} match q {3}}"), term`SwitchExpr(LiteralExpr(1), [Matcher(FinalPattern(NounExpr("p"), null), LiteralExpr(2)), Matcher(FinalPattern(NounExpr("q"), null), LiteralExpr(3))])`) + assert.equal(expr("switch (1):\n match p:\n 2\n match q:\n 3"), term`SwitchExpr(LiteralExpr(1), [Matcher(FinalPattern(NounExpr("p"), null), LiteralExpr(2)), Matcher(FinalPattern(NounExpr("q"), null), LiteralExpr(3))])`) + +def test_TryExpr(assert): + assert.equal(expr("try {1} catch p {2} catch q {3} finally {4}"), + term`TryExpr(LiteralExpr(1), [Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(2)), Catcher(FinalPattern(NounExpr("q"), null), LiteralExpr(3))], LiteralExpr(4))`) + assert.equal(expr("try {1} finally {2}"), + term`TryExpr(LiteralExpr(1), [], LiteralExpr(2))`) + assert.equal(expr("try {1} catch p {2}"), + term`TryExpr(LiteralExpr(1), [Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(2))], null)`) + assert.equal(expr("try:\n 1\ncatch p:\n 2\ncatch q:\n 3\nfinally:\n 4"), + term`TryExpr(LiteralExpr(1), [Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(2)), Catcher(FinalPattern(NounExpr("q"), null), LiteralExpr(3))], LiteralExpr(4))`) + assert.equal(expr("try:\n 1\nfinally:\n 2"), + term`TryExpr(LiteralExpr(1), [], LiteralExpr(2))`) + assert.equal(expr("try:\n 1\ncatch p:\n 2"), + term`TryExpr(LiteralExpr(1), [Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(2))], null)`) + +def test_WhileExpr(assert): + assert.equal(expr("while (1):\n 2"), term`WhileExpr(LiteralExpr(1), LiteralExpr(2), null)`) + assert.equal(expr("while (1):\n 2\ncatch p:\n 3"), term`WhileExpr(LiteralExpr(1), LiteralExpr(2), Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(3)))`) + +def test_WhenExpr(assert): + assert.equal(expr("when (1) -> {2}"), term`WhenExpr([LiteralExpr(1)], LiteralExpr(2), [], null)`) + assert.equal(expr("when (1, 2) -> {3}"), term`WhenExpr([LiteralExpr(1), LiteralExpr(2)], LiteralExpr(3), [], null)`) + assert.equal(expr("when (1) -> {2} catch p {3}"), term`WhenExpr([LiteralExpr(1)], LiteralExpr(2), [Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(3))], null)`) + assert.equal(expr("when (1) -> {2} finally {3}"), term`WhenExpr([LiteralExpr(1)], LiteralExpr(2), [], LiteralExpr(3))`) + assert.equal(expr("when (1) -> {2} catch p {3} finally {4}"), term`WhenExpr([LiteralExpr(1)], LiteralExpr(2), [Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(3))], LiteralExpr(4))`) + assert.equal(expr("when (1) ->\n 2"), term`WhenExpr([LiteralExpr(1)], LiteralExpr(2), [], null)`) + assert.equal(expr("when (1, 2) ->\n 3"), term`WhenExpr([LiteralExpr(1), LiteralExpr(2)], LiteralExpr(3), [], null)`) + assert.equal(expr("when (1) ->\n 2\ncatch p:\n 3"), term`WhenExpr([LiteralExpr(1)], LiteralExpr(2), [Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(3))], null)`) + assert.equal(expr("when (1) ->\n 2\nfinally:\n 3"), term`WhenExpr([LiteralExpr(1)], LiteralExpr(2), [], LiteralExpr(3))`) + assert.equal(expr("when (1) ->\n 2\ncatch p:\n 3\nfinally:\n 4"), term`WhenExpr([LiteralExpr(1)], LiteralExpr(2), [Catcher(FinalPattern(NounExpr("p"), null), LiteralExpr(3))], LiteralExpr(4))`) + +def test_ObjectExpr(assert): + assert.equal(expr("object foo {}"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], Script(null, [], []))`) + assert.equal(expr("object _ {}"), term`ObjectExpr(null, IgnorePattern(null), null, [], Script(null, [], []))`) + assert.equal(expr("object ::\"object\" {}"), term`ObjectExpr(null, FinalPattern(NounExpr("object"), null), null, [], Script(null, [], []))`) + assert.equal(expr("bind foo {}"), term`ObjectExpr(null, BindPattern(NounExpr("foo")), null, [], Script(null, [], []))`) + assert.equal(expr("object bind foo {}"), term`ObjectExpr(null, BindPattern(NounExpr("foo")), null, [], Script(null, [], []))`) + assert.equal(expr("object foo { to doA(x, y) :z {0} method blee() {1} to \"object\"() {2} match p {3} match q {4}}"), + term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], Script(null, [To(null, "doA", [FinalPattern(NounExpr("x"), null), FinalPattern(NounExpr("y"), null)], NounExpr("z"), LiteralExpr(0)), Method(null, "blee", [], null, LiteralExpr(1)), To(null, "object", [], null, LiteralExpr(2))], [Matcher(FinalPattern(NounExpr("p"), null), LiteralExpr(3)), Matcher(FinalPattern(NounExpr("q"), null), LiteralExpr(4))]))`) + assert.equal(expr("object foo {\"hello\" to blee() {\"yes\"\n1}}"), term`ObjectExpr("hello", FinalPattern(NounExpr("foo"), null), null, [], Script(null, [To("yes", "blee", [], null, LiteralExpr(1))], []))`) + assert.equal(expr("object foo as A implements B, C {}"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), NounExpr("A"), [NounExpr("B"), NounExpr("C")], Script(null, [], []))`) + assert.equal(expr("object foo extends baz {}"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], Script(NounExpr("baz"), [], []))`) + + assert.equal(expr("object foo:\n pass"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], Script(null, [], []))`) + assert.equal(expr("object _:\n pass"), term`ObjectExpr(null, IgnorePattern(null), null, [], Script(null, [], []))`) + assert.equal(expr("object ::\"object\":\n pass"), term`ObjectExpr(null, FinalPattern(NounExpr("object"), null), null, [], Script(null, [], []))`) + assert.equal(expr("bind foo:\n pass"), term`ObjectExpr(null, BindPattern(NounExpr("foo")), null, [], Script(null, [], []))`) + assert.equal(expr("object bind foo:\n pass"), term`ObjectExpr(null, BindPattern(NounExpr("foo")), null, [], Script(null, [], []))`) + assert.equal(expr("object foo:\n to doA(x, y) :z:\n 0\n method blee():\n 1\n to \"object\"():\n 2\n match p:\n 3\n match q:\n 4"), + term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], Script(null, [To(null, "doA", [FinalPattern(NounExpr("x"), null), FinalPattern(NounExpr("y"), null)], NounExpr("z"), LiteralExpr(0)), Method(null, "blee", [], null, LiteralExpr(1)), To(null, "object", [], null, LiteralExpr(2))], [Matcher(FinalPattern(NounExpr("p"), null), LiteralExpr(3)), Matcher(FinalPattern(NounExpr("q"), null), LiteralExpr(4))]))`) + assert.equal(expr("object foo:\n \"hello\"\n to blee():\n \"yes\"\n 1"), term`ObjectExpr("hello", FinalPattern(NounExpr("foo"), null), null, [], Script(null, [To("yes", "blee", [], null, LiteralExpr(1))], []))`) + assert.equal(expr("object foo as A implements B, C:\n pass"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), NounExpr("A"), [NounExpr("B"), NounExpr("C")], Script(null, [], []))`) + assert.equal(expr("object foo extends baz:\n pass"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], Script(NounExpr("baz"), [], []))`) + +def test_Function(assert): + assert.equal(expr("def foo() {1}"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], FunctionScript([], null, LiteralExpr(1)))`) + assert.equal(expr("def foo(a, b) :c {1}"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], FunctionScript([FinalPattern(NounExpr("a"), null), FinalPattern(NounExpr("b"), null)], NounExpr("c"), LiteralExpr(1)))`) + assert.equal(expr("def foo():\n 1"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], FunctionScript([], null, LiteralExpr(1)))`) + assert.equal(expr("def foo(a, b) :c:\n 1"), term`ObjectExpr(null, FinalPattern(NounExpr("foo"), null), null, [], FunctionScript([FinalPattern(NounExpr("a"), null), FinalPattern(NounExpr("b"), null)], NounExpr("c"), LiteralExpr(1)))`) + +def test_Interface(assert): + assert.equal(expr("interface foo {\"yes\"}"), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [], [], [])`) + assert.equal(expr("interface foo extends baz, blee {\"yes\"}"), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [NounExpr("baz"), NounExpr("blee")], [], [])`) + assert.equal(expr("interface foo implements bar {\"yes\"}"), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [], [NounExpr("bar")], [])`) + assert.equal(expr("interface foo extends baz implements boz, bar {}"), term`InterfaceExpr(null, FinalPattern(NounExpr("foo"), null), null, [NounExpr("baz")], [NounExpr("boz"), NounExpr("bar")], [])`) + assert.equal(expr("interface foo guards FooStamp extends boz, biz implements bar {}"), term`InterfaceExpr(null, FinalPattern(NounExpr("foo"), null), FinalPattern(NounExpr("FooStamp"), null), [NounExpr("boz"), NounExpr("biz")], [NounExpr("bar")], [])`) + assert.equal(expr("interface foo {\"yes\"\nto run(a :int, b :float64) :any}"), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [], [], [MessageDesc(null, "run", [ParamDesc("a", NounExpr("int")), ParamDesc("b", NounExpr("float64"))], NounExpr("any"))])`) + assert.equal(expr("interface foo {\"yes\"\nto run(a :int, b :float64) :any {\"msg docstring\"}}"), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [], [], [MessageDesc("msg docstring", "run", [ParamDesc("a", NounExpr("int")), ParamDesc("b", NounExpr("float64"))], NounExpr("any"))])`) + assert.equal(expr("interface foo(a :int, b :float64) :any {\"msg docstring\"}"), term`FunctionInterfaceExpr("msg docstring", FinalPattern(NounExpr("foo"), null), null, [], [], MessageDesc("msg docstring", "run", [ParamDesc("a", NounExpr("int")), ParamDesc("b", NounExpr("float64"))], NounExpr("any")))`) + assert.equal(expr("interface foo(a :int, b :float64) :any"), term`FunctionInterfaceExpr(null, FinalPattern(NounExpr("foo"), null), null, [], [], MessageDesc(null, "run", [ParamDesc("a", NounExpr("int")), ParamDesc("b", NounExpr("float64"))], NounExpr("any")))`) + + assert.equal(expr("interface foo:\n \"yes\""), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [], [], [])`) + assert.equal(expr("interface foo extends baz, blee:\n \"yes\""), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [NounExpr("baz"), NounExpr("blee")], [], [])`) + assert.equal(expr("interface foo implements bar:\n \"yes\""), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [], [NounExpr("bar")], [])`) + assert.equal(expr("interface foo extends baz implements boz, bar:\n pass"), term`InterfaceExpr(null, FinalPattern(NounExpr("foo"), null), null, [NounExpr("baz")], [NounExpr("boz"), NounExpr("bar")], [])`) + assert.equal(expr("interface foo guards FooStamp extends boz, biz implements bar:\n pass"), term`InterfaceExpr(null, FinalPattern(NounExpr("foo"), null), FinalPattern(NounExpr("FooStamp"), null), [NounExpr("boz"), NounExpr("biz")], [NounExpr("bar")], [])`) + assert.equal(expr("interface foo:\n \"yes\"\n to run(a :int, b :float64) :any"), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [], [], [MessageDesc(null, "run", [ParamDesc("a", NounExpr("int")), ParamDesc("b", NounExpr("float64"))], NounExpr("any"))])`) + assert.equal(expr("interface foo:\n \"yes\"\n to run(a :int, b :float64) :any:\n \"msg docstring\""), term`InterfaceExpr("yes", FinalPattern(NounExpr("foo"), null), null, [], [], [MessageDesc("msg docstring", "run", [ParamDesc("a", NounExpr("int")), ParamDesc("b", NounExpr("float64"))], NounExpr("any"))])`) + assert.equal(expr("interface foo(a :int, b :float64) :any:\n \"msg docstring\""), term`FunctionInterfaceExpr("msg docstring", FinalPattern(NounExpr("foo"), null), null, [], [], MessageDesc("msg docstring", "run", [ParamDesc("a", NounExpr("int")), ParamDesc("b", NounExpr("float64"))], NounExpr("any")))`) + +def test_Call(assert): + assert.equal(expr("a.b(c, d)"), term`MethodCallExpr(NounExpr("a"), "b", [NounExpr("c"), NounExpr("d")])`) + assert.equal(expr("a.b()"), term`MethodCallExpr(NounExpr("a"), "b", [])`) + assert.equal(expr("a.b"), term`CurryExpr(NounExpr("a"), "b", false)`) + assert.equal(expr("a.b().c()"), term`MethodCallExpr(MethodCallExpr(NounExpr("a"), "b", []), "c", [])`) + assert.equal(expr("a.\"if\"()"), term`MethodCallExpr(NounExpr("a"), "if", [])`) + assert.equal(expr("a(b, c)"), term`FunCallExpr(NounExpr("a"), [NounExpr("b"), NounExpr("c")])`) + +def test_Send(assert): + assert.equal(expr("a <- b(c, d)"), term`SendExpr(NounExpr("a"), "b", [NounExpr("c"), NounExpr("d")])`) + assert.equal(expr("a <- b()"), term`SendExpr(NounExpr("a"), "b", [])`) + assert.equal(expr("a <- b"), term`CurryExpr(NounExpr("a"), "b", true)`) + assert.equal(expr("a <- b() <- c()"), term`SendExpr(SendExpr(NounExpr("a"), "b", []), "c", [])`) + assert.equal(expr("a <- \"if\"()"), term`SendExpr(NounExpr("a"), "if", [])`) + assert.equal(expr("a <- (b, c)"), term`FunSendExpr(NounExpr("a"), [NounExpr("b"), NounExpr("c")])`) + +def test_Get(assert): + assert.equal(expr("a[b, c]"), term`GetExpr(NounExpr("a"), [NounExpr("b"), NounExpr("c")])`) + assert.equal(expr("a[]"), term`GetExpr(NounExpr("a"), [])`) + assert.equal(expr("a.b()[c].d()"), term`MethodCallExpr(GetExpr(MethodCallExpr(NounExpr("a"), "b", []), [NounExpr("c")]), "d", [])`) + +def test_Meta(assert): + assert.equal(expr("meta.context()"), term`MetaContextExpr()`) + assert.equal(expr("meta.getState()"), term`MetaStateExpr()`) + +def test_Def(assert): + assert.equal(expr("def a := b"), term`DefExpr(FinalPattern(NounExpr("a"), null), null, NounExpr("b"))`) + assert.equal(expr("def a exit b := c"), term`DefExpr(FinalPattern(NounExpr("a"), null), NounExpr("b"), NounExpr("c"))`) + assert.equal(expr("var a := b"), term`DefExpr(VarPattern(NounExpr("a"), null), null, NounExpr("b"))`) + assert.equal(expr("bind a := b"), term`DefExpr(BindPattern(NounExpr("a")), null, NounExpr("b"))`) + +def test_Assign(assert): + assert.equal(expr("a := b"), term`AssignExpr(NounExpr("a"), NounExpr("b"))`) + assert.equal(expr("a[b] := c"), term`AssignExpr(GetExpr(NounExpr("a"), [NounExpr("b")]), NounExpr("c"))`) + assert.equal(expr("a foo= (b)"), term`VerbAssignExpr("foo", NounExpr("a"), [NounExpr("b")])`) + assert.equal(expr("a += b"), term`AugAssignExpr("+", NounExpr("a"), NounExpr("b"))`) + +def test_Prefix(assert): + assert.equal(expr("-3"), term`PrefixExpr("-", LiteralExpr(3))`) + assert.equal(expr("!foo.baz()"), term`PrefixExpr("!", MethodCallExpr(NounExpr("foo"), "baz", []))`) + assert.equal(expr("~foo.baz()"), term`PrefixExpr("~", MethodCallExpr(NounExpr("foo"), "baz", []))`) + assert.equal(expr("&&foo"), term`BindingExpr(NounExpr("foo"))`) + assert.equal(expr("&foo"), term`SlotExpr(NounExpr("foo"))`) + +def test_Coerce(assert): + assert.equal(expr("foo :baz"), term`CoerceExpr(NounExpr("foo"), NounExpr("baz"))`) + +def test_Infix(assert): + assert.equal(expr("x ** -y"), term`BinaryExpr(NounExpr("x"), "**", PrefixExpr("-", NounExpr("y")))`) + assert.equal(expr("x * y"), term`BinaryExpr(NounExpr("x"), "*", NounExpr("y"))`) + assert.equal(expr("x / y"), term`BinaryExpr(NounExpr("x"), "/", NounExpr("y"))`) + assert.equal(expr("x // y"), term`BinaryExpr(NounExpr("x"), "//", NounExpr("y"))`) + assert.equal(expr("x % y"), term`BinaryExpr(NounExpr("x"), "%", NounExpr("y"))`) + assert.equal(expr("x + y"), term`BinaryExpr(NounExpr("x"), "+", NounExpr("y"))`) + assert.equal(expr("(x + y) + z"), term`BinaryExpr(BinaryExpr(NounExpr("x"), "+", NounExpr("y")), "+", NounExpr("z"))`) + assert.equal(expr("x - y"), term`BinaryExpr(NounExpr("x"), "-", NounExpr("y"))`) + assert.equal(expr("x - y + z"), term`BinaryExpr(BinaryExpr(NounExpr("x"), "-", NounExpr("y")), "+", NounExpr("z"))`) + assert.equal(expr("x..y"), term`RangeExpr(NounExpr("x"), "..", NounExpr("y"))`) + assert.equal(expr("x..!y"), term`RangeExpr(NounExpr("x"), "..!", NounExpr("y"))`) + assert.equal(expr("x < y"), term`CompareExpr(NounExpr("x"), "<", NounExpr("y"))`) + assert.equal(expr("x <= y"), term`CompareExpr(NounExpr("x"), "<=", NounExpr("y"))`) + assert.equal(expr("x <=> y"), term`CompareExpr(NounExpr("x"), "<=>", NounExpr("y"))`) + assert.equal(expr("x >= y"), term`CompareExpr(NounExpr("x"), ">=", NounExpr("y"))`) + assert.equal(expr("x > y"), term`CompareExpr(NounExpr("x"), ">", NounExpr("y"))`) + assert.equal(expr("x << y"), term`BinaryExpr(NounExpr("x"), "<<", NounExpr("y"))`) + assert.equal(expr("x >> y"), term`BinaryExpr(NounExpr("x"), ">>", NounExpr("y"))`) + assert.equal(expr("x << y >> z"), term`BinaryExpr(BinaryExpr(NounExpr("x"), "<<", NounExpr("y")), ">>", NounExpr("z"))`) + assert.equal(expr("x == y"), term`SameExpr(NounExpr("x"), NounExpr("y"), true)`) + assert.equal(expr("x != y"), term`SameExpr(NounExpr("x"), NounExpr("y"), false)`) + assert.equal(expr("x &! y"), term`BinaryExpr(NounExpr("x"), "&!", NounExpr("y"))`) + assert.equal(expr("x ^ y"), term`BinaryExpr(NounExpr("x"), "^", NounExpr("y"))`) + assert.equal(expr("x & y"), term`BinaryExpr(NounExpr("x"), "&", NounExpr("y"))`) + assert.equal(expr("x & y & z"), term`BinaryExpr(BinaryExpr(NounExpr("x"), "&", NounExpr("y")), "&", NounExpr("z"))`) + assert.equal(expr("x | y"), term`BinaryExpr(NounExpr("x"), "|", NounExpr("y"))`) + assert.equal(expr("x | y | z"), term`BinaryExpr(BinaryExpr(NounExpr("x"), "|", NounExpr("y")), "|", NounExpr("z"))`) + assert.equal(expr("x && y"), term`AndExpr(NounExpr("x"), NounExpr("y"))`) + assert.equal(expr("x && y && z"), term`AndExpr(NounExpr("x"), AndExpr(NounExpr("y"), NounExpr("z")))`) + assert.equal(expr("x || y"), term`OrExpr(NounExpr("x"), NounExpr("y"))`) + assert.equal(expr("x || y || z"), term`OrExpr(NounExpr("x"), OrExpr(NounExpr("y"), NounExpr("z")))`) + assert.equal(expr("x =~ y"), term`MatchBindExpr(NounExpr("x"), FinalPattern(NounExpr("y"), null))`) + assert.equal(expr("x && y || z"), expr("(x && y) || z")) + assert.equal(expr("x || y && z"), expr("x || (y && z)")) + assert.equal(expr("x =~ a || y == b && z != c"), + expr("(x =~ a) || ((y == b) && (z != c))")) + assert.equal(expr("x | y > z"), expr("x | (y > z)")) + assert.equal(expr("x < y | y > z"), expr("(x < y) | (y > z)")) + assert.equal(expr("x & y > z"), expr("x & (y > z)")) + assert.equal(expr("x < y & y > z"), expr("(x < y) & (y > z)")) + assert.equal(expr("x..y <=> a..!b"), expr("(x..y) <=> (a..!b)")) + assert.equal(expr("a << b..y >> z"), expr("(a << b) .. (y >> z)")) + assert.equal(expr("x.y() :List[Int] > a..!b"), + expr("(x.y() :List[Int]) > a..!b")) + assert.equal(expr("a + b >> z"), expr("(a + b) >> z")) + assert.equal(expr("a >> b + z"), expr("a >> (b + z)")) + assert.equal(expr("a + b * c"), expr("a + (b * c)")) + assert.equal(expr("a - b + c * d"), expr("(a - b) + (c * d)")) + assert.equal(expr("a / b + c - d"), expr("((a / b) + c) - d")) + assert.equal(expr("a / b * !c ** ~d"), expr("(a / b) * ((!c) ** (~d))")) + +def test_Exits(assert): + assert.equal(expr("return x + y"), term`ExitExpr("return", BinaryExpr(NounExpr("x"), "+", NounExpr("y")))`) + assert.equal(expr("continue x + y"), term`ExitExpr("continue", BinaryExpr(NounExpr("x"), "+", NounExpr("y")))`) + assert.equal(expr("break x + y"), term`ExitExpr("break", BinaryExpr(NounExpr("x"), "+", NounExpr("y")))`) + assert.equal(expr("return(x + y)"), term`ExitExpr("return", BinaryExpr(NounExpr("x"), "+", NounExpr("y")))`) + assert.equal(expr("continue(x + y)"), term`ExitExpr("continue", BinaryExpr(NounExpr("x"), "+", NounExpr("y")))`) + assert.equal(expr("break(x + y)"), term`ExitExpr("break", BinaryExpr(NounExpr("x"), "+", NounExpr("y")))`) + assert.equal(expr("return()"), term`ExitExpr("return", null)`) + assert.equal(expr("continue()"), term`ExitExpr("continue", null)`) + assert.equal(expr("break()"), term`ExitExpr("break", null)`) + assert.equal(expr("return"), term`ExitExpr("return", null)`) + assert.equal(expr("continue"), term`ExitExpr("continue", null)`) + assert.equal(expr("break"), term`ExitExpr("break", null)`) + +def test_IgnorePattern(assert): + assert.equal(pattern("_"), term`IgnorePattern(null)`) + assert.equal(pattern("_ :Int"), term`IgnorePattern(NounExpr("Int"))`) + assert.equal(pattern("_ :(1)"), term`IgnorePattern(LiteralExpr(1))`) + +def test_FinalPattern(assert): + assert.equal(pattern("foo"), term`FinalPattern(NounExpr("foo"), null)`) + assert.equal(pattern("foo :Int"), term`FinalPattern(NounExpr("foo"), NounExpr("Int"))`) + assert.equal(pattern("foo :(1)"), term`FinalPattern(NounExpr("foo"), LiteralExpr(1))`) + assert.equal(pattern("::\"foo baz\""), term`FinalPattern(NounExpr("foo baz"), null)`) + assert.equal(pattern("::\"foo baz\" :Int"), term`FinalPattern(NounExpr("foo baz"), NounExpr("Int"))`) + assert.equal(pattern("::\"foo baz\" :(1)"), term`FinalPattern(NounExpr("foo baz"), LiteralExpr(1))`) + +def test_SlotPattern(assert): + assert.equal(pattern("&foo"), term`SlotPattern(NounExpr("foo"), null)`) + assert.equal(pattern("&foo :Int"), term`SlotPattern(NounExpr("foo"), NounExpr("Int"))`) + assert.equal(pattern("&foo :(1)"), term`SlotPattern(NounExpr("foo"), LiteralExpr(1))`) + assert.equal(pattern("&::\"foo baz\""), term`SlotPattern(NounExpr("foo baz"), null)`) + assert.equal(pattern("&::\"foo baz\" :Int"), term`SlotPattern(NounExpr("foo baz"), NounExpr("Int"))`) + assert.equal(pattern("&::\"foo baz\" :(1)"), term`SlotPattern(NounExpr("foo baz"), LiteralExpr(1))`) + +def test_VarPattern(assert): + assert.equal(pattern("var foo"), term`VarPattern(NounExpr("foo"), null)`) + assert.equal(pattern("var foo :Int"), term`VarPattern(NounExpr("foo"), NounExpr("Int"))`) + assert.equal(pattern("var foo :(1)"), term`VarPattern(NounExpr("foo"), LiteralExpr(1))`) + assert.equal(pattern("var ::\"foo baz\""), term`VarPattern(NounExpr("foo baz"), null)`) + assert.equal(pattern("var ::\"foo baz\" :Int"), term`VarPattern(NounExpr("foo baz"), NounExpr("Int"))`) + assert.equal(pattern("var ::\"foo baz\" :(1)"), term`VarPattern(NounExpr("foo baz"), LiteralExpr(1))`) + +def test_BindPattern(assert): + assert.equal(pattern("bind foo"), term`BindPattern(NounExpr("foo"))`) + assert.equal(pattern("bind ::\"foo baz\""), term`BindPattern(NounExpr("foo baz"))`) + +def test_BindingPattern(assert): + assert.equal(pattern("&&foo"), term`BindingPattern(NounExpr("foo"))`) + assert.equal(pattern("&&::\"foo baz\""), term`BindingPattern(NounExpr("foo baz"))`) + +def test_SamePattern(assert): + assert.equal(pattern("==1"), term`SamePattern(LiteralExpr(1), true)`) + assert.equal(pattern("==(x)"), term`SamePattern(NounExpr("x"), true)`) + +def test_NotSamePattern(assert): + assert.equal(pattern("!=1"), term`SamePattern(LiteralExpr(1), false)`) + assert.equal(pattern("!=(x)"), term`SamePattern(NounExpr("x"), false)`) + +def test_ViaPattern(assert): + assert.equal(pattern("via (b) a"), term`ViaPattern(NounExpr("b"), FinalPattern(NounExpr("a"), null))`) + +def test_ListPattern(assert): + assert.equal(pattern("[]"), term`ListPattern([], null)`) + assert.equal(pattern("[a, b]"), term`ListPattern([FinalPattern(NounExpr("a"), null), FinalPattern(NounExpr("b"), null)], null)`) + assert.equal(pattern("[a, b] + c"), term`ListPattern([FinalPattern(NounExpr("a"), null), FinalPattern(NounExpr("b"), null)], FinalPattern(NounExpr("c"), null))`) + +def test_MapPattern(assert): + assert.equal(pattern("[\"k\" => v, (a) => b, => c]"), term`MapPattern([MapPatternRequired(MapPatternAssoc(LiteralExpr("k"), FinalPattern(NounExpr("v"), null))), MapPatternRequired(MapPatternAssoc(NounExpr("a"), FinalPattern(NounExpr("b"), null))), MapPatternRequired(MapPatternImport(FinalPattern(NounExpr("c"), null)))], null)`) + assert.equal(pattern("[\"a\" => b := 1] | c"), term`MapPattern([MapPatternDefault(MapPatternAssoc(LiteralExpr("a"), FinalPattern(NounExpr("b"), null)), LiteralExpr(1))], FinalPattern(NounExpr("c"), null))`) + assert.equal(pattern("[\"k\" => &v, => &&b, => ::\"if\"]"), term`MapPattern([MapPatternRequired(MapPatternAssoc(LiteralExpr("k"), SlotPattern(NounExpr("v"), null))), MapPatternRequired(MapPatternImport(BindingPattern(NounExpr("b")))), MapPatternRequired(MapPatternImport(FinalPattern(NounExpr("if"), null)))], null)`) + +def test_QuasiliteralPattern(assert): + assert.equal(pattern("`foo`"), term`QuasiParserPattern(null, [QuasiText("foo")])`) + assert.equal(pattern("bob`foo`"), term`QuasiParserPattern("bob", [QuasiText("foo")])`) + assert.equal(pattern("bob`foo`` $x baz`"), term`QuasiParserPattern("bob", [QuasiText("foo`` "), QuasiExprHole(NounExpr("x")), QuasiText(" baz")])`) + assert.equal(pattern("`($x)`"), term`QuasiParserPattern(null, [QuasiText("("), QuasiExprHole(NounExpr("x")), QuasiText(")")])`) + assert.equal(pattern("`foo @{w}@x $y${z} baz`"), term`QuasiParserPattern(null, [QuasiText("foo "), QuasiPatternHole(FinalPattern(NounExpr("w"), null)), QuasiPatternHole(FinalPattern(NounExpr("x"), null)), QuasiText(" "), QuasiExprHole(NounExpr("y")), QuasiExprHole(NounExpr("z")), QuasiText(" baz")])`) + +def test_SuchThatPattern(assert): + assert.equal(pattern("x :y ? (1)"), term`SuchThatPattern(FinalPattern(NounExpr("x"), NounExpr("y")), LiteralExpr(1))`) + + +# def test_holes(assert): +# assert.equal(quasiMonteParser.valueMaker(["foo(", quasiMonteParser.valueHole(0), ")"]), term`ValueHoleExpr(0)`) +# assert.equal(expr("@{2}"), term`PatternHoleExpr(2)`) +# assert.equal(pattern("${2}"), term`ValueHoleExpr(0)`) +# assert.equal(pattern("@{2}"), term`PatternHoleExpr(0)`) +#unittest([test_Literal, test_Noun, test_QuasiliteralExpr, test_Hide, test_Call, test_Send, test_Get, test_Meta, test_List, test_Map, test_ListComprehensionExpr, test_MapComprehensionExpr, test_IfExpr, test_EscapeExpr, test_ForExpr, test_FunctionExpr, test_SwitchExpr, test_TryExpr, test_WhileExpr, test_WhenExpr, test_ObjectExpr, test_Function, test_Interface, test_Def, test_Assign, test_Prefix, test_Coerce, test_Infix, test_Exits, test_IgnorePattern, test_FinalPattern, test_VarPattern, test_BindPattern, test_SamePattern, test_NotSamePattern, test_SlotPattern, test_BindingPattern, test_ViaPattern, test_ListPattern, test_MapPattern, test_QuasiliteralPattern, test_SuchThatPattern]) diff --git a/monte/src/package.mt b/monte/src/package.mt index 40e08de..4bbe15a 100644 --- a/monte/src/package.mt +++ b/monte/src/package.mt @@ -9,8 +9,11 @@ def example := pkg.readFile("examples/testing.mt")([=> unittest]) def regionTests := pkg.readFile("test_regions.mt")([=> unittest]) def [=> makeOMeta] := pkg.readFile("ometa.mt")() def ometaTests := pkg.readFile("test_ometa.mt")([=> makeOMeta, => unittest]) -def terml := pkg.readPackage("./terml")() def testUnicode := pkg.readFile("test_unicode.mt")([=> unittest]) def testSwitch := pkg.readFile("test_switch.mt")([=> unittest]) - -pkg.makeModule(terml | blackjack | example | ometaTests | testUnicode | regionTests) +def testOperators := pkg.readFile("test_operators.mt")([=> unittest]) +def monte_lexer := pkg.readFile("monte_lexer.mt")([=> unittest]) +def monte_ast := pkg.readFile("monte_ast.mt")([=> unittest]) +def monte_parser := pkg.readFile("monte_parser.mt")([=> unittest] | monte_lexer | monte_ast) +def monte_expander := pkg.readFile("monte_expander.mt")([=> unittest] | monte_parser | monte_ast |monte_lexer) +pkg.makeModule(monte_expander | monte_parser | monte_lexer | blackjack | example | ometaTests | testUnicode | regionTests | testOperators) diff --git a/monte/src/prim/package.mt b/monte/src/prim/package.mt index dd1ea75..eb1d00d 100644 --- a/monte/src/prim/package.mt +++ b/monte/src/prim/package.mt @@ -4,12 +4,28 @@ def files := pkg.readFiles(".") def [=> OrderedSpaceMaker, => OrderedRegionMaker] := files["regions"]([=> unittest]) -def [=> charSpace, +def [=> __makeOrderedSpace, + => charSpace, => intSpace, - => floatSpace] := files["primSpaces"]([=> OrderedSpaceMaker]) + => floatSpace] := files["primSpaces"]([=> OrderedSpaceMaker]) + +#all this should go in terml/package.mt probably +def terml_files := pkg.readFiles("./terml") +def [=> Tag, => makeTag, => optMakeTagFromData] := terml_files["tag"]([=> unittest]) +def [=> Term, => makeTerm, => termBuilder] := terml_files["term"]([=> Tag, => makeTag, => optMakeTagFromData]) +def [=> convertToTerm] := terml_files["convertToTerm"]([=> makeTerm, => Term, + => makeTag, => optMakeTagFromData, => unittest]) +def [=> termFactory] := terml_files["termFactory"]([=> makeTerm, => makeTag, => optMakeTagFromData, + => convertToTerm]) +def [=> makeTermLexer] := terml_files["termLexer"]([=> __makeOrderedSpace, => makeTag, => makeTerm, => termBuilder, => unittest]) +def [=> makeQFunctor, => makeQTerm, => makeQSome, => makeQDollarHole, => makeQAtHole, => qEmptySeq, => makeQPairSeq] := terml_files["quasiterm"]([=> __makeOrderedSpace, => convertToTerm, => makeTerm, => makeTag, => termBuilder, => Term, => optMakeTagFromData]) +def [=> parseTerm, => quasitermParser] := terml_files["termParser"]([ + => __makeOrderedSpace, => makeTag, => makeTerm, => makeTermLexer, => convertToTerm, => makeQFunctor, => makeQTerm, => makeQSome, => makeQDollarHole, => makeQAtHole, => qEmptySeq, => makeQPairSeq, => termBuilder, => optMakeTagFromData, => unittest]) pkg.makeModule([ - "__makeOrderedSpace" => OrderedSpaceMaker, - "char" => charSpace, - "int" => intSpace, - "float" => floatSpace]) + => __makeOrderedSpace, + "Char" => charSpace, + "Int" => intSpace, + "Double" => floatSpace, + "term__quasiParser" => quasitermParser, + ]) diff --git a/monte/src/prim/primSpaces.mt b/monte/src/prim/primSpaces.mt index 4e336e8..e796c05 100644 --- a/monte/src/prim/primSpaces.mt +++ b/monte/src/prim/primSpaces.mt @@ -1,6 +1,38 @@ module OrderedSpaceMaker -export (charSpace, intSpace, floatSpace) +export (charSpace, intSpace, floatSpace, __makeOrderedSpace) -def charSpace := OrderedSpaceMaker(char, "char") -def intSpace := OrderedSpaceMaker(int, "int") -def floatSpace := OrderedSpaceMaker(float, "float") +def charSpace := OrderedSpaceMaker(Char, "Char") +def intSpace := OrderedSpaceMaker(Int, "Int") +def floatSpace := OrderedSpaceMaker(Double, "Double") + +object __makeOrderedSpace extends OrderedSpaceMaker: + /** + * Given a value of a type whose reflexive (x <=> x) instances are + * fully ordered, this returns the corresponding OrderedSpace + */ + to spaceOfValue(value): + if (value =~ i :Int): + return intSpace + else if (value =~ f :Double): + return floatSpace + else if (value =~ c :Char): + return charSpace + else: + def type := value._getAllegedType() + return OrderedSpaceMaker(type, M.toQuote(type)) + + /** + * start..!bound is equivalent to + * (space >= start) & (space < bound) + */ + to op__till(start, bound): + def space := __makeOrderedSpace.spaceOfValue(start) + return (space >= start) & (space < bound) + + /** + * start..stop is equivalent to + * (space >= start) & (space <= stop) + */ + to op__thru(start, stop): + def space := __makeOrderedSpace.spaceOfValue(start) + return (space >= start) & (space <= stop) diff --git a/monte/src/prim/regions.mt b/monte/src/prim/regions.mt index 3e236c0..885a6a7 100644 --- a/monte/src/prim/regions.mt +++ b/monte/src/prim/regions.mt @@ -1,6 +1,6 @@ module unittest export (OrderedRegionMaker, OrderedSpaceMaker) -def primInt :DeepFrozen := int +def primInt :DeepFrozen := Int /** * A min where null represents positive infinity @@ -55,12 +55,12 @@ def get(list, index) as DeepFrozen: * @author Mark S. Miller */ object OrderedRegionMaker as DeepFrozen: - to run(myType :DeepFrozen, myName :str, var initBoundedLeft :boolean, var initEdges): + to run(myType :DeepFrozen, myName :Str, var initBoundedLeft :Bool, var initEdges): /** * Notational convenience */ - def region(boundedLeft :boolean, edges) as DeepFrozen: + def region(boundedLeft :Bool, edges) as DeepFrozen: return OrderedRegionMaker(myType, myName, boundedLeft, edges) @@ -75,7 +75,7 @@ object OrderedRegionMaker as DeepFrozen: def myLen :primInt := initEdges.size() def myTypeR :Same[myType] := myType # for SubrangeGuard audit - def myBoundedLeft :boolean := initBoundedLeft + def myBoundedLeft :Bool := initBoundedLeft def myEdges :DeepFrozen := initEdges /** @@ -140,7 +140,7 @@ object OrderedRegionMaker as DeepFrozen: * If it's in the type but not in the region, the answer is false. * If it's not in the type, a problem is thrown. */ - to run(pos :myType) :boolean: + to run(pos :myType) :Bool: # XXX linear time algorithm. For long myEdges lists, # it should do a binary search. for i => edge in myEdges: @@ -177,7 +177,7 @@ object OrderedRegionMaker as DeepFrozen: * Note that the empty region is bounded left, but it doesn't * have a start */ - to isBoundedLeft() :boolean: + to isBoundedLeft() :Bool: return myBoundedLeft /** @@ -187,7 +187,7 @@ object OrderedRegionMaker as DeepFrozen: * Returns the start or null. The start is the least element * which is *in* the region. */ - to getOptStart() :nullOk[myType]: + to getOptStart() :NullOk[myType]: if (myBoundedLeft && myLen >= 1): return myEdges[0] else: @@ -197,7 +197,7 @@ object OrderedRegionMaker as DeepFrozen: * Note that the empty region is bounded right, but it doesn't * have a bound */ - to isBoundedRight() :boolean: + to isBoundedRight() :Bool: return myLen % 2 == myInParity /** @@ -208,7 +208,7 @@ object OrderedRegionMaker as DeepFrozen: * element greater than all elements in the region. Unlike the * left bound, it is *not* in the region. */ - to getOptBound() :nullOk[myType]: + to getOptBound() :NullOk[myType]: if (myLen >= 1 && myLen % 2 == myInParity): return myEdges[myLen -1] else: @@ -221,7 +221,7 @@ object OrderedRegionMaker as DeepFrozen: * of a distinction and its complement, and is therefore an * interval, but not a distinction. */ - to isEmpty() :boolean: + to isEmpty() :Bool: return myLen == 0 && myBoundedLeft /** @@ -230,7 +230,7 @@ object OrderedRegionMaker as DeepFrozen: * The full region is the intersection (and) of no regions, and * is therefore a interval but not a distinction. */ - to isFull() :boolean: + to isFull() :Bool: return myLen == 0 && !myBoundedLeft /** @@ -240,7 +240,7 @@ object OrderedRegionMaker as DeepFrozen: * The not of a distinction must be a distinction. For this space, * the distinctions are (myType < y) and (myType >= y). */ - to isDistinction() :boolean: + to isDistinction() :Bool: return myLen == 1 /** @@ -256,7 +256,7 @@ object OrderedRegionMaker as DeepFrozen: * need not be an interval. A non-interval is a complex * region. */ - to isSimpleRegion() :boolean: + to isSimpleRegion() :Bool: if (myLen <= 1): # distinctions, empty, and full are all intervals return true @@ -378,8 +378,9 @@ object OrderedRegionMaker as DeepFrozen: if (minout != null): flex.push(minout) # XXX compiler bug workaround - def bluh := (out1 != null && out1 < out2) - if (out2 == null || bluh): + if (out2 == null): + i += 2 + else if (out1 != null && out1 < out2): i += 2 else: j += 2 @@ -491,7 +492,7 @@ object OrderedRegionMaker as DeepFrozen: /** * As a region, my comparison is a subset test. */ - to op__cmp(other) :float: + to op__cmp(other) :Double: def selfExtra := !(self & !other).isEmpty() def otherExtra := !(other & !self).isEmpty() if (selfExtra): @@ -514,48 +515,17 @@ object OrderedRegionMaker as DeepFrozen: object OrderedSpaceMaker as DeepFrozen: - /** - * Given a value of a type whose reflexive (x <=> x) instances are - * fully ordered, this returns the corresponding OrderedSpace - */ - to spaceOfValue(value): - if (value =~ i :int): - return int - else if (value =~ f :float): - return float - else if (value =~ c :char): - return char - else: - def type := value._getAllegedType() - return OrderedSpaceMaker(type, M.toQuote(type)) - - /** - * start..!bound is equivalent to - * (space >= start) & (space < bound) - */ - to op__till(start, bound): - def space := OrderedSpaceMaker.spaceOfValue(start) - return (space >= start) & (space < bound) - - /** - * start..stop is equivalent to - * (space >= start) & (space <= stop) - */ - to op__thru(start, stop): - def space := OrderedSpaceMaker.spaceOfValue(start) - return (space >= start) & (space <= stop) - /** * Given a type whose reflexive (x <=> x) instances are fully * ordered, this makes an OrderedSpace for making Regions and * Twisters for those instances using operator notation. */ - to run(myType :DeepFrozen, myName :str): + to run(myType :DeepFrozen, myName :Str): /** * Notational convenience */ - def region(boundedLeft :boolean, edges) as DeepFrozen: + def region(boundedLeft :Bool, edges) as DeepFrozen: return OrderedRegionMaker(myType, myName, boundedLeft, edges) @@ -661,25 +631,25 @@ object OrderedSpaceMaker as DeepFrozen: def testIterable(assert): - def intspace := OrderedSpaceMaker(int, "int") + def intspace := OrderedSpaceMaker(Int, "int") def reg := (intspace >= 0) & (intspace < 5) assert.equal([x for x in reg], [0, 1, 2, 3, 4]) def testContainment(assert): - def intspace := OrderedSpaceMaker(int, "int") + def intspace := OrderedSpaceMaker(Int, "int") def reg := (intspace >= 0) & (intspace < 5) assert.equal(reg(3), true) assert.equal(reg(5), false) assert.raises(fn fail {reg(1.0)}) def testGuard(assert): - def intspace := OrderedSpaceMaker(int, "int") + def intspace := OrderedSpaceMaker(Int, "int") def reg := (intspace >= 0) & (intspace < 5) assert.equal(def x :reg := 3, 3) assert.ejects(fn ej, fail {def x :reg exit ej := 7}) def testDeepFrozen(assert): - def intspace := OrderedSpaceMaker(int, "int") + def intspace := OrderedSpaceMaker(Int, "int") def reg := (intspace >= 0) & (intspace < 5) def x :reg := 2 #traceln("welp") diff --git a/monte/src/prim/terml/convertToTerm.mt b/monte/src/prim/terml/convertToTerm.mt new file mode 100644 index 0000000..282c2be --- /dev/null +++ b/monte/src/prim/terml/convertToTerm.mt @@ -0,0 +1,81 @@ +module makeTerm :DeepFrozen, Term :DeepFrozen, makeTag :DeepFrozen, unittest +export (convertToTerm) + +# copypasted here since I am too lazy to DF-annotate everything that needs +# it. remove ASAP +def optMakeTagFromData(val, mkt) as DeepFrozen: + switch (val): + match ==null: + return mkt("null", null) + match ==true: + return mkt("true", null) + match ==false: + return mkt("false", null) + match v :Int: + return mkt(".int.", v) + match v :Double: + return mkt(".float64.", v) + match v :Str: + return mkt(".String.", v) + match v :Char: + return mkt(".char.", v) + match _: + return null + +def mkt(name, data) as DeepFrozen: + return makeTerm(makeTag(null, name, Any), data, [], null) + +def convertToTerm(val, ej) as DeepFrozen: + if (val =~ _ :Term): + return val + if ((def t := optMakeTagFromData(val, mkt)) != null): + return t + switch (val): + match v :List: + def ts := [].diverge() + for item in v: + ts.push(convertToTerm(item, ej)) + def l := ts.snapshot() + return makeTerm(makeTag(null, ".tuple.", Any), null, l, null) + # match v :set: + # return mkt(".bag.", null, [for item in (v) convertToTerm(item)]) + match m :Map: + def mm := [].diverge() + for k => v in m: + mm.push(makeTerm(makeTag(null, ".attr.", Any), null, [convertToTerm(k, ej), + convertToTerm(v, ej)], null)) + return makeTerm(makeTag(null, ".bag.", Any), null, + mm.snapshot(), null) + match _: + throw.eject(ej, `Could not coerce $val to term`) + +def test_convert(assert): + def t1 := convertToTerm([1, null, 2.5, "yes", 'c', true, [1 => 2]], null) + assert.equal(t1.getTag().getName(), ".tuple.") + def a := t1.getArgs() + def one := a[0] + assert.equal(one.getTag().getName(), ".int.") + assert.equal(one.getData(), 1) + def nul := a[1] + assert.equal(nul.getTag().getName(), "null") + def flo := a[2] + assert.equal(flo.getTag().getName(), ".float64.") + assert.equal(flo.getData(), 2.5) + def s := a[3] + assert.equal(s.getTag().getName(), ".String.") + assert.equal(s.getData(), "yes") + def c := a[4] + assert.equal(c.getTag().getName(), ".char.") + assert.equal(c.getData(), 'c') + def b := a[5] + assert.equal(b.getTag().getName(), "true") + def m := a[6] + assert.equal(m.getTag().getName(), ".bag.") + def ma := m.getArgs() + assert.equal(ma[0].getTag().getName(), ".attr.") + def k := ma[0].getArgs()[0] + assert.equal(k.getData(), 1) + def v := ma[0].getArgs()[1] + assert.equal(v.getData(), 2) + +unittest([test_convert]) diff --git a/monte/src/prim/terml/quasiterm.mt b/monte/src/prim/terml/quasiterm.mt new file mode 100644 index 0000000..c3f0f1d --- /dev/null +++ b/monte/src/prim/terml/quasiterm.mt @@ -0,0 +1,420 @@ +module __makeOrderedSpace, convertToTerm, makeTerm, makeTag, termBuilder, Term +export (makeQFunctor, makeQTerm, makeQSome, makeQDollarHole, makeQAtHole, qEmptySeq, makeQPairSeq) + +object qEmptySeq: + to reserve(): + return 0 + + to startShape(values, bindings, prefix, shapeSoFar): + return shapeSoFar + + to endShape(bindings, prefix, shape): + pass + + to substSlice(values, indices): + return [] + + to matchBindSlice(args, specimens, bindings, indices, max): + return 0 + + +def makeQPairSeq(left, right): + return object qpair: + to getLeft(): + return left + + to getRight(): + return right + + to getSpan(): + return null + + to startShape(values, bindings, prefix, var shapeSoFar): + shapeSoFar := left.startShape(values, bindings, prefix, shapeSoFar) + return right.startShape(values, bindings, prefix, shapeSoFar) + + to endShape(bindings, prefix, shape): + left.endShape(bindings, prefix, shape) + right.endShape(bindings, prefix, shape) + + to substSlice(values, indices): + def v := left.substSlice(values, indices) + right.substSlice(values, indices) + return v + + to matchBindSlice(args, specimens, bindings, indices, max): + def leftNum := left.matchBindSlice(args, specimens, bindings, indices, + max - right.reserve()) + if (leftNum < 0): + return -1 + def rightNum := right.matchBindSlice(args, specimens.slice(leftNum), + bindings, indices, max - leftNum) + if (rightNum < 0): + return -1 + return leftNum + rightNum + + to reserve(): + return left.reserve() + right.reserve() + + +def matchCoerce(val, isFunctorHole, tag): + var result := null + if (isFunctorHole): + def mkt(name, data, args): + return makeTerm(makeTag(null, name, Any), data, args, null) + switch (val): + match _ :Term: + if (val.getArgs().size() != 0): + return null + result := val + match ==null: + result := mkt("null", null, []) + match ==true: + result := mkt("true", null, []) + match ==false: + result := mkt("false", null, []) + match v :Str: + result := mkt(v, null, []) + match _: + return null + else: + escape e: + result := convertToTerm(val, e) + catch _: + return null + if (tag == null || tag <=> result.getTag()): + return result + return null + + +def makeQTerm(functor, args): + def coerce(termoid): + if (termoid !~ _ :Term): + return matchCoerce(termoid, functor.getIsFunctorHole(), functor.getTag()) + def newFunctor := matchCoerce(termoid.withoutArgs(), functor.getIsFunctorHole(), functor.getTag()) + if (newFunctor == null): + return null + return makeTerm(newFunctor.getTag(), newFunctor.getData(), termoid.getArgs(), termoid.getSpan()) + + return object qterm: + to isHole(): + return false + + to getFunctor(): + return functor + + to getArgs(): + return args + + to startShape(values, bindings, prefix, var shapeSoFar): + shapeSoFar := functor.startShape(values, bindings, prefix, shapeSoFar) + shapeSoFar := args.startShape(values, bindings, prefix, shapeSoFar) + return shapeSoFar + + to endShape(bindings, prefix, shape): + functor.endShape(bindings, prefix, shape) + functor.endShape(bindings, prefix, shape) + + to substSlice(values, indices): + def tFunctor := functor.substSlice(values, indices)[0] + def tArgs := args.substSlice(values, indices) + def term := makeTerm(tFunctor.getTag(), tFunctor.getData(), + tArgs, tFunctor.getSpan()) + return [term] + + to matchBindSlice(values, specimens, bindings, indices, max): + if (specimens.size() <= 0): + return -1 + def specimen := coerce(specimens[0]) + if (specimen == null): + return -1 + def matches := functor.matchBindSlice(values, [specimen.withoutArgs()], + bindings, indices, 1) + if (matches <= 0): + return -1 + if (matches != 1): + throw("Functor may only match 0 or 1 specimen: ", matches) + def tArgs := specimen.getArgs() + def num := args.matchBindSlice(values, tArgs, + bindings, indices, tArgs.size()) + if (tArgs.size() == num): + if (max >= 1): + return 1 + return -1 + + to reserve(): + return 1 + +def makeQFunctor(tag, data, span): + return object qfunctor: + to _printOn(out): + out.print(tag.getName()) + + to isHole(): + return false + + to getIsFunctorHole(): + return false + + to getTag(): + return tag + + to getData(): + return data + + to getSpan(): + return span + + to asFunctor(): + return qfunctor + + to reserve(): + return 1 + + to startShape(args, bindings, prefix, shapeSoFar): + return shapeSoFar + + to endShape(bindings, prefix, shape): + pass + + to substSlice(values, indices): + if (data == null): + return [termBuilder.leafInternal(tag, null, span)] + else: + return [termBuilder.leafData(data, span)] + + to matchBindSlice(args, specimens, bindings, indices, max): + if (specimens.size() <= 0): + return -1 + def spec := matchCoerce(specimens[0], true, tag) + if (spec == null): + return -1 + if (data != null): + def otherData := spec.getData() + if (otherData == null): + return -1 + if (data != otherData): + if ([data, otherData] =~ [_ :Str, _ :Str]): + if (data.bare() != otherData.bare()): + return -1 + if (max >= 1): + return 1 + return -1 + + +def multiget(args, num, indices, repeat): + var result := args[num] + for i in indices: + if (result =~ rlist :List): + result := rlist[i] + else: + if (repeat): + return result + throw("index out of bounds") + return result + + +def multiput(bindings, holeNum, indices, newVal): + var list := bindings + var dest := holeNum + for i in indices: + if (list.size() < dest + 1): + throw("Index out of bounds") + var next := list[dest] + if (next == null): + next := [].diverge() + list[dest] := next + list := next + dest := i + var result := null + if (list.size() > dest): + result := list[dest] + list[dest] := newVal + else if (list.size() == dest): + list.push(newVal) + else: + throw("what's going on in here") + return result + + +def makeQDollarHole(tag, holeNum, isFunctorHole): + return object qdollarhole: + + to isHole(): + return true + + to getTag(): + return tag + + to getHoleNum(): + return holeNum + + to getSpan(): + return null + + to getIsFunctorHole(): + return isFunctorHole + + to asFunctor(): + if (isFunctorHole): + return qdollarhole + else: + return makeQDollarHole(tag, holeNum, true) + + to startShape(values, bindings, prefix, shapeSoFar): + def t := multiget(values, holeNum, prefix, true) + if (t =~ vals :List): + def result := vals.size() + if (![-1, result].contains(shapeSoFar)): + throw(`Inconsistent shape: $shapeSoFar vs $result`) + return result + return shapeSoFar + + to endShape(bindings, prefix, shape): + pass + + to substSlice(values, indices): + def termoid := multiget(values, holeNum, indices, true) + def term := matchCoerce(termoid, isFunctorHole, tag) + if (term == null): + throw(`Term $termoid doesn't match $qdollarhole`) + return [term] + + to matchBindSlice(args, specimens, bindings, indices, max): + if (specimens.size() <= 0): + return -1 + def specimen := specimens[0] + def termoid := multiget(args, holeNum, indices, true) + def term := matchCoerce(termoid, isFunctorHole, tag) + if (term == null): + throw(`Term $termoid doesn't match $qdollarhole`) + if (term <=> specimen): + if (max >= 1): + return 1 + return -1 + + to reserve(): + return 1 + + +def makeQAtHole(tag, holeNum, isFunctorHole): + return object qathole: + to isHole(): + return true + + to getTag(): + return tag + + to getSpan(): + return null + + to getHoleNum(): + return holeNum + + to getIsFunctorHole(): + return isFunctorHole + + to asFunctor(): + if (isFunctorHole): + return qathole + else: + return makeQAtHole(tag, holeNum, true) + + to startShape(values, bindings, prefix, shapeSoFar): + # if (bindings == null): + # throw("no at-holes in a value maker") + multiput(bindings, holeNum, prefix, [].diverge()) + return shapeSoFar + + to endShape(bindings, prefix, shape): + def bits := multiget(bindings, holeNum, prefix, false) + multiput(bindings, holeNum, prefix, bits.slice(0, shape)) + + to substSlice(values, indices): + throw("A quasiterm with an @-hole may not be used in a value context") + + to matchBindSlice(args, specimens, bindings, indices, max): + if (specimens.size() <= 0): + return -1 + def spec := matchCoerce(specimens[0], isFunctorHole, tag) + if (spec == null): + return -1 + def oldVal := multiput(bindings, holeNum, indices, spec) + if (oldVal == null || oldVal <=> spec): + if (max >= 1): + return 1 + + return -1 + + to reserve(): + return 1 + +def inBounds(num, quant): + switch (quant): + match =="?": + return num == 0 || num == 1 + match =="+": + return num >= 1 + match =="*": + return num >= 0 + return false + +def makeQSome(subPattern, quant, span): + return object qsome: + to getSubPattern(): + return subPattern + + to getQuant(): + return quant + + to getSpan(): + return span + to reserve(): + switch (quant): + match =="?": + return 0 + match =="+": + return subPattern.reserve() + match =="*": + return 0 + + to startShape(values, bindings, prefix, shapeSoFar): + return subPattern.startShape(values, bindings, prefix, shapeSoFar) + + to endShape(bindings, prefix, shape): + return subPattern.endShape(bindings, prefix, shape) + + to substSlice(values, indices): + def shape := subPattern.startShape(values, [], indices, -1) + if (shape < 0): + throw(`Indeterminate repetition: $qsome`) + def result := [].diverge() + for i in 0..!shape: + result.extend(subPattern.substSlice(values, indices + [i])) + subPattern.endShape([], indices, shape) + if (!inBounds(result.size(), quant)): + throw(`Improper quantity: $shape vs $quant`) + return result.snapshot() + + to matchBindSlice(values, var specimens, bindings, indices, var max): + def maxShape := subPattern.startShape(values, bindings, indices, -1) + var result := 0 + var shapeSoFar := 0 + while (maxShape == -1 || shapeSoFar < maxShape): + if (specimens.size() == 0): + break + if (quant == "?" && result > 0): + break + def more := subPattern.matchBindSlice(values, specimens, bindings, + indices + [shapeSoFar], max) + if (more == -1): + break + max -= more + if (more < 0 && maxShape == -1): + throw(`Patterns of indeterminate rank must make progress: $qsome vs $specimens`) + result += more + specimens := specimens.slice(more) + shapeSoFar += 1 + subPattern.endShape(bindings, indices, shapeSoFar) + if (!inBounds(result, quant)): + throw("Improper quantity: $result vs $quant") + return result diff --git a/monte/src/prim/terml/schema.mt b/monte/src/prim/terml/schema.mt new file mode 100644 index 0000000..460b321 --- /dev/null +++ b/monte/src/prim/terml/schema.mt @@ -0,0 +1,59 @@ +module unittest, convertToTerm, termFactory +export (baseSchema) +def t := termFactory + +object baseSchema: + to load(data): + return null + + to dump(data): + return null + +def baseNodeNames := ["null", "true", "false", ".String.", ".float64.", + ".char.", ".int.", ".tuple.", ".bag.", ".attr."] + +def wrap(f): + def testf(assert): + object moreAssert extends assert: + to check(schema, term): + return assert.equal(schema.load(schema.dump(term)), term) + + return f(moreAssert) + return testf + +def testNull(assert): + assert.check(baseSchema, t.null()) + +def testInt(assert): + assert.check(baseSchema, convertToTerm(0, null)) + assert.check(baseSchema, convertToTerm(-255, null)) + assert.check(baseSchema, convertToTerm(1048369, null)) + +def testBigint(assert): + assert.check(baseSchema, convertToTerm(0x100000001)) + assert.check(baseSchema, convertToTerm(443464870465066586048)) + assert.check(baseSchema, convertToTerm(-443464870465066586048)) + +def testFloat(assert): + assert.check(baseSchema, convertToTerm(0.0)) + assert.check(baseSchema, convertToTerm(-1.0)) + assert.check(baseSchema, convertToTerm(3.14)) + +def testString(assert): + assert.check(baseSchema, convertToTerm("")) + assert.check(baseSchema, convertToTerm("yes")) + assert.check(baseSchema, convertToTerm("\u2603")) + +def testTuple(assert): + assert.check(baseSchema, convertToTerm([0, 1, "", []])) + +def testMap(assert): + assert.check(baseSchema, convertToTerm([1 => "yes", "no" => 0])) + +def test_custom(assert): + def sch := baseSchema.extend(["foo" => 1]) + assert.check(sch, t.foo(0)) + assert.check(sch, t.foo(t.foo(null))) + +def tests := [testNull, ] +unittest([wrap(test) for test in tests]) diff --git a/monte/src/terml/tag.mt b/monte/src/prim/terml/tag.mt similarity index 57% rename from monte/src/terml/tag.mt rename to monte/src/prim/terml/tag.mt index 19e3a47..0e5edbe 100644 --- a/monte/src/terml/tag.mt +++ b/monte/src/prim/terml/tag.mt @@ -1,5 +1,5 @@ module unittest -export (Tag, makeTag) +export (Tag, makeTag, optMakeTagFromData) interface Tag :DeepFrozen guards TagStamp :DeepFrozen: pass @@ -7,7 +7,7 @@ interface Tag :DeepFrozen guards TagStamp :DeepFrozen: object makeTag as DeepFrozen: to asType(): return Tag - to run(code :nullOk[int >= 0], name :str, dataGuard :DeepFrozen): + to run(code :NullOk[Int], name :Str, dataGuard :DeepFrozen): return object tag implements Selfless, Transparent, TagStamp: to _uncall(): return [makeTag, "run", [code, name, dataGuard]] @@ -23,16 +23,16 @@ object makeTag as DeepFrozen: out.print(dataGuard) out.print(">") - to getTagCode(): + to getCode(): return code - to getTagName(): + to getName(): return name to getDataGuard(): return dataGuard - to isTagForData(data) :boolean: + to isTagForData(data) :Bool: if (data == null): return true if (dataGuard == null): @@ -40,8 +40,30 @@ object makeTag as DeepFrozen: return data =~ _ :dataGuard + to op__cmp(other): + return name.op__cmp(other.getName()) + +def optMakeTagFromData(val, mkt): + switch (val): + match ==null: + return mkt("null", null) + match ==true: + return mkt("true", null) + match ==false: + return mkt("false", null) + match v :Int: + return mkt(".int.", v) + match v :Double: + return mkt(".float64.", v) + match v :Str: + return mkt(".String.", v) + match v :Char: + return mkt(".char.", v) + match _: + return null + def testPrint(assert): - def t1 := makeTag(1, "foo", int) + def t1 := makeTag(1, "foo", Int) assert.equal(M.toString(t1), "") def t2 := makeTag(null, "foo", null) diff --git a/monte/src/terml/term.mt b/monte/src/prim/terml/term.mt similarity index 76% rename from monte/src/terml/term.mt rename to monte/src/prim/terml/term.mt index c32707c..85f8ad3 100644 --- a/monte/src/terml/term.mt +++ b/monte/src/prim/terml/term.mt @@ -1,11 +1,11 @@ -module Tag :DeepFrozen -export (Term, makeTerm) +module Tag :DeepFrozen, makeTag :DeepFrozen, optMakeTagFromData +export (Term, makeTerm, termBuilder) object TermStamp as DeepFrozen: to audit(_): return true -def TermData :DeepFrozen := any[nullOk, str, int, float, char] +def TermData :DeepFrozen := Any[NullOk, Str, Int, Double, Char] object Term as DeepFrozen: to coerce(specimen, ej): @@ -20,8 +20,8 @@ object makeTerm as DeepFrozen: to asType(): return Term - to run(tag :Tag, data :TermData, args :nullOk[List], span): - if (data != null && args != null): + to run(tag :Tag, data :TermData, args :List, span): + if (data != null && args != []): throw(`Term $tag can't have both data and children`) return object term implements TermStamp, Transparent, Selfless: @@ -43,6 +43,9 @@ object makeTerm as DeepFrozen: to getArgs(): return args + to asFunctor(): + return term + to withoutArgs(): return makeTerm(tag, data, [], span) @@ -50,7 +53,7 @@ object makeTerm as DeepFrozen: var tagCmp := tag.op__cmp(other.getTag()) if (tagCmp != 0): return tagCmp - if (data != null): + if (data == null): if (other.getData() != null): return -1 else: @@ -74,24 +77,26 @@ object makeTerm as DeepFrozen: to _conformTo(guard): def x := args != null && args.size() == 0 - if (x && [str, float, int, char].contains(guard)): + if (x && [Str, Double, Int, Char].contains(guard)): if (data == null): - return tag.getTagName() + return tag.getName() return data else: return term to _printOn(out): - return term.prettyPrintOn(out, false) + out.print("term`") + term.prettyPrintOn(out, false) + out.print("`") - to prettyPrintOn(out, isQuasi :boolean): + to prettyPrintOn(out, isQuasi :Bool): var label := null # should be def w/ later bind var reps := null var delims := null switch (data): match ==null: - label := tag.getTagName() - match f :float: + label := tag.getName() + match f :Double: if (f.isNaN()): label := "%NaN" else if (f.isInfinite()): @@ -101,7 +106,7 @@ object makeTerm as DeepFrozen: label := "-%Infinity" else: label := `$data` - match s :str: + match s :Str: label := s.quote().replace("\n", "\\n") match _: label := M.toQuote(data) @@ -113,7 +118,7 @@ object makeTerm as DeepFrozen: if (label == ".tuple."): if (term.getHeight() <= 1): - out.raw_print("[]") + out.print("[]") return reps := 1 delims := ["[", ",", "]"] @@ -126,7 +131,7 @@ object makeTerm as DeepFrozen: else if (args == null): out.print(label) return - else if (args.size() == 1 && (args[0].getTag().getTagName() != null)): + else if (args.size() == 1 && (args[0].getTag().getName() != null)): out.print(label) out.print("(") args[0].prettyPrintOn(out, isQuasi) @@ -160,3 +165,29 @@ object makeTerm as DeepFrozen: sub.println(sep) a.prettyPrintOn(sub, isQuasi) sub.print(close) + + +def mkt(name, data) as DeepFrozen: + return makeTerm(makeTag(null, name, Any), data, [], null) + +object termBuilder: + to leafInternal(tag, data, span): + return makeTerm(tag, data, [], span) + + to leafData(data, span): + return optMakeTagFromData(data, mkt) + + to composite(tag, data, span): + return termBuilder.term(termBuilder.leafInternal(tag, null, span)) + + to term(functor, args): + if (functor.getArgs().size() > 0): + throw(`To use as a functor, a Term must not have args: $functor`) + return makeTerm(functor.getTag(), functor.getData(), args.snapshot(), functor.getSpan()) + + to empty(): + return [].diverge() + + to addArg(arglist, arg): + arglist.push(arg) + return arglist diff --git a/monte/src/terml/termFactory.mt b/monte/src/prim/terml/termFactory.mt similarity index 100% rename from monte/src/terml/termFactory.mt rename to monte/src/prim/terml/termFactory.mt diff --git a/monte/src/prim/terml/termLexer.mt b/monte/src/prim/terml/termLexer.mt new file mode 100644 index 0000000..194fcd1 --- /dev/null +++ b/monte/src/prim/terml/termLexer.mt @@ -0,0 +1,377 @@ +module __makeOrderedSpace, makeTag, makeTerm, termBuilder, unittest +export (makeTermLexer) + +object VALUE_HOLE {} +object PATTERN_HOLE {} +object EOF {} +def decimalDigits := '0'..'9' +def hexDigits := decimalDigits | 'a'..'f' | 'A'..'F' + +# huh, maybe regions are dumb for this? guess we need sets +def segStart := 'a'..'z' | 'A'..'Z' | '_'..'_' | '$'..'$' | '.'..'.' +def segPart := segStart | '0'..'9' | '-'..'-' +def closers := ['(' => ')', '[' => ']', '{' => '}'] + + +def _makeTermLexer(input, builder, braceStack, var nestLevel): + + # The character under the cursor. + var currentChar := null + # Offset of the current character. + var position := -1 + + # Start offset of the text for the token being created. + var startPos := -1 + + # Syntax error produced from most recent tokenization attempt. + var errorMessage := null + + var count := -1 + + def leafTag(tagname, span): + return builder.leafInternal(makeTag(null, tagname, Any), null, span) + + + def atEnd(): + return position == input.size() + + def advance(): + position += 1 + if (atEnd()): + currentChar := EOF + else: + currentChar := input[position] + return currentChar + + def peekChar(): + if (atEnd()): + throw("attempt to read past end of input") + if (position + 1 == input.size()): + return EOF + return input[position + 1] + + def pushBrace(opener, closer, indent, canNest): + if (canNest): + nestLevel += 1 + braceStack.push([opener, closer, indent, canNest]) + + def popBrace(closer, fail): + if (braceStack.size() <= 1): + fail(`Unmatched closing character ${closer.quote()}`) + else if (braceStack.last()[1] != closer): + fail(`Mismatch: ${closer.quote()} doesn't close ${braceStack.last()[0]}`) + def item := braceStack.pop() + if (item[3]): + nestLevel -= 1 + + def skipWhitespace(): + if (atEnd()): + return + while (['\n', ' '].contains(currentChar)): + advance() + + def startToken(): + if (startPos >= 0): + throw("Token already started") + startPos := position + + def endToken(fail): + def pos := position + def tok := input.slice(startPos, pos) + startPos := -1 + return tok + + def collectDigits(var digitset): + if (atEnd() || !digitset(currentChar)): + return false + digitset |= ('_'..'_') + while (!atEnd() && digitset(currentChar)): + advance() + return true + + def numberLiteral(fail): + var radix := 10 + var floating := false + if (currentChar == '0'): + advance() + if (currentChar == 'X' || currentChar == 'x'): + radix := 16 + advance() + if (radix == 16): + collectDigits(hexDigits) + else: + collectDigits(decimalDigits) + if (currentChar == '.'): + def pc := peekChar() + if (pc == EOF): + fail("Missing fractional part") + if (decimalDigits(pc)): + advance() + floating := true + collectDigits(decimalDigits) + if (currentChar == 'e' || currentChar == 'E'): + advance() + floating := true + if (currentChar == '-' || currentChar == '+'): + advance() + if (!collectDigits(decimalDigits)): + fail("Missing exponent") + def tok := endToken(fail) + def s := tok.replace("_", "") + if (floating): + return builder.leafInternal(makeTag(null, ".float64.", Any), __makeFloat(s), tok.getSpan()) + else: + if (radix == 16): + return builder.leafInternal(makeTag(null, ".int.", Any), __makeInt(s.slice(2), 16), tok.getSpan()) + else: + return builder.leafInternal(makeTag(null, ".int.", Any), __makeInt(s), tok.getSpan()) + + def charConstant(fail): + if (currentChar == '\\'): + def nex := advance() + if (nex == 'u'): + def hexstr := __makeString.fromChars([advance() for _ in 0..!4]) + def v + try: + bind v := __makeInt(hexstr, 16) + catch _: + throw.eject(fail, "\\u escape must be four hex digits") + advance() + return __makeCharacter(v) + else if (nex == 'x'): + def v + try: + bind v := __makeInt(__makeString.fromChars([advance(), advance()]), 16) + catch _: + throw.eject(fail, "\\x escape must be two hex digits") + advance() + return __makeCharacter(v) + else if (nex == EOF): + throw.eject(fail, "End of input in middle of literal") + def c := [ + 'b' => '\b', + 't' => '\t', + 'n' => '\n', + 'f' => '\f', + 'r' => '\r', + '"' => '"', + '\'' => '\'', + '\\' => '\\', + '\n' => null, + ].fetch(nex, fn{-1}) + if (c == -1): + throw.eject(fail, `Unrecognized escape character ${nex.quote()}`) + else: + advance() + return c + if (currentChar == EOF): + throw.eject(fail, "End of input in middle of literal") + else if (currentChar == '\t'): + throw.eject(fail, "Quoted tabs must be written as \\t") + else: + def c := currentChar + advance() + return c + + def stringLike(fail): + def opener := currentChar + advance() + pushBrace(opener, '"', 0, false) + def buf := [].diverge() + while (currentChar != '"'): + if (atEnd()): + fail("Input ends inside string literal") + def cc := charConstant(fail) + if (cc != null): + buf.push(cc) + advance() + return __makeString.fromChars(buf.snapshot()) + + def charLiteral(fail): + advance() + var c := charConstant(fail) + while (c == null): + c := charConstant(fail) + if (currentChar != '\''): + throw.eject(fail, "Character constant must end in \"'\"") + advance() + return builder.leafInternal(makeTag(null, ".char.", Any), c, endToken(fail).getSpan()) + + def tag(fail, initial): + var done := false + def segs := [].diverge() + if (initial != null): + segs.push(initial) + while (currentChar == ':' && peekChar() == ':'): + advance() + advance() + if (currentChar == '"'): + def s := stringLike(fail) + segs.push("::\"") + segs.push(s) + segs.push("\"") + else: + segs.push("::") + def segStartPos := position + if (currentChar != EOF && segStart(currentChar)): + advance() + else: + throw.eject(fail, "Invalid character starting tag name segment") + while (currentChar != EOF && segPart(currentChar)): + advance() + segs.push(input.slice(segStartPos, position)) + return leafTag("".join(segs), endToken(fail).getSpan()) + + def getNextToken(fail): + skipWhitespace() + startToken() + def cur := currentChar + if (cur == EOF): + throw.eject(fail, null) + if (cur == '"'): + def s := stringLike(fail) + def closer := endToken(fail) + popBrace('"', fail) + + return builder.leafInternal(makeTag(null, ".String.", Any), s, closer.getSpan()) + if (cur == '\''): + return charLiteral(fail) + if (cur == '-'): + advance() + return numberLiteral(fail) + if (decimalDigits(cur)): + return numberLiteral(fail) + if (segStart(cur)): + def segStartPos := position + advance() + while (currentChar != EOF && segPart(currentChar)): + advance() + return tag(fail, input.slice(segStartPos, position)) + if (cur == ':' && peekChar() == ':'): + return tag(fail, null) + if (['(', '[','{'].contains(cur)): + pushBrace(cur, closers[cur], 1, true) + def s := input.slice(position, position + 1) + def t := leafTag(s, s.getSpan()) + advance() + return t + if ([')', ']', '}'].contains(cur)): + popBrace(cur, fail) + def s := input.slice(position, position + 1) + def t := leafTag(s, s.getSpan()) + advance() + return t + if ([':', '-', ',', '*', '+', '?'].contains(cur)): + def s := input.slice(position, position + 1) + def t := leafTag(s, s.getSpan()) + advance() + return t + fail(`Unrecognized character ${cur.quote()}`) + + advance() + return object termLexer: + + to _makeIterator(): + return termLexer + + to getSyntaxError(): + return errorMessage + + to valueHole(): + return VALUE_HOLE + + to patternHole(): + return PATTERN_HOLE + + to next(ej): + try: + if (currentChar == EOF): + throw.eject(ej, null) + def errorStartPos := position + escape e: + def t := getNextToken(e) + return [count += 1, t] + catch msg: + errorMessage := msg + throw.eject(ej, msg) + finally: + startPos := -1 + + to lexerForNextChunk(chunk): + return _makeTermLexer(chunk, builder, braceStack, nestLevel) + +object makeTermLexer: + to run(input, builder): + # State for paired delimiters like "", {}, (), [] + def braceStack := [[null, null, 0, true]].diverge() + return _makeTermLexer(input, builder, braceStack, 0) + + to holes(): + return [VALUE_HOLE, PATTERN_HOLE] + +def lex(s): + def l := makeTermLexer(s, termBuilder) + def toks := [t for t in l] + if ((def err := l.getSyntaxError()) != null): + throw(err) + if (toks.size() > 0 && toks.last().getTag().getName() == "EOL"): + return toks.slice(0, toks.size() - 1) + return toks + +def test_integer(assert): + def mkint(n): + return makeTerm(makeTag(null, ".int.", Any), n, [], null) + assert.equal(lex("0"), [mkint(0)]) + assert.equal(lex("-1"), [mkint(-1)]) + assert.equal(lex("7"), [mkint(7)]) + assert.equal(lex("3_000"), [mkint(3000)]) + assert.equal(lex("0xABad1dea"), [mkint(0xabad1dea)]) + +def test_float(assert): + def mkfloat(n): + return makeTerm(makeTag(null, ".float64.", Any), n, [], null) + assert.equal(lex("1e9"), [mkfloat(1e9)]) + assert.equal(lex("3.1415E17"), [mkfloat(3.1415E17)]) + assert.equal(lex("0.91"), [mkfloat(0.91)]) + assert.equal(lex("-0.91"), [mkfloat(-0.91)]) + assert.equal(lex("3e-2"), [mkfloat(3e-2)]) + +def test_string(assert): + def mkstr(s): + return makeTerm(makeTag(null, ".String.", Any), s, [], null) + assert.equal(lex("\"foo bar\""), [mkstr("foo bar")]) + assert.equal(lex("\"foo\\nbar\""), [mkstr("foo\nbar")]) + assert.equal(lex("\"foo\\\nbar\""), [mkstr("foobar")]) + assert.equal(lex("\"z\\u0061p\""), [mkstr("zap")]) + assert.equal(lex("\"z\\x61p\""), [mkstr("zap")]) + +def test_char(assert): + def mkchar(c): + return makeTerm(makeTag(null, ".char.", Any), c, [], null) + assert.equal(lex("'z'"), [mkchar('z')]) + assert.equal(lex("'\\n'"), [mkchar('\n')]) + assert.equal(lex("'\\u0061'"), [mkchar('a')]) + assert.equal(lex("'\\x61'"), [mkchar('a')]) + +def test_tag(assert): + def mkTag(n): + return makeTerm(makeTag(null, n, Any), null, [], null) + assert.equal(lex("foo"), [mkTag("foo")]) + assert.equal(lex("::\"foo\""), [mkTag("::\"foo\"")]) + assert.equal(lex("::foo"), [mkTag("::foo")]) + assert.equal(lex("foo::baz"), [mkTag("foo::baz")]) + assert.equal(lex("foo::\"baz\""), [mkTag("foo::\"baz\"")]) + assert.equal(lex("biz::baz::foo"), [mkTag("biz::baz::foo")]) + assert.equal(lex("foo_yay"), [mkTag("foo_yay")]) + assert.equal(lex("foo$baz32"), [mkTag("foo$baz32")]) + assert.equal(lex("foo-baz.19"), [mkTag("foo-baz.19")]) + +def test_quant(assert): + def mkTag(n): + return makeTerm(makeTag(null, n, Any), null, [], null) + assert.equal(lex("*"), [mkTag("*")]) + assert.equal(lex("+"), [mkTag("+")]) + assert.equal(lex("?"), [mkTag("?")]) + + +unittest([test_integer, test_float, test_string, test_char, test_tag, test_quant]) diff --git a/monte/src/prim/terml/termParser.mt b/monte/src/prim/terml/termParser.mt new file mode 100644 index 0000000..f4c7cc8 --- /dev/null +++ b/monte/src/prim/terml/termParser.mt @@ -0,0 +1,353 @@ +module __makeOrderedSpace, makeTag, optMakeTagFromData, makeTerm, makeTermLexer, convertToTerm, makeQFunctor, makeQTerm, makeQSome, makeQDollarHole, makeQAtHole, qEmptySeq, makeQPairSeq, termBuilder, unittest +export (parseTerm, quasitermParser) +def tokenStart := 'a'..'z' | 'A'..'Z' | '_'..'_' | '$'..'$' | '.'..'.' + + +def mkq(name, data): + return makeQFunctor(makeTag(null, name, Any), data, null) + +object qBuilder: + to leafInternal(tag, data, span): + return makeQFunctor(tag, data, span) + + to leafData(data, span): + return makeQFunctor(optMakeTagFromData(data, mkq), data, span) + + to composite(tag, data, span): + return qBuilder.term(qBuilder.leafInternal(tag, null, span), qBuilder.leafData(data, span)) + + to term(functor, args): + if (functor.isHole() && !functor.getIsFunctorHole()): + return functor + return makeQTerm(functor, args) + + to some(sub, quant): + return makeQSome(sub, quant, if (sub == null) {null} else {sub.getSpan()}) + + to empty(): + return qEmptySeq + + to addArg(arglist, arg): + return makeQPairSeq(arglist, arg) + + +def _parseTerm(lex, builder, err): + def [VALUE_HOLE, PATTERN_HOLE] := [lex.valueHole(), lex.patternHole()] + def tokens := __makeList.fromIterable(lex) + var dollarHoleValueIndex := -1 + var atHoleValueIndex := -1 + var position := -1 + + def onError(e, msg): + def syntaxError(_): + e(msg) + return syntaxError + + def advance(ej): + position += 1 + if (position >= tokens.size()): + ej("hit EOF") + return tokens[position] + + def rewind(): + position -= 1 + + def peek(): + if (position + 1 >= tokens.size()): + return null + return tokens[position + 1] + + def accept(termName, fail): + def t := advance(fail) + def isHole := t == VALUE_HOLE || t == PATTERN_HOLE + if (!isHole && t.getTag().getName() == termName): + return t + else: + rewind() + fail(`expected $termName, got $t`) + + def maybeAccept(termName): + escape e: + def t := advance(e) + def isHole := t == VALUE_HOLE || t == PATTERN_HOLE + if (!isHole && t.getTag().getName() == termName): + return t + rewind() + return null + + def functor(fail): + def token := advance(fail) + if (token == VALUE_HOLE): + return makeQDollarHole(null, dollarHoleValueIndex += 1, false) + if (token == PATTERN_HOLE): + return makeQAtHole(null, atHoleValueIndex += 1, false) + if (token.getData() != null): + return token + def name := token.getTag().getName() + if (name.size() > 0 && tokenStart(name[0])): + if (peek() == VALUE_HOLE): + advance(fail) + return makeQDollarHole(token, dollarHoleValueIndex += 1, false) + if (peek() == PATTERN_HOLE): + advance(fail) + return makeQAtHole(token.getTag(), atHoleValueIndex += 1, false) + return token + rewind() + fail(null) + + def term + def arglist(closer, fail): + var args := builder.empty() + escape e: + args := builder.addArg(args, term(e)) + catch err: + accept(closer, fail) + return args + escape outOfArgs: + while (true): + accept(",", outOfArgs) + args := builder.addArg(args, term(outOfArgs)) + accept(closer, fail) + return args + def namedTerm(name, args): + return builder.term(builder.leafInternal(makeTag(null, name, Any), null, null), args) + def extraTerm(fail): + if (maybeAccept("[") != null): + return namedTerm(".tuple.", arglist("]", fail)) + else if (maybeAccept("{") != null): + return namedTerm(".bag.", arglist("}", fail)) + def rootTerm := functor(fail) + if (maybeAccept("{") != null): + def f := rootTerm.asFunctor() + return builder.term(f, builder.addArg(builder.empty(), namedTerm(".bag.", arglist("}", fail)))) + if (maybeAccept("(") != null): + def f := rootTerm.asFunctor() + return builder.term(f, arglist(")", fail)) + return builder.term(rootTerm, builder.empty()) + + def prim(fail): + def k := extraTerm(fail) + if (maybeAccept(":") != null): + def v := extraTerm(onError(fail, "Expected term after ':'")) + return namedTerm(".attr.", builder.addArg(builder.addArg(builder.empty(), k), v)) + else: + return k + + def some(t): + if (maybeAccept("*") != null): + return builder.some(t, "*") + if (maybeAccept("+") != null): + return builder.some(t, "+") + if (maybeAccept("?") != null): + return builder.some(t, "?") + return t + + bind term(fail): + if (maybeAccept("(") != null): + return some(arglist(")", fail)) + return some(prim(fail)) + + term # deleting this line breaks tests. is there some compiler BS going on? + return prim(err) + +def parseTerm(input): + def lex := makeTermLexer(input, termBuilder) + return _parseTerm(lex, termBuilder, throw) + +def makeQuasiTokenChain(makeLexer, template): + var i := -1 + var current := makeLexer("", qBuilder) + var lex := current + def [VALUE_HOLE, PATTERN_HOLE] := makeLexer.holes() + var j := 0 + return object chainer: + to _makeIterator(): + return chainer + + to valueHole(): + return VALUE_HOLE + + to patternHole(): + return PATTERN_HOLE + + to next(ej): + if (i >= template.size()): + throw.eject(ej, null) + j += 1 + if (current == null): + if (template[i] == VALUE_HOLE || template[i] == PATTERN_HOLE): + def hol := template[i] + i += 1 + return [j, hol] + else: + current := lex.lexerForNextChunk(template[i])._makeIterator() + lex := current + escape e: + def t := current.next(e)[1] + return [j, t] + catch z: + i += 1 + current := null + return chainer.next(ej) + + +def [VALUE_HOLE, PATTERN_HOLE] := makeTermLexer.holes() + +object quasitermParser: + to valueHole(n): + return VALUE_HOLE + to patternHole(n): + return PATTERN_HOLE + + to valueMaker(template): + def chain := makeQuasiTokenChain(makeTermLexer, template) + def q := _parseTerm(chain, qBuilder, throw) + return object qterm extends q: + to substitute(values): + def vals := q.substSlice(values, [].diverge()) + if (vals.size() != 1): + throw(`Must be a single match: ${vals}`) + return vals[0] + + to matchMaker(template): + def chain := makeQuasiTokenChain(makeTermLexer, template) + def q := _parseTerm(chain, qBuilder, throw) + return object qterm extends q: + to matchBind(values, specimen, ej): + def bindings := [].diverge() + def blee := q.matchBindSlice(values, [specimen], bindings, [], 1) + if (blee == 1): + return bindings + else: + ej(`$q doesn't match $specimen: $blee`) + + to makeTag(code, name, guard): + return makeTag(code, name, guard) + + to makeTerm(tag, data, arglist, span): + return makeTerm(tag, data, arglist, span) + + + +def test_literal(assert): + def mk(tag, val): + return makeTerm(makeTag(null, tag, Any), val, [], null) + assert.equal(parseTerm("0xDECAFC0FFEEBAD"), mk(".int.", 0xDECAFC0FFEEBAD)) + assert.equal(parseTerm("3.14159E17"), mk(".float64.", 3.14159E17)) + assert.equal(parseTerm("1e9"), mk(".float64.", 1e9)) + assert.equal(parseTerm("0"), mk(".int.", 0)) + assert.equal(parseTerm("7"), mk(".int.", 7)) + assert.equal(parseTerm("-1"), mk(".int.", -1)) + assert.equal(parseTerm("-3.14"), mk(".float64.", -3.14)) + assert.equal(parseTerm("3_000"), mk(".int.", 3000)) + assert.equal(parseTerm("0.91"), mk(".float64.", 0.91)) + assert.equal(parseTerm("3e-2"), mk(".float64.", 3e-2)) + assert.equal(parseTerm("\"foo\\nbar\""), mk(".String.", "foo\nbar")) + assert.equal(parseTerm("\"foo\\\nbar\""), mk(".String.", "foobar")) + assert.equal(parseTerm("\"z\\x61p\""), mk(".String.", "zap")) + assert.equal(parseTerm("'x'"), mk(".char.", 'x')) + assert.equal(parseTerm("'\\n'"), mk(".char.", '\n')) + assert.equal(parseTerm("'\\u0061'"), mk(".char.", 'a')) + +def test_simpleTerm(assert): + def mk(name, args): + return makeTerm(makeTag(null, name, Any), null, args, null) + assert.equal(parseTerm("x"), mk("x", [])) + assert.equal(parseTerm("x()"), mk("x", [])) + assert.equal(parseTerm("x(y)"), mk("x", [mk("y", [])])) + assert.equal(parseTerm("x(y, z)"), mk("x", [mk("y", []), mk("z", [])])) + assert.equal(parseTerm("x(y, z,)"), mk("x", [mk("y", []), mk("z", [])])) + +def test_fullTerm(assert): + assert.equal(parseTerm("[x, y, 1]"), parseTerm(".tuple.(x, y, 1)")) + assert.equal(parseTerm("{x, y, 1}"), parseTerm(".bag.(x, y, 1)")) + assert.equal(parseTerm("f {x, y, 1}"), parseTerm("f(.bag.(x, y, 1))")) + assert.equal(parseTerm("a: b"), parseTerm(".attr.(a, b)")) + +def test_qtermSubstitute(assert): + def qt__quasiParser := quasitermParser + { + def x := 1 + def y := parseTerm("baz") + assert.equal(qt`foo($x, $y)`, parseTerm("foo(1, baz)")) + + } + { + def x := parseTerm("foo") + assert.equal(qt`$x(3)`, parseTerm("foo(3)")) + def y := parseTerm("baz(3)") + assert.equal(qt`foo($y)`._uncall(), parseTerm("foo(baz(3))")._uncall()) + } + { + def x := parseTerm("foo(3)") + assert.raises(fn { qt`$x(3)` }) + } + { + def args := [qt`foo`, qt`bar(3)`] + assert.equal(qt`zip($args*)`, qt`zip(foo, bar(3))`) + assert.equal(qt`zip($args+)`, qt`zip(foo, bar(3))`) + assert.equal(qt`zip(${[]}*)`, qt`zip`) + assert.raises(fn {qt`zip($args?)`}) + assert.raises(fn {qt`zip(${[]}+)`}) + } + + +def test_qtermMatch(assert): + def qt__quasiParser := quasitermParser + { + def qt`@foo` := "hello" + assert.equal(foo, parseTerm("\"hello\"")) + } + { + def qt`@bar()` := "hello" + assert.equal(bar, parseTerm("hello")) + } + { + assert.raises(fn {def qt`hello@foo` := "hello"}) + } + { + def qt`hello@foo` := parseTerm("hello(3, 4)") + assert.equal(foo, parseTerm("hello(3, 4)")) + } + { + def qt`.String.@foo` := "hello" + assert.equal(foo, qt`"hello"`) + } + { + # XXX WTF does this mean? + def qt`hello@bar()` := "hello" + assert.equal(bar, parseTerm("hello")) + } + { + assert.raises(fn { + def qt`hello@bar()` := "hello world" + }) + } + { + def qt`${qt`foo`}(@args*)` := parseTerm("foo(2, 3)") + assert.equal(args, [qt`2`, qt`3`]) + } + { + def t := qt`foo(bar, bar(3), zip(zap))` + def qt`foo(bar@bars*, zip@z)` := t + assert.equal(bars, [qt`bar`, qt`bar(3)`]) + assert.equal(z, qt`zip(zap)`) + } + { + def qt`[@x*, @y, @z]` := qt`[4, 5, 6, 7, 8]` + assert.equal([x, y, z], [[qt`4`, qt`5`, qt`6`], qt`7`, qt`8`]) + } + { + def qt`[@x*, @y?, @z]` := qt`[4, 5, 6, 7, 8]` + assert.equal([x, y, z], [[qt`4`, qt`5`, qt`6`, qt`7`], [], qt`8`]) + } + { + def qt`[@x*, @y+, @z]` := qt`[4, 5, 6, 7, 8]` + assert.equal([x, y, z], [[qt`4`, qt`5`, qt`6`], [qt`7`], qt`8`]) + } + { + def qt`[@x*, (@y, @z)+]` := qt`[4, 5, 6, 7, 8]` + assert.equal([x, y, z], [[qt`4`, qt`5`, qt`6`], [qt`7`], [qt`8`]]) + } +unittest([test_literal, test_simpleTerm, test_fullTerm, test_qtermSubstitute, + test_qtermMatch]) diff --git a/monte/src/terml/convertToTerm.mt b/monte/src/terml/convertToTerm.mt deleted file mode 100644 index 30c6287..0000000 --- a/monte/src/terml/convertToTerm.mt +++ /dev/null @@ -1,67 +0,0 @@ -module makeTerm :DeepFrozen, Term :DeepFrozen, makeTag :DeepFrozen, unittest -export (convertToTerm) - -def mkt(name, data, args) as DeepFrozen: - return makeTerm(makeTag(null, name, null), data, args, null) - -def convertToTerm(val, ej) as DeepFrozen: - switch (val): - match _ :Term: - return val - match ==null: - return mkt("null", null, null) - match ==true: - return mkt("true", null, null) - match ==false: - return mkt("false", null, null) - match v :int: - return mkt(".int.", v, null) - match v :float: - return mkt(".float.", v, null) - match v :str: - return mkt(".String.", v, null) - match v :char: - return mkt(".char.", v, null) - match v :List: - def l := [convertToTerm(item, ej) for item in v] - return mkt(".tuple.", null, l) - # match v :set: - # return mkt(".bag.", null, [convertToTerm(item) for item in v]) - match m :Map: - return mkt(".bag.", null, - [mkt(".attr.", null, [convertToTerm(k, ej), - convertToTerm(v, ej)]) - for k => v in m]) - match _: - throw.eject(ej, `Could not coerce $val to term`) - -def test_convert(assert): - def t1 := convertToTerm([1, null, 2.5, "yes", 'c', true, [1 => 2]], null) - assert.equal(t1.getTag().getTagName(), ".tuple.") - def a := t1.getArgs() - def one := a[0] - assert.equal(one.getTag().getTagName(), ".int.") - assert.equal(one.getData(), 1) - def nul := a[1] - assert.equal(nul.getTag().getTagName(), "null") - def flo := a[2] - assert.equal(flo.getTag().getTagName(), ".float.") - assert.equal(flo.getData(), 2.5) - def s := a[3] - assert.equal(s.getTag().getTagName(), ".String.") - assert.equal(s.getData(), "yes") - def c := a[4] - assert.equal(c.getTag().getTagName(), ".char.") - assert.equal(c.getData(), 'c') - def b := a[5] - assert.equal(b.getTag().getTagName(), "true") - def m := a[6] - assert.equal(m.getTag().getTagName(), ".bag.") - def ma := m.getArgs() - assert.equal(ma[0].getTag().getTagName(), ".attr.") - def k := ma[0].getArgs()[0] - assert.equal(k.getData(), 1) - def v := ma[0].getArgs()[1] - assert.equal(v.getData(), 2) - -unittest([test_convert]) diff --git a/monte/src/terml/package.mt b/monte/src/terml/package.mt deleted file mode 100644 index 1f7abb7..0000000 --- a/monte/src/terml/package.mt +++ /dev/null @@ -1,13 +0,0 @@ -def files := pkg.readFiles(".") -def unittest := pkg.testCollector() - -def [=> Tag, => makeTag] := files["tag"]([=> unittest]) -def [=> Term, => makeTerm] := files["term"]([=> Tag]) -def [=> convertToTerm] := files["convertToTerm"]([=> makeTerm, => Term, - => makeTag, => unittest]) -def [=> termFactory] := files["termFactory"]([=> makeTerm, => makeTag, - => convertToTerm]) - -def terml := pkg.makeModule([=> Tag, => Term, => makeTag, => makeTerm, - => termFactory]) -terml diff --git a/monte/src/test_ometa.mt b/monte/src/test_ometa.mt index 6d12b9d..4c62829 100644 --- a/monte/src/test_ometa.mt +++ b/monte/src/test_ometa.mt @@ -1,6 +1,4 @@ module makeOMeta, unittest -export (foo) -def foo := null def makeRuntimeTests(assert): def test_anything(): diff --git a/monte/src/test_operators.mt b/monte/src/test_operators.mt new file mode 100644 index 0000000..88ecd7e --- /dev/null +++ b/monte/src/test_operators.mt @@ -0,0 +1,60 @@ +module unittest + +def makeOperatorTests(assert): + def test_op_rocket(): + def ar := [1,3] + var change_me := 0 + + for a => b in ar: + change_me := b + + assert.equal(change_me, 3) + + def test_op_asBigAs(): + assert.equal(4 <=> 4, true) + assert.equal(4 <=> 8, false) + + def test_op_assign(): + def a := 3 + var b := 8 + assert.equal(a, 3) + assert.equal(b, 8) + + def test_op_exponent(): + assert.equal(2 ** 8, 256) + + def test_op_multiply(): + assert.equal(2 * 8, 16) + + def test_op_equality(): + assert.equal(4 == 4, true) + assert.equal(4 == 7, false) + + def test_op_lessThan(): + assert.equal(2 < 5, true) + assert.equal(5 < 2, false) + + def test_op_greaterThan(): + assert.equal(9 > 3, true) + assert.equal(3 > 9, false) + + def test_op_lessThanOrEqual(): + assert.equal(6 <= 9, true) + assert.equal(6 <= 6, true) + assert.equul(9 <= 6, false) + + def test_op_greaterThanOrEqual(): + assert.equal(8 >= 0, true) + assert.equal(0 >= 0, true) + assert.equal(0 >= 8, false) + + def test_op_and(): + assert.equal(true && true, true) + assert.equal(false && false, true) + assert.equal(true && false, false) + assert.equal(false && true, false) + + return [test_op_rocket, test_op_asBigAs, test_op_assign, test_op_exponent, test_op_multiply, test_op_equality, + test_op_lessThan, test_op_greaterThan, test_op_lessThanOrEqual, test_op_greaterThanOrEqual, test_op_and] + +unittest([makeOperatorTests]) diff --git a/monte/src/test_regions.mt b/monte/src/test_regions.mt index 8a53a38..508d194 100644 --- a/monte/src/test_regions.mt +++ b/monte/src/test_regions.mt @@ -1,6 +1,4 @@ module unittest -export (foo) -def foo := null def testIterable(assert): assert.equal([x for x in 0..!5], [0, 1, 2, 3, 4]) diff --git a/monte/src/test_switch.mt b/monte/src/test_switch.mt index 50cfc44..bcd4a1c 100644 --- a/monte/src/test_switch.mt +++ b/monte/src/test_switch.mt @@ -1,11 +1,8 @@ module unittest -export (foo) - -def foo := null def makeIntPatternTests(assert): - def test_equal(): + def test_pattern_equal(): def foo(n): switch (n){ match == 0 { return 0 } @@ -17,7 +14,7 @@ def makeIntPatternTests(assert): def test_suchthat_pythonic(): def foo(n): switch(n): - match x ? (x < 3): + match x ? (x < 3): return 0 match _ : return 1 @@ -42,7 +39,7 @@ def makeIntPatternTests(assert): assert.equal(foo(0), 0) assert.equal(foo(42), 1) - return [test_equal, test_suchthat_pythonic, test_suchthat_brackets, + return [test_pattern_equal, test_suchthat_pythonic, test_suchthat_brackets, test_mixing_brackets] unittest([makeIntPatternTests]) diff --git a/monte/src/test_unicode.mt b/monte/src/test_unicode.mt index 1e9a258..1d40028 100644 --- a/monte/src/test_unicode.mt +++ b/monte/src/test_unicode.mt @@ -1,18 +1,16 @@ module unittest -export(foo) # Until https://github.com/monte-language/monte/issues/23 -def foo := null def makeUnicodeTest(assert): - def test_char(): + def test_unicode_char(): def snowman := '\u2603' # def snowman := '☃' traceln(snowman) assert.equal(snowman, '\u2603') - def test_string(): + def test_unicode_string(): def snowman := "\u2603" # def snowman := "☃" traceln(snowman) assert.equal(snowman, "\u2603") - return [test_char, test_string] + return [test_unicode_char, test_unicode_string] unittest([makeUnicodeTest]) diff --git a/monte/src/unittest.mt b/monte/src/unittest.mt index c4da34e..d740661 100644 --- a/monte/src/unittest.mt +++ b/monte/src/unittest.mt @@ -2,9 +2,13 @@ object _failure: pass object unitTestAssertions: + to notEqual(left, right): + if (left == right): + throw(`Equal: ${M.toQuote(left)} == ${M.toQuote(right)}`) + to equal(left, right): if (left != right): - throw(`Not equal: $left != $right`) + throw(`Not equal: ${M.toQuote(left)} != ${M.toQuote(right)}`) to ejects(f): var reason := null diff --git a/monte/test/test_ast.py b/monte/test/test_ast.py index 1646c12..504c0a2 100644 --- a/monte/test/test_ast.py +++ b/monte/test/test_ast.py @@ -14,6 +14,17 @@ def test_decode(self): [0, 17, 64, 250, 224, 239]) +class VarIntTests(unittest.TestCase): + def test_single_roundtrip(self): + i = 42 + j, _ = ast.loadVarint(ast.dumpVarint(i), 0) + self.assertEqual(i, j) + + def test_double_roundtrip(self): + i = 402 + j, _ = ast.loadVarint(ast.dumpVarint(i), 0) + self.assertEqual(i, j) + class CodecTests(unittest.TestCase): def check(self, term): diff --git a/monte/test/test_compiler.py b/monte/test/test_compiler.py index 179abdd..872a262 100644 --- a/monte/test/test_compiler.py +++ b/monte/test/test_compiler.py @@ -67,9 +67,9 @@ def test_varNoun(self): """) def test_guardedVar(self): - self.eq_("var x :(1..!10) := 1", + self.eq_("var x :Int := 1", """ - _g_guard1 = _m_outerScope["__makeOrderedSpace"].op__till(_monte.wrap(1), _monte.wrap(10)) + _g_guard1 = _m_outerScope["Int"] _g_x2 = _monte.wrap(1) x = _monte.VarSlot(_g_guard1, _g_x2, _monte.throw) _g_x2 @@ -89,9 +89,9 @@ def test_assign(self): self.assertRaises(CompileError, ecompile, "x := 2", {}) def test_guardpattern(self): - self.eq_("def x :float := 1", + self.eq_("def x :Double := 1", """ - _g_guard1 = _m_outerScope["float"] + _g_guard1 = _m_outerScope["Double"] x = _g_guard1.coerce(_monte.wrap(1), _monte.throw) x """) @@ -109,7 +109,7 @@ def test_listpattern(self): _g_total_list1 """) - self.eq_('def [x :float, y :str, z] := "foo"', + self.eq_('def [x :Double, y :Str, z] := "foo"', """ _g_total_list1 = _monte.wrap(u'foo') try: @@ -117,15 +117,15 @@ def test_listpattern(self): except ValueError, _g_e5: _monte.throw(_g_e5) raise RuntimeError("Ejector did not exit") - _g_guard6 = _m_outerScope["float"] + _g_guard6 = _m_outerScope["Double"] x = _g_guard6.coerce(_g_list2, _monte.throw) - _g_guard7 = _m_outerScope["str"] + _g_guard7 = _m_outerScope["Str"] y = _g_guard7.coerce(_g_list3, _monte.throw) z = _g_list4 _g_total_list1 """) - self.eq_('def ej := 1; def [x :float, y :str, z] exit ej := "foo"', + self.eq_('def ej := 1; def [x :Double, y :Str, z] exit ej := "foo"', """ ej = _monte.wrap(1) _g_total_list1 = _monte.wrap(u'foo') @@ -134,9 +134,9 @@ def test_listpattern(self): except ValueError, _g_e5: ej(_g_e5) raise RuntimeError("Ejector did not exit") - _g_guard6 = _m_outerScope["float"] + _g_guard6 = _m_outerScope["Double"] x = _g_guard6.coerce(_g_list2, ej) - _g_guard7 = _m_outerScope["str"] + _g_guard7 = _m_outerScope["Str"] y = _g_guard7.coerce(_g_list3, ej) z = _g_list4 _g_total_list1 @@ -271,7 +271,7 @@ def run(outer, f): return o outer = _m_outer_Script() - outer(_monte.wrap(0)).inner(_monte.wrap(1)) + outer.run(_monte.wrap(0)).inner(_monte.wrap(1)) """) def test_classGensym(self): @@ -309,9 +309,9 @@ def test_frameFinal(self): self.eq_( ''' object foo { - method baz(x :int, y) { + method baz(x :Int, y) { def a := 2 - def b :(float >= 0) := 3.0 + def b :(Double >= 0) := 3.0 object boz { method blee() { b.foo(a + x) } } @@ -336,10 +336,10 @@ def blee(boz): class _m_foo_Script(_monte.MonteObject): _m_fqn = '__main$foo' def baz(foo, _g_Final1, y): - _g_guard2 = _m_outerScope["int"] + _g_guard2 = _m_outerScope["Int"] x = _g_guard2.coerce(_g_Final1, _monte.throw) a = _monte.wrap(2) - _g_guard3 = _m_outerScope["__comparer"].geq(_m_outerScope["float"], _monte.wrap(0)) + _g_guard3 = _m_outerScope["__comparer"].geq(_m_outerScope["Double"], _monte.wrap(0)) b = _g_guard3.coerce(_monte.wrap(3.0), _monte.throw) boz = _m_boz_Script((_monte.FinalSlot(a, _monte.null, unsafe=True), _monte.FinalSlot.asType().get(_monte.null)), (_monte.FinalSlot(b, _g_guard3, unsafe=True), _monte.FinalSlot.asType().get(_g_guard3)), (_monte.FinalSlot(x, _g_guard2, unsafe=True), _monte.FinalSlot.asType().get(_g_guard2))) return boz @@ -352,9 +352,9 @@ def test_sharedVar(self): self.eq_( ''' object foo { - method baz(x :int, y) { + method baz(x :Int, y) { var a := 1 - var b :int := 0 + var b :Int := 0 object left { method inc() { a += 1; b } } @@ -398,16 +398,16 @@ def dec(right): class _m_foo_Script(_monte.MonteObject): _m_fqn = '__main$foo' def baz(foo, _g_Final1, y): - _g_guard2 = _m_outerScope["int"] + _g_guard2 = _m_outerScope["Int"] x = _g_guard2.coerce(_g_Final1, _monte.throw) _g_a3 = _monte.wrap(1) a = _monte.VarSlot(_monte.null, _g_a3, _monte.throw) - _g_guard4 = _m_outerScope["int"] + _g_guard4 = _m_outerScope["Int"] _g_b5 = _monte.wrap(0) b = _monte.VarSlot(_g_guard4, _g_b5, _monte.throw) left = _m_left_Script((a, _monte.VarSlot.asType().get(_monte.null)), (b, _monte.VarSlot.asType().get(_g_guard4))) right = _m_right_Script((a, _monte.VarSlot.asType().get(_monte.null)), (b, _monte.VarSlot.asType().get(_g_guard4))) - return _m_outerScope["__makeList"](left, right) + return _m_outerScope["__makeList"].run(left, right) foo = _m_foo_Script() foo @@ -449,7 +449,7 @@ def __init__(b, _m___return_slotPair, x_slotPair): def do(b): c = _m_c_Script((b._m_slots["x"][0], b._m_slots["x"][1])) - return b._m___return(c) + return b._m___return.run(c) class _m_a_Script(_monte.MonteObject): _m_fqn = '__main$a' @@ -459,7 +459,7 @@ def go(a): _g_x3 = _monte.wrap(0) x = _monte.VarSlot(_monte.null, _g_x3, _monte.throw) b = _m_b_Script((_monte.FinalSlot(_m___return, _monte.null, unsafe=True), _monte.FinalSlot.asType().get(_monte.null)), (x, _monte.VarSlot.asType().get(_monte.null))) - _m___return(b) + _m___return.run(b) _g_escape2 = _monte.null except _m___return._m_type, _g___return1: _g_escape2 = _g___return1.args[0] @@ -490,7 +490,7 @@ def __init__(foo, _m_auditors): } foo._m_audit(_m_auditors, _m_outerScope) - _m_objectExpr = "0 :)! #foo '# )! *DeepFrozen)! $Data1 ' ' " + _m_objectExpr = "1 ;+##foo '# +#*DeepFrozen+#$Data2 ' ' " foo = _m_foo_Script([_m_outerScope["DeepFrozen"], _m_outerScope["Data"]]) foo @@ -499,9 +499,9 @@ def __init__(foo, _m_auditors): def test_auditBindingGuards(self): self.eq_( ''' - def x :int := 1 + def x :Int := 1 def y := 2 - var z :float := 0 + var z :Double := 0 def &w := __makeFinalSlot(9) object foo implements DeepFrozen, Data { method run() { @@ -531,17 +531,17 @@ def __init__(foo, _m_auditors, w_slotPair, x_slotPair, y_slotPair, z_slotPair): foo._m_audit(_m_auditors, _m_outerScope) def run(foo): - return _m_outerScope["__makeList"](foo.x, foo.y, foo.z, foo.w) + return _m_outerScope["__makeList"].run(foo.x, foo.y, foo.z, foo.w) - _m_objectExpr = "0 :)! #foo '# )! *DeepFrozen)! $Data1 '!2 ! #run' ,)! *__makeList! #run'$)! !x)! !y)! !z)! !w' " + _m_objectExpr = "1 ;+##foo '# +#*DeepFrozen+#$Data2 '!3 ##run' .+#*__makeList##run'$+#!x+#!y+#!z+#!w' " - _g_guard1 = _m_outerScope["int"] + _g_guard1 = _m_outerScope["Int"] x = _g_guard1.coerce(_monte.wrap(1), _monte.throw) y = _monte.wrap(2) - _g_guard2 = _m_outerScope["float"] + _g_guard2 = _m_outerScope["Double"] _g_z3 = _monte.wrap(0) z = _monte.VarSlot(_g_guard2, _g_z3, _monte.throw) - w = _m_outerScope["__slotToBinding"](_m_outerScope["__makeFinalSlot"](_monte.wrap(9)), _monte.wrapEjector(_monte.throw)) + w = _m_outerScope["__slotToBinding"](_m_outerScope["__makeFinalSlot"].run(_monte.wrap(9)), _monte.wrapEjector(_monte.throw)) foo = _m_foo_Script([_m_outerScope["DeepFrozen"], _m_outerScope["Data"]], (w.slot, w.guard), (_monte.FinalSlot(x, _g_guard1, unsafe=True), _monte.FinalSlot.asType().get(_g_guard1)), (_monte.FinalSlot(y, _monte.null, unsafe=True), _monte.FinalSlot.asType().get(_monte.null)), (z, _monte.VarSlot.asType().get(_g_guard2))) foo """) @@ -562,7 +562,7 @@ def __init__(foo, _m_auditors): } foo._m_audit(_m_auditors, _m_outerScope) - _m_objectExpr = '0 :)! #foo \'")! $Data)! *DeepFrozen1 \' \' ' + _m_objectExpr = '1 ;+##foo \'"+#$Data+#*DeepFrozen2 \' \' ' foo = _m_foo_Script([_m_outerScope["Data"], _m_outerScope["DeepFrozen"]]) foo @@ -587,7 +587,7 @@ def __init__(foo, _m_auditors, foo_slotPair): } foo._m_audit(_m_auditors, _m_outerScope) - _m_objectExpr = '0 <)! #foo \'")! $Data)! *DeepFrozen1 \' \' ' + _m_objectExpr = '1 =+##foo \'"+#$Data+#*DeepFrozen2 \' \' ' foo = _monte.VarSlot(_monte.null) _g_foo1 = _m_foo_Script([_m_outerScope["Data"], _m_outerScope["DeepFrozen"]], (foo, _monte.VarSlot.asType().get(_m_outerScope["Data"]))) @@ -597,7 +597,7 @@ def __init__(foo, _m_auditors, foo_slotPair): def test_methGuard(self): self.eq_( - 'object foo { method baz(x, y) :int { x }}', + 'object foo { method baz(x, y) :Int { x }}', """ class _m_foo_Script(_monte.MonteObject): _m_fqn = '__main$foo' @@ -608,7 +608,7 @@ def __init__(foo, _m_methodGuards): def baz(foo, x, y): return foo._m_guardForMethod('baz').coerce(x, _monte.throw) - foo = _m_foo_Script({'baz': _m_outerScope["int"]}) + foo = _m_foo_Script({'baz': _m_outerScope["Int"]}) foo """) @@ -654,7 +654,7 @@ class _m_foo_Script(_monte.MonteObject): def run(foo, x): _m___return = _monte.ejector("__return") try: - _m___return(_monte.wrap(1)) + _m___return.run(_monte.wrap(1)) _g_escape2 = _monte.null except _m___return._m_type, _g___return1: _g_escape2 = _g___return1.args[0] @@ -692,7 +692,7 @@ def __init__(foo, foo_slotPair): def run(foo, x): _m___return = _monte.ejector("__return") try: - _m___return(foo.foo(x)) + _m___return.run(foo.foo.run(x)) _g_escape2 = _monte.null except _m___return._m_type, _g___return1: _g_escape2 = _g___return1.args[0] @@ -774,7 +774,7 @@ def blee(boz): boz.b = _g_b2 return _monte.StaticContext('__main$foo$boz', ['a', 'b'], _m_boz_Script._m_objectExpr) - _m_objectExpr = '0 :)! #boz \'! 1 \'!2 ! $blee\' +\'"4)! !b)! !a9! \'Context\' ' + _m_objectExpr = '1 ;+##boz \'! 2 \'!3 #$blee\' -\'"5+#!b+#!a:#\'Context\' ' class _m_foo_Script(_monte.MonteObject): _m_fqn = '__main$foo' @@ -792,7 +792,7 @@ def baz(foo, x, y): def test_metastate_empty(self): self.eq_( ''' - def _() :any { def x := 1; return meta.getState() }() + def _() :Any { def x := 1; return meta.getState() }() ''', """ class _m__g_ignore1_Script(_monte.MonteObject): @@ -805,7 +805,7 @@ def run(_g_ignore1): _m___return = _monte.ejector("__return") try: x = _monte.wrap(1) - _m___return(_monte.Map(())) + _m___return.run(_monte.Map(())) _g_escape4 = _monte.null except _m___return._m_type, _g___return3: _g_escape4 = _g___return3.args[0] @@ -813,8 +813,8 @@ def run(_g_ignore1): _m___return.disable() return _g_ignore1._m_guardForMethod('run').coerce(_g_escape4, _monte.throw) - _g_ignore2 = _m__g_ignore1_Script({'run': _m_outerScope["any"]}) - _g_ignore2() + _g_ignore2 = _m__g_ignore1_Script({'run': _m_outerScope["Any"]}) + _g_ignore2.run() """) def test_metastate(self): @@ -866,7 +866,7 @@ def run(foo): _g_c4 = _monte.wrap(3) c = _monte.VarSlot(_monte.null, _g_c4, _monte.throw) boz = _m_boz_Script((_monte.FinalSlot(a, _monte.null, unsafe=True), _monte.FinalSlot.asType().get(_monte.null)), (b, _monte.VarSlot.asType().get(_monte.null))) - _m___return(boz) + _m___return.run(boz) _g_escape2 = _monte.null except _m___return._m_type, _g___return1: _g_escape2 = _g___return1.args[0] @@ -1009,7 +1009,7 @@ def test_bindingexpr_local(self): """ _g_x1 = _monte.wrap(1) x = _monte.VarSlot(_monte.null, _g_x1, _monte.throw) - _monte.Binding(_monte.VarSlot.asType().get(_monte.null), x) + _monte.Binding(x, _monte.VarSlot.asType().get(_monte.null)) """) def test_bindingexpr_frame(self): @@ -1044,14 +1044,14 @@ def baz(foo): def test_bindingpatt(self): self.eq_( ''' - def a :int := 1 + def a :Int := 1 def &&x := &&a x ''', """ - _g_guard1 = _m_outerScope["int"] + _g_guard1 = _m_outerScope["Int"] a = _g_guard1.coerce(_monte.wrap(1), _monte.throw) - x = _monte.Binding(_monte.FinalSlot.asType().get(_g_guard1), _monte.FinalSlot(a)) + x = _monte.Binding(_monte.FinalSlot(a), _monte.FinalSlot.asType().get(_g_guard1)) x.slot.get() """) diff --git a/monte/test/test_expander.py b/monte/test/test_expander.py index 830bddb..f37d24d 100644 --- a/monte/test/test_expander.py +++ b/monte/test/test_expander.py @@ -233,17 +233,17 @@ def test_matchbind(self): "run", [["NounExpr", "true"], ["BindingExpr", ["NounExpr", "y"]]]]]], - ["Catch", ["FinalPattern", ["NounExpr", "problem__4"], None], - ["SeqExpr", [["Def", ["ViaPattern", ["NounExpr", "__slotToBinding"], - ["BindingPattern", ["NounExpr", "b__5"]]], - None, - ["MethodCallExpr", ["NounExpr", "Ref"], - "broken", - [["NounExpr", "problem__4"]]]], - ["MethodCallExpr", ["NounExpr", "__makeList"], - "run", - [["NounExpr", "false"], - ["BindingExpr", ["NounExpr", "b__5"]]]]]]]]], + ["FinalPattern", ["NounExpr", "problem__4"], None], + ["SeqExpr", [["Def", ["ViaPattern", ["NounExpr", "__slotToBinding"], + ["BindingPattern", ["NounExpr", "b__5"]]], + None, + ["MethodCallExpr", ["NounExpr", "Ref"], + "broken", + [["NounExpr", "problem__4"]]]], + ["MethodCallExpr", ["NounExpr", "__makeList"], + "run", + [["NounExpr", "false"], + ["BindingExpr", ["NounExpr", "b__5"]]]]]]]], ["NounExpr", "ok__2"]]]) def test_suchThat(self): @@ -302,23 +302,22 @@ def test_matchbindScope(self): 'run', [['NounExpr', 'true'], ['BindingExpr', ['NounExpr', 'y']]]]]], - ['Catch', - ['FinalPattern', ['NounExpr', 'problem__4'], None], - ['SeqExpr', - [['Def', - ['ViaPattern', - ['NounExpr', '__slotToBinding'], - ['BindingPattern', ['NounExpr', 'b__5']]], - None, - ['MethodCallExpr', - ['NounExpr', 'Ref'], - 'broken', - [['NounExpr', 'problem__4']]]], + ['FinalPattern', ['NounExpr', 'problem__4'], None], + ['SeqExpr', + [['Def', + ['ViaPattern', + ['NounExpr', '__slotToBinding'], + ['BindingPattern', ['NounExpr', 'b__5']]], + None, ['MethodCallExpr', - ['NounExpr', '__makeList'], - 'run', - [['NounExpr', 'false'], - ['BindingExpr', ['NounExpr', 'b__5']]]]]]]]], + ['NounExpr', 'Ref'], + 'broken', + [['NounExpr', 'problem__4']]]], + ['MethodCallExpr', + ['NounExpr', '__makeList'], + 'run', + [['NounExpr', 'false'], + ['BindingExpr', ['NounExpr', 'b__5']]]]]]]], ['NounExpr', 'ok__2']]]]], ['IgnorePattern', None]]], None]], @@ -366,12 +365,12 @@ def test_for(self): ['NounExpr', 'value__3']], ['NounExpr', 'z'], ['NounExpr', 'null']]], - None]]]]], + None, None]]]]], []]]]], ['Assign', ['NounExpr', 'validFlag__1'], ['NounExpr', 'false']]], ['NounExpr', 'null']]], - None]) + None, None]) def test_forScope(self): self.assertRaises(ParseError, self.parse, "for foo in foo {}") @@ -496,15 +495,14 @@ def test_while(self): ["Script", None, [["Method", None, "run", [["IgnorePattern", None], ["IgnorePattern", None]], - ["NounExpr", "boolean"], + ["NounExpr", "Bool"], ["SeqExpr", [["Escape", ["FinalPattern", - ["NounExpr", "__continue"], - None], + ["NounExpr", "__continue"], None], ["NounExpr", "y"], - None], + None, None], ["NounExpr", "true"]]]]], - []]]]], None]) + []]]]], None, None]) def test_comparison(self): self.assertEqual( @@ -722,7 +720,7 @@ def test_to(self): ["SeqExpr", [["NounExpr", "x"], ["NounExpr", "null"]]], - None]]], + None, None]]], []]]) def test_method(self): @@ -759,7 +757,7 @@ def test_function(self): ["SeqExpr", [["NounExpr", "y"], ["NounExpr", "null"]]], - None], + None, None], ]], []]]) @@ -793,8 +791,8 @@ def test_switch(self): ["SeqExpr", [["Def", ["FinalPattern", ["NounExpr", "specimen__1"], None], None, ["NounExpr", "x"]], - ["Escape", ["FinalPattern", ["NounExpr", "ej__2"], None], - ["SeqExpr", + ["Escape", ["FinalPattern", ["NounExpr", "ej__2"], None], + ["SeqExpr", [["Def", ["ListPattern", [["FinalPattern", ["NounExpr", "a"], None], ["FinalPattern", ["NounExpr", "b"], None]], @@ -802,22 +800,22 @@ def test_switch(self): ["NounExpr", "ej__2"], ["NounExpr", "specimen__1"]], ["NounExpr", "c"]]], - ["Catch", ["FinalPattern", ["NounExpr", "failure__3"], None], - ["Escape", ["FinalPattern", ["NounExpr", "ej__4"], None], - ["SeqExpr", - [["Def", ["FinalPattern", ["NounExpr", "x"], None], - ["NounExpr", "ej__4"], - ["NounExpr", "specimen__1"]], + ["FinalPattern", ["NounExpr", "failure__3"], None], + ["Escape", ["FinalPattern", ["NounExpr", "ej__4"], None], + ["SeqExpr", + [["Def", ["FinalPattern", ["NounExpr", "x"], None], + ["NounExpr", "ej__4"], + ["NounExpr", "specimen__1"]], ["NounExpr", "y"]]], - ["Catch", ["FinalPattern", ["NounExpr", "failure__5"], None], + ["FinalPattern", ["NounExpr", "failure__5"], None], ["MethodCallExpr", ["NounExpr", "__switchFailed"], "run", - [["NounExpr", "specimen__1"], ["NounExpr", "failure__3"], ["NounExpr", "failure__5"]]]]]]]]]]) + [["NounExpr", "specimen__1"], ["NounExpr", "failure__3"], ["NounExpr", "failure__5"]]]]]]]]) def test_switch2(self): self.assertEqual( self.parse("switch (1) {match ==2 {'a'} match ==1 {'c'}}"), -["HideExpr", + ["HideExpr", ["SeqExpr", [["Def", ["FinalPattern", ["NounExpr", "specimen__1"], None], None, ["LiteralExpr", 1]], @@ -831,21 +829,21 @@ def test_switch2(self): ["NounExpr", "ej__2"], ["NounExpr", "specimen__1"]], ["LiteralExpr", ["Character", "a"]]]], - ["Catch", ["FinalPattern", ["NounExpr", "failure__3"], None], - ["Escape", ["FinalPattern", ["NounExpr", "ej__4"], None], - ["SeqExpr", - [["Def", ["ViaPattern", - ["MethodCallExpr", ["NounExpr", "__matchSame"], - "run", - [["LiteralExpr", 1]]], - ["IgnorePattern", None]], - ["NounExpr", "ej__4"], - ["NounExpr", "specimen__1"]], + ["FinalPattern", ["NounExpr", "failure__3"], None], + ["Escape", ["FinalPattern", ["NounExpr", "ej__4"], None], + ["SeqExpr", + [["Def", ["ViaPattern", + ["MethodCallExpr", ["NounExpr", "__matchSame"], + "run", + [["LiteralExpr", 1]]], + ["IgnorePattern", None]], + ["NounExpr", "ej__4"], + ["NounExpr", "specimen__1"]], ["LiteralExpr", ["Character", "c"]]]], - ["Catch", ["FinalPattern", ["NounExpr", "failure__5"], None], + ["FinalPattern", ["NounExpr", "failure__5"], None], ["MethodCallExpr", ["NounExpr", "__switchFailed"], "run", - [["NounExpr", "specimen__1"], ["NounExpr", "failure__3"], ["NounExpr", "failure__5"]]]]]]]]]]) + [["NounExpr", "specimen__1"], ["NounExpr", "failure__3"], ["NounExpr", "failure__5"]]]]]]]]) def test_interface(self): @@ -904,7 +902,7 @@ def test_interface(self): "run", [["LiteralExpr", "c"], ["NounExpr", "int"]]]]], - ["NounExpr", "void"]]]], + ["NounExpr", "Void"]]]], ["HideExpr", ["MethodCallExpr", ["NounExpr", "__makeMessageDesc"], @@ -917,7 +915,7 @@ def test_interface(self): ["NounExpr", "__makeParamDesc"], "run", [["LiteralExpr", "d"], - ["NounExpr", "any"]]]]], + ["NounExpr", "Any"]]]]], ["NounExpr", "float64"]]]] ]]]]]]) @@ -1014,22 +1012,23 @@ def test_when(self): def test_quasiexpr(self): - self.assertEqual(self.parse("`$x`"), - ['MethodCallExpr', - ['MethodCallExpr', - ['NounExpr', 'simple__quasiParser'], - 'valueMaker', [['LiteralExpr', '${0}']]], - 'substitute', - [['MethodCallExpr', ['NounExpr', '__makeList'], - 'run', [['NounExpr', 'x']]]]]) - self.assertEqual(self.parse("`($x)`"), + self.assertEqual(self.parse("`hello $x world`"), ['MethodCallExpr', - ['MethodCallExpr', - ['NounExpr', 'simple__quasiParser'], - 'valueMaker', [['LiteralExpr', '(${0})']]], - 'substitute', - [['MethodCallExpr', ['NounExpr', '__makeList'], - 'run', [['NounExpr', 'x']]]]]) + ['MethodCallExpr', + ['NounExpr', 'simple__quasiParser'], + 'valueMaker', [ + ["MethodCallExpr", + ["NounExpr", "__makeList"], + "run", + [['LiteralExpr', 'hello '], + ['MethodCallExpr', + ['NounExpr', 'simple__quasiParser'], + 'valueHole', + [['LiteralExpr', 0]]], + ['LiteralExpr', ' world']]]]], + 'substitute', + [['MethodCallExpr', ['NounExpr', '__makeList'], + 'run', [['NounExpr', 'x']]]]]) def test_quasipattern(self): self.assertEqual(self.parse("def foo`(@x)` := 1"), @@ -1040,7 +1039,15 @@ def test_quasipattern(self): "run", [['MethodCallExpr', ['NounExpr', 'foo__quasiParser'], - 'matchMaker', [['LiteralExpr', '(@{0})']]], + 'matchMaker', + [['MethodCallExpr', ['NounExpr', '__makeList'], + 'run', [ + ['LiteralExpr', '('], + ['MethodCallExpr', + ['NounExpr', 'foo__quasiParser'], + 'patternHole', + [['LiteralExpr', 0]]], + ['LiteralExpr', ')']]]]], ['MethodCallExpr', ['NounExpr', '__makeList'], 'run', []]]], ['ListPattern', [['FinalPattern', ['NounExpr', 'x'], None]], @@ -1057,10 +1064,23 @@ def test_quasicombo(self): "run", [['MethodCallExpr', ['NounExpr', 'foo__quasiParser'], - 'matchMaker', [['LiteralExpr', '(@{0}:${0})']]], - ['MethodCallExpr', ['NounExpr', '__makeList'], + 'matchMaker', [ + ['MethodCallExpr', ['NounExpr', '__makeList'], + 'run', [ + ['LiteralExpr', '('], + ['MethodCallExpr', + ['NounExpr', 'foo__quasiParser'], + 'patternHole', + [['LiteralExpr', 0]]], + ['LiteralExpr', ':'], + ['MethodCallExpr', + ['NounExpr', 'foo__quasiParser'], + 'valueHole', + [['LiteralExpr', 0]]], + ['LiteralExpr', ')']]]]], + ['MethodCallExpr', ['NounExpr', '__makeList'], 'run', [['NounExpr', 'y']]]]], ['ListPattern', [['FinalPattern', ['NounExpr', 'x'], None]], - None]], + None]], None, ['LiteralExpr', 1]]) diff --git a/monte/test/test_lexer.py b/monte/test/test_lexer.py index c6b8e36..6511f17 100644 --- a/monte/test/test_lexer.py +++ b/monte/test/test_lexer.py @@ -18,13 +18,32 @@ def test_ident(self): def test_char(self): self.assertEqual(lex("'z'"), [Term(Tag(".char."), "z", None, None)]) self.assertEqual(lex("'\\n'"), [Term(Tag(".char."), "\n", None, None)]) + self.assertEqual(lex("'\\U00008000'"),[Term(Tag(".char."), u'\U00008000', None, None)]) self.assertEqual(lex("'\\u0061'"), [Term(Tag(".char."), "a", None, None)]) + self.assertEqual(lex("'\\x61'"), [Term(Tag(".char."), "a", None, None)]) + self.assertEqual(lex("'\\\\'"), [Term(Tag(".char."), "\\", None, None)]) + self.assertEqual(lex("'\f'"), [Term(Tag(".char."), "\f", None, None)]) + self.assertEqual(lex("'\\''"), [Term(Tag(".char."), "'", None, None)]) + def test_string(self): self.assertEqual(lex('"foo\\\nbar"'), [Term(Tag(".String."), 'foobar', None, None)]) self.assertEqual(lex('"foo"'), [Term(Tag(".String."), 'foo', None, None)]) self.assertEqual(lex('"foo bar 9"'), [Term(Tag(".String."), 'foo bar 9', None, None)]) + self.assertEqual(lex('"a\\U00008000"'),[Term(Tag(".String."), u'a\U00008000', None, None)]) + self.assertEqual(lex('"\\ta\\U00008000"'),[Term(Tag(".String."), u"\ta\U00008000", None, None)]) + self.assertEqual(lex('"\\U00008000"'),[Term(Tag(".String."), u'\U00008000', None, None)]) + self.assertEqual(lex('"abc\\u0061"'), [Term(Tag(".String."), 'abca', None, None)]) + self.assertEqual(lex('"\\u0061 bc"'), [Term(Tag(".String."), 'a bc', None, None)]) + self.assertEqual(lex('"\\u0061"'), [Term(Tag(".String."), 'a', None, None)]) + self.assertEqual(lex('"\\x61"'), [Term(Tag(".String."), 'a', None, None)]) + self.assertEqual(lex('"\\x61\\tb"'), [Term(Tag(".String."), 'a\tb', None, None)]) + self.assertEqual(lex('"\\\n\\x61"'), [Term(Tag(".String."), 'a', None, None)]) + self.assertEqual(lex('"w\\x61t"'), [Term(Tag(".String."), 'wat', None, None)]) self.assertEqual(lex('"foo\\nbar"'), [Term(Tag(".String."), 'foo\nbar', None, None)]) + self.assertEqual(lex('"\\t\\n\\f\\r\\\\"'),[Term(Tag(".String."),'\t\n\f\r\\', None, None)]) + + def test_integer(self): self.assertEqual(lex('0'), [Term(Tag(".int."), 0, None, None)]) diff --git a/monte/test/test_monte.py b/monte/test/test_monte.py index 76ce32c..cd66500 100644 --- a/monte/test/test_monte.py +++ b/monte/test/test_monte.py @@ -1,3 +1,5 @@ +#import cProfile, lsprofcalltree +import json import os from zope.interface import implementer @@ -6,6 +8,7 @@ from twisted.trial.itrial import ITestCase import monte +from monte.runtime import load, audit from monte.runtime.load import TestCollector, buildPackage, eval as monte_eval from monte.runtime.scope import bootScope, createSafeScope from monte.runtime.tables import ConstMap @@ -37,7 +40,7 @@ def run(self, result): try: self.obj.run(self.asserts) except RuntimeError as e: - result.addFailure(self, failure.Failure(e)) + result.addFailure(self, failure.Failure()) else: result.addSuccess(self) result.stopTest(self) @@ -45,12 +48,25 @@ def run(self, result): def testSuite(): srcdir = os.path.join(os.path.dirname(monte.__file__), 'src') + parsecache = os.path.join(srcdir, "parse.cache") + if os.path.exists(parsecache): + with monte.TimeRecorder("loadCache"): + load.COMPILE_CACHE = json.load(open(parsecache)) safeScope = createSafeScope(bootScope) asserts = monte_eval(open(os.path.join(srcdir, "unittest.mt")).read(), safeScope) tests = [] c = TestCollector() pkg = buildPackage(srcdir, u"", safeScope, c) pkg.configure(None).load(ConstMap({})) - for (name, obj) in c.tests.d.items(): - tests.append(MonteTestCase(name.s, obj, asserts)) + testlist = sorted(c.tests.d.items()) + for (name, obj) in testlist: + tests.append(MonteTestCase(name.bare().s, obj, asserts)) + json.dump(load.COMPILE_CACHE, open(parsecache, 'w')) return unittest.TestSuite(tests) + +# def testSuite(): +# prof = cProfile.Profile() +# ts = prof.runcall(_testSuite) +# kc = lsprofcalltree.KCacheGrind(prof) +# kc.output(open("monte.cachegrind", 'w')) +# return ts diff --git a/monte/test/test_parser.py b/monte/test/test_parser.py index 25fd088..5b62cb8 100644 --- a/monte/test/test_parser.py +++ b/monte/test/test_parser.py @@ -121,7 +121,7 @@ def test_quasiliteralsBraces(self): Test that quasiliterals and braces don't smash each other up. """ parse = self.getParser("seq") - self.assertEqual(parse("{`${x}`}; 1"), ["SeqExpr", [["HideExpr", ["QuasiExpr", None, [["QuasiExprHole", ["NounExpr", "x"]], ["QuasiText", '']]]], ["LiteralExpr", 1]]]) + self.assertEqual(parse("{`${x}`}; 1"), ["SeqExpr", [["HideExpr", ["QuasiExpr", None, [["QuasiExprHole", ["NounExpr", "x"]]]]], ["LiteralExpr", 1]]]) def test_collections(self): """ @@ -433,8 +433,8 @@ def test_while(self): While expression. """ parse = self.getParser("blockExpr") - self.assertEqual(parse("while (true) {1}"), ["While", ["NounExpr", "true"], ["LiteralExpr", 1], None]) - self.assertEqual(parse("while (true):\n 1"), ["While", ["NounExpr", "true"], ["LiteralExpr", 1], None]) + self.assertEqual(parse("while (true) {1}"), ["While", ["NounExpr", "true"], ["LiteralExpr", 1], [None, None]]) + self.assertEqual(parse("while (true):\n 1"), ["While", ["NounExpr", "true"], ["LiteralExpr", 1], [None, None]]) def test_when(self): """ @@ -443,7 +443,7 @@ def test_when(self): parse = self.getParser("blockExpr") self.assertEqual(parse("when (d) -> {1}"), ["When", [["NounExpr", "d"]], ["LiteralExpr", 1], [], None]) self.assertEqual(parse("when (d) ->\n 1"), ["When", [["NounExpr", "d"]], ["LiteralExpr", 1], [], None]) - self.assertEqual(parse("when (d) ->\n 1\ncatch p:\n 2"), ["When", [["NounExpr", "d"]], ["LiteralExpr", 1], [["Catch", ["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]], None]) + self.assertEqual(parse("when (d) ->\n 1\ncatch p:\n 2"), ["When", [["NounExpr", "d"]], ["LiteralExpr", 1], [[["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]], None]) self.assertEqual(parse("when (d) -> {1} finally {3}"), ["When", [["NounExpr", "d"]], ["LiteralExpr", 1], [], ["LiteralExpr", 3]]) self.assertEqual(parse("when (d) ->\n 1\nfinally:\n 3"), ["When", [["NounExpr", "d"]], ["LiteralExpr", 1], [], ["LiteralExpr", 3]]) self.assertEqual(parse("when (e, d) -> {1}"), ["When", [["NounExpr", "e"], ["NounExpr", "d"]], ["LiteralExpr", 1], [], None]) @@ -467,12 +467,12 @@ def test_for(self): For-loop expression. """ parse = self.getParser("blockExpr") - self.assertEqual(parse("for k => v in x {}"), ["For", ["FinalPattern", ["NounExpr", "k"], None], ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["SeqExpr", []], None]) - self.assertEqual(parse("for k => v in x:\n pass"), ["For", ["FinalPattern", ["NounExpr", "k"], None], ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["SeqExpr", []], None]) - self.assertEqual(parse("for v in x {}"), ["For", None, ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["SeqExpr", []], None]) - self.assertEqual(parse("for v in x:\n pass"), ["For", None, ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["SeqExpr", []], None]) - self.assertEqual(parse("for v in x {1} catch p {2}"), ["For", None, ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["LiteralExpr", 1], ["Catch", ["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]]) - self.assertEqual(parse("for v in x:\n 1\ncatch p:\n 2"), ["For", None, ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["LiteralExpr", 1], ["Catch", ["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]]) + self.assertEqual(parse("for k => v in x {}"), ["For", ["FinalPattern", ["NounExpr", "k"], None], ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["SeqExpr", []], [None, None]]) + self.assertEqual(parse("for k => v in x:\n pass"), ["For", ["FinalPattern", ["NounExpr", "k"], None], ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["SeqExpr", []], [None, None]]) + self.assertEqual(parse("for v in x {}"), ["For", None, ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["SeqExpr", []], [None, None]]) + self.assertEqual(parse("for v in x:\n pass"), ["For", None, ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["SeqExpr", []], [None, None]]) + self.assertEqual(parse("for v in x {1} catch p {2}"), ["For", None, ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["LiteralExpr", 1], [["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]]) + self.assertEqual(parse("for v in x:\n 1\ncatch p:\n 2"), ["For", None, ["FinalPattern", ["NounExpr", "v"], None], ["NounExpr", "x"], ["LiteralExpr", 1], [["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]]) def test_if(self): """ @@ -490,10 +490,10 @@ def test_escape(self): Escape expression. """ parse = self.getParser("blockExpr") - self.assertEqual(parse("escape e {1}"), ["Escape", ["FinalPattern", ["NounExpr", "e"], None], ["LiteralExpr", 1], None]) - self.assertEqual(parse("escape e {1} catch p {2}"), ["Escape", ["FinalPattern", ["NounExpr", "e"], None], ["LiteralExpr", 1], ["Catch", ["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]]) - self.assertEqual(parse("escape e:\n 1"), ["Escape", ["FinalPattern", ["NounExpr", "e"], None], ["LiteralExpr", 1], None]) - self.assertEqual(parse("escape e:\n 1\ncatch p:\n 2"), ["Escape", ["FinalPattern", ["NounExpr", "e"], None], ["LiteralExpr", 1], ["Catch", ["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]]) + self.assertEqual(parse("escape e {1}"), ["Escape", ["FinalPattern", ["NounExpr", "e"], None], ["LiteralExpr", 1], None, None]) + self.assertEqual(parse("escape e {1} catch p {2}"), ["Escape", ["FinalPattern", ["NounExpr", "e"], None], ["LiteralExpr", 1], ["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]) + self.assertEqual(parse("escape e:\n 1"), ["Escape", ["FinalPattern", ["NounExpr", "e"], None], ["LiteralExpr", 1], None, None]) + self.assertEqual(parse("escape e:\n 1\ncatch p:\n 2"), ["Escape", ["FinalPattern", ["NounExpr", "e"], None], ["LiteralExpr", 1], ["FinalPattern", ["NounExpr", "p"], None], ["LiteralExpr", 2]]) def test_updoc(self): """ diff --git a/monte/test/test_runtime.py b/monte/test/test_runtime.py index e132ce4..797879a 100644 --- a/monte/test/test_runtime.py +++ b/monte/test/test_runtime.py @@ -2,7 +2,7 @@ from textwrap import dedent from monte.test import unittest from monte.runtime.base import MonteObject, toQuote -from monte.runtime.data import false, true, Integer, String +from monte.runtime.data import false, true, null, Integer, String, SourceSpan from monte.runtime.load import (PackageMangler, TestCollector, eval as _monte_eval, getModuleStructure) from monte.runtime.scope import bootScope @@ -126,7 +126,7 @@ def test_switch(self): self.assertRaises(RuntimeError, monte_eval, 'switch (2) { match ==0 { 1} match ==1 { 2}}') def test_coerce(self): - self.assertEqual(monte_eval('true :boolean'), true) + self.assertEqual(monte_eval('true :Bool'), true) def test_simple_quasiParser_value(self): self.assertEqual(monte_eval('def x := 42; `($x)`'), String(u"(42)")) @@ -143,6 +143,8 @@ def test_simple_quasiParser_pattern(self): self.assertEqual( monte_eval('def a := "baz"; def `foo @x$a` := "foo baz"; x'), String(u"")) + self.assertEqual(monte_eval('(if ("=>" =~ `@op=`) {op}) == null'), true) + self.assertEqual(monte_eval('(if ("+=" =~ `@op=`) {op}) == "+"'), true) def test_and(self): self.assertEqual(monte_eval("true && false"), false) @@ -154,7 +156,8 @@ def test_or(self): def test_binding(self): self.assertEqual(monte_eval("def x := 1; (&&x).get().get()"), Integer(1)) - + self.assertEqual(monte_eval("var x := 1; object foo {method baz(){ &&x }}; def &&a := foo.baz(); a"), Integer(1)) + def test_interface(self): self.assertEqual(monte_eval( "interface Foo { to doStuff(x)} ; object blee implements Foo {}; blee =~ _ :Foo"), @@ -317,7 +320,7 @@ def test_badReturn(self): monte_eval, dedent(""" object approver: - to audit(audition) :any: + to audit(audition) :Any: return 43 object auditSample implements approver {} __auditedBy(approver, auditSample) @@ -377,11 +380,11 @@ def test_doesNotGuard(self): """ def FinalSlot := __makeFinalSlot.asType() def x := 1 - object doesNotGuardX implements CheckGuard["x", FinalSlot[int]]: + object doesNotGuardX implements CheckGuard["x", FinalSlot[Int]]: to f(): return x """), self.scope) - self.assertEqual(str(err), "x: expected FinalSlot[int], got FinalSlot[any]") + self.assertEqual(str(err), "x: expected FinalSlot[Int], got FinalSlot[Any]") def test_doesNotMention(self): err = self.assertRaises( @@ -391,7 +394,7 @@ def test_doesNotMention(self): """ def FinalSlot := __makeFinalSlot.asType() def x := 1 - object doesNotMentionX implements CheckGuard["x", FinalSlot[int]]: + object doesNotMentionX implements CheckGuard["x", FinalSlot[Int]]: to f(): return 0 """), self.scope) @@ -401,8 +404,8 @@ def test_final(self): monte_eval(dedent( """ def FinalSlot := __makeFinalSlot.asType() - def x :int := 1 - object guardsX implements CheckGuard["x", FinalSlot[int]]: + def x :Int := 1 + object guardsX implements CheckGuard["x", FinalSlot[Int]]: to f(): return x """), self.scope) @@ -412,7 +415,7 @@ def test_var(self): """ def VarSlot := __makeVarSlot.asType() var x := 1 - object guardsX implements CheckGuard["x", VarSlot[any]]: + object guardsX implements CheckGuard["x", VarSlot[Any]]: to f(): return x """), self.scope) @@ -421,8 +424,8 @@ def test_guardedVar(self): monte_eval(dedent( """ def VarSlot := __makeVarSlot.asType() - var x :int := 1 - object guardsX implements CheckGuard["x", VarSlot[int]]: + var x :Int := 1 + object guardsX implements CheckGuard["x", VarSlot[Int]]: to f(): return x """), self.scope) @@ -432,7 +435,7 @@ def test_objectFinal(self): """ def FinalSlot := __makeFinalSlot.asType() object x {} - object guardsX implements CheckGuard["x", FinalSlot[any]]: + object guardsX implements CheckGuard["x", FinalSlot[Any]]: to f(): return x """), self.scope) @@ -455,7 +458,7 @@ def test_slot(self): """ def s := __makeFinalSlot(1) def &x := s - object guardsX implements CheckGuard["x", any]: + object guardsX implements CheckGuard["x", Any]: to f(): return x """), self.scope) @@ -464,7 +467,7 @@ def test_guardedSlot(self): monte_eval(dedent( """ def s := __makeFinalSlot(1) - object g extends any {} + object g extends Any {} def &x :g := s object guardsX implements CheckGuard["x", g]: to f(): @@ -563,10 +566,6 @@ def test_size(self): self.assertEqual(monte_eval("def x := [1, 2, 3].diverge(); x.size()"), Integer(3)) - def test_add(self): - self.assertEqual(monte_eval("([1, 2].diverge() + [3, 4].diverge()).snapshot() == [1, 2, 3, 4]"), - true) - def test_contains(self): self.assertEqual(monte_eval("[1, 2, 3].diverge().contains(2)"), true) @@ -661,25 +660,25 @@ def test_valueGuard(self): self.assertRaises( RuntimeError, monte_eval, - "def x := [1, 4, 3, 'b'].diverge(int)") + "def x := [1, 4, 3, 'b'].diverge(Int)") self.assertEqual(monte_eval( - "def x := [1, 4, 3].diverge(int); x[1] := 5; x.snapshot() == [1, 5, 3]"), + "def x := [1, 4, 3].diverge(Int); x[1] := 5; x.snapshot() == [1, 5, 3]"), true) self.assertEqual(monte_eval( - "def x := [1, 4, 3].diverge(int); x.push(5); x.snapshot() == [1, 4, 3, 5]"), + "def x := [1, 4, 3].diverge(Int); x.push(5); x.snapshot() == [1, 4, 3, 5]"), true) self.assertRaises( RuntimeError, monte_eval, - "def x := [1, 4, 3].diverge(int); x[1] := 'b'") + "def x := [1, 4, 3].diverge(Int); x[1] := 'b'") self.assertRaises( RuntimeError, monte_eval, - "def x := [1, 4, 3].diverge(int); x.push('b')") + "def x := [1, 4, 3].diverge(Int); x.push('b')") class ConstMapTests(unittest.TestCase): @@ -773,11 +772,6 @@ def test_size(self): self.assertEqual(monte_eval( "def x := [1 => 3, 4 => 7].diverge(); x.size()"), Integer(2)) - def test_or(self): - self.assertEqual(monte_eval( - "def x := [1 => 3, 4 => 7].diverge() | [4 => 6, 2 => 'b']; x == [4 => 7, 2 => 'b', 1 => 3]"), - true) - def test_and(self): self.assertEqual(monte_eval( "def x := [1 => 3, 4 => 7].diverge() & [4 => 6, 2 => 'b']; x == [4 => 7]"), @@ -851,26 +845,26 @@ def test_valueGuard(self): self.assertRaises( RuntimeError, monte_eval, - "def x := [1 => 4, 3 => 'b'].diverge(int, char)") + "def x := [1 => 4, 3 => 'b'].diverge(Int, Char)") self.assertRaises( RuntimeError, monte_eval, - "def x := [3.5 => 'a', 3 => 'b'].diverge(int, char)") + "def x := [3.5 => 'a', 3 => 'b'].diverge(Int, Char)") self.assertEqual(monte_eval( - "def x := [4 => 'a', 3 => 'b'].diverge(int, char); x[5] := 'c'; x.snapshot() == [4 => 'a', 3 => 'b', 5 => 'c']"), + "def x := [4 => 'a', 3 => 'b'].diverge(Int, Char); x[5] := 'c'; x.snapshot() == [4 => 'a', 3 => 'b', 5 => 'c']"), true) self.assertRaises( RuntimeError, monte_eval, - "def x := [4 => 'a', 3 => 'b'].diverge(int, char); x[4.5] := 'c'") + "def x := [4 => 'a', 3 => 'b'].diverge(Int, Char); x[4.5] := 'c'") self.assertRaises( RuntimeError, monte_eval, - "def x := [4 => 'a', 3 => 'b'].diverge(int, char); x[5] := 9") + "def x := [4 => 'a', 3 => 'b'].diverge(Int, Char); x[5] := 9") class ListGuardTests(unittest.TestCase): @@ -884,13 +878,13 @@ def test_plainConst(self): def test_guardConst(self): self.assertEqual(monte_eval( - "escape e {def x :List[int] exit e := [1 => 2]; 1} catch v {2}"), + "escape e {def x :List[Int] exit e := [1 => 2]; 1} catch v {2}"), Integer(2)) self.assertEqual(monte_eval( - "escape e {def x :List[int] exit e := [3, 'b']; 1} catch v {2}"), + "escape e {def x :List[Int] exit e := [3, 'b']; 1} catch v {2}"), Integer(2)) self.assertEqual(monte_eval( - "escape e {def x :List[int] exit e := [3, 4]; 1} catch v {2}"), + "escape e {def x :List[Int] exit e := [3, 4]; 1} catch v {2}"), Integer(1)) def test_var(self): @@ -898,7 +892,7 @@ def test_var(self): "escape e {def x :List exit e := [3, 'b'].diverge(); 1} catch v {2}"), Integer(2)) self.assertEqual(monte_eval( - "escape e {def x :List[int] exit e := [3, 4].diverge(); 1} catch v {2}"), + "escape e {def x :List[Int] exit e := [3, 4].diverge(); 1} catch v {2}"), Integer(2)) @@ -913,16 +907,16 @@ def test_plainConst(self): def test_guardConst(self): self.assertEqual(monte_eval( - "escape e {def x :Map[int, char] exit e := [1 => 'b', 2 => 'x']; 1} catch v {2}"), + "escape e {def x :Map[Int, Char] exit e := [1 => 'b', 2 => 'x']; 1} catch v {2}"), Integer(1)) self.assertEqual(monte_eval( - "escape e {def x :Map[int, char] exit e := [1 => 2, 3 => 'x']; 1} catch v {2}"), + "escape e {def x :Map[Int, Char] exit e := [1 => 2, 3 => 'x']; 1} catch v {2}"), Integer(2)) self.assertEqual(monte_eval( - "escape e {def x :Map[int, char] exit e := ['a' => 'b']; 1} catch v {2}"), + "escape e {def x :Map[Int, Char] exit e := ['a' => 'b']; 1} catch v {2}"), Integer(2)) self.assertEqual(monte_eval( - "escape e {def x :Map[int, char] exit e := 3; 1} catch v {2}"), + "escape e {def x :Map[Int, Char] exit e := 3; 1} catch v {2}"), Integer(2)) def test_var(self): @@ -930,7 +924,7 @@ def test_var(self): "escape e {def x :Map exit e := [3 => 'b'].diverge(); 1} catch v {2}"), Integer(2)) self.assertEqual(monte_eval( - "escape e {def x :Map[int, char] exit e := [3 => 'b'].diverge(); 1} catch v {2}"), + "escape e {def x :Map[Int, Char] exit e := [3 => 'b'].diverge(); 1} catch v {2}"), Integer(2)) class SameGuardTests(unittest.TestCase): @@ -948,25 +942,25 @@ def test_sameySame(self): class SubrangeGuardTests(unittest.TestCase): def test_deepFrozen(self): - self.assertEqual(monte_eval("SubrangeGuard[int] =~ _ :DeepFrozen"), true) - self.assertEqual(monte_eval("Selfless.passes(SubrangeGuard[int])"), true) + self.assertEqual(monte_eval("SubrangeGuard[Int] =~ _ :DeepFrozen"), true) + self.assertEqual(monte_eval("Selfless.passes(SubrangeGuard[Int])"), true) def test_fail(self): - self.assertEqual(str(monte_eval("try {object x implements SubrangeGuard[int] {}} catch p {p}")), + self.assertEqual(str(monte_eval("try {object x implements SubrangeGuard[Int] {}} catch p {p}")), "__main$x has no `coerce` method") - self.assertEqual(str(monte_eval("try {object x implements SubrangeGuard[int] { to coerce(a, b) {return a}}} catch p {p}")), + self.assertEqual(str(monte_eval("try {object x implements SubrangeGuard[Int] { to coerce(a, b) {return a}}} catch p {p}")), "__main$x does not have a noun as its `coerce` result guard") - self.assertEqual(str(monte_eval("object z as DeepFrozen {}; try {object x implements SubrangeGuard[int] { to coerce(a, b) :z {return a}}} catch p {p}")), + self.assertEqual(str(monte_eval("object z as DeepFrozen {}; try {object x implements SubrangeGuard[Int] { to coerce(a, b) :z {return a}}} catch p {p}")), "__main$x does not have a determinable result guard, but <& z> :FinalSlot[]") - self.assertEqual(str(monte_eval("try {object x implements SubrangeGuard[int] { to coerce(a, b) :boolean {return a}}} catch p {p}")), - "__main$x does not have a result guard implying int, but boolean") - self.assertEqual(str(monte_eval("try {object x implements SubrangeGuard[int] { to coerce(a, b) :(1 + 1) {return a}}} catch p {p}")), + self.assertEqual(str(monte_eval("try {object x implements SubrangeGuard[Int] { to coerce(a, b) :Bool {return a}}} catch p {p}")), + "__main$x does not have a result guard implying Int, but Bool") + self.assertEqual(str(monte_eval("try {object x implements SubrangeGuard[Int] { to coerce(a, b) :(1 + 1) {return a}}} catch p {p}")), "__main$x does not have a noun as its `coerce` result guard") def test_success(self): - self.assertEqual(monte_eval("object x implements SubrangeGuard[int] { to coerce(a, b) :int {return 42}};def y :x := 3; y"), Integer(42)) - self.assertEqual(monte_eval("object x implements SubrangeGuard[DeepFrozen] { to coerce(a, b) :int {return 42}}; def y :x := 3; y"), Integer(42)) + self.assertEqual(monte_eval("object x implements SubrangeGuard[Int] { to coerce(a, b) :Int {return 42}};def y :x := 3; y"), Integer(42)) + self.assertEqual(monte_eval("object x implements SubrangeGuard[DeepFrozen] { to coerce(a, b) :Int {return 42}}; def y :x := 3; y"), Integer(42)) class DeepFrozenGuardTests(unittest.TestCase): @@ -990,7 +984,7 @@ def test_audited(self): self.assertEqual(monte_eval(dedent(""" var w := 0 def x := 1 - def y :int := 1 + def y :Int := 1 object foo implements DeepFrozen: to doStuff(z): return y + z @@ -1007,7 +1001,7 @@ def y :int := 1 foo =~ _ :DeepFrozen """)), true) self.assertEqual(monte_eval(dedent(""" - def baz :List[int] := [] + def baz :List[Int] := [] object foo implements DeepFrozen: to doStuff(z): return baz @@ -1015,7 +1009,7 @@ def baz :List[int] := [] """)), true) self.assertEqual(monte_eval(dedent(""" - def blee :int := 3 + def blee :Int := 3 def baz :Same[blee] := blee object foo implements DeepFrozen: to doStuff(z): @@ -1030,8 +1024,8 @@ def test_rejected(self): RuntimeError, monte_eval, dedent(""" - var x :int := 0 - def y :int := 1 + var x :Int := 0 + def y :Int := 1 object foo implements DeepFrozen: to doStuff(z): return x + y @@ -1064,15 +1058,15 @@ def y :int := 1 class IntegerGuardTests(unittest.TestCase): def test_type(self): - monte_eval('def x :int := 1') - self.assertRaises(RuntimeError, monte_eval, 'def x :int := "foo"') + monte_eval('def x :Int := 1') + self.assertRaises(RuntimeError, monte_eval, 'def x :Int := "foo"') class FloatGuardTests(unittest.TestCase): def test_type(self): - monte_eval('def x :float := 1.0') - self.assertRaises(RuntimeError, monte_eval, 'def x :float := "foo"') + monte_eval('def x :Double := 1.0') + self.assertRaises(RuntimeError, monte_eval, 'def x :Double := "foo"') class TransparentGuardTests(unittest.TestCase): def test_reject(self): @@ -1129,7 +1123,174 @@ def boz := 2 """)), true) -class StringTests(unittest.TestCase): +class _TwineTests(object): + + def test_endsWith(self): + self.assertEqual(monte_eval('x.endsWith("blee")', + x=self.makeString(u'foo blee')), + true) + self.assertEqual(monte_eval('x.endsWith("blue")', + x=self.makeString(u'foo blee')), + false) + + def test_startsWith(self): + self.assertEqual(monte_eval('x.startsWith("foo")', + x=self.makeString(u'foo blee')), + true) + self.assertEqual(monte_eval('x.startsWith("fu")', + x=self.makeString(u'foo blee')), + false) + + def test_add(self): + s = self.makeString(u'foo ') + t = self.makeString2(u'blee') + st = s.add(t) + self.assertEqual(st.bare().s, u'foo blee') + self.assertEqual(st.slice(Integer(0), Integer(4)).getSpan(), + s.getSpan()) + self.assertEqual(st.slice(Integer(4), Integer(8)).getSpan(), + t.getSpan()) + + def test_join(self): + s = self.makeString(u'foo ') + t = self.makeString2(u'blee') + st = String(u", ").join(ConstList([s, t])) + self.assertEqual(st.bare().s, u'foo , blee') + self.assertEqual(st.slice(Integer(0), Integer(4)).getSpan(), + s.getSpan()) + self.assertEqual(st.slice(Integer(6), Integer(10)).getSpan(), + t.getSpan()) + + def test_singleLineAsFrom(self): + s = self.makeString(u'foo blee').asFrom( + String(u'test'), Integer(3), Integer(4)) + self.assertEqual(s.bare().s, u'foo blee') + span = s.getSpan() + self.assertEqual(span._isOneToOne, true) + self.assertEqual(span.startLine.n, 3) + self.assertEqual(span.startCol.n, 4) + self.assertEqual(span.endLine.n, 3) + self.assertEqual(span.endCol.n, 11) + + def test_multiLineAsFrom(self): + s = self.makeString(u'foo blee\nbar baz').asFrom( + String(u'test'), Integer(3), Integer(4)) + self.assertEqual(s.bare().s, u'foo blee\nbar baz') + span = s.getSpan() + self.assertEqual(span._isOneToOne, false) + self.assertEqual(span.startLine.n, 3) + self.assertEqual(span.startCol.n, 4) + self.assertEqual(span.endLine.n, 4) + self.assertEqual(span.endCol.n, 6) + + parts = s.getParts() + self.assertEqual(parts.size().n, 2) + first, second = parts.l + self.assertEqual(first.bare().s, u'foo blee\n') + self.assertEqual(second.bare().s, u'bar baz') + span1 = first.getSpan() + self.assertEqual(span1._isOneToOne, true) + self.assertEqual(span1.startLine.n, 3) + self.assertEqual(span1.startCol.n, 4) + self.assertEqual(span1.endLine.n, 3) + self.assertEqual(span1.endCol.n, 12) + + span2 = second.getSpan() + self.assertEqual(span2._isOneToOne, true) + self.assertEqual(span2.startLine.n, 4) + self.assertEqual(span2.startCol.n, 0) + self.assertEqual(span2.endLine.n, 4) + self.assertEqual(span2.endCol.n, 6) + + def test_infect_base(self): + s = self.makeString(u"foo") + self.assertRaises(RuntimeError, s.infect, Integer(0)) + self.assertRaises(RuntimeError, s.infect, String(u"xy"), true) + + def test_cmp(self): + a = self.makeString(u'aaa') + b = self.makeString(u'aab') + self.assertEqual(a.op__cmp(b), Integer(-1)) + self.assertEqual(a.op__cmp(a), Integer(0)) + self.assertEqual(b.op__cmp(a), Integer(1)) + + def test_multiply(self): + s = self.makeString(u'ab') + self.assertEqual(s.multiply(Integer(3)).bare().s, + u'ababab') + + def test_quote(self): + s = self.makeString(u'foo\xa0baz') + q = s.quote() + self.assertEqual(q.bare().s, u'"foo\\u00a0baz"') + self.assertEqual(q.slice(Integer(0), Integer(1)).getSpan(), + null) + self.assertEqual(q.slice(Integer(10), Integer(14)).getSpan(), + null) + self.assertEqual(q.slice(Integer(1), Integer(9)).getSpan(), + s.slice(Integer(0), Integer(4)).getSpan().notOneToOne()) + self.assertEqual(q.slice(Integer(10), Integer(13)).getSpan(), + s.slice(Integer(4), Integer(7)).getSpan()) + + def test_split(self): + s = self.makeString(u'foo, bar, baz') + x = s.split(String(u', ')) + self.assertIsInstance(x, ConstList) + self.assertEqual([t.bare().s for t in x.l], + [u'foo', u'bar', u'baz']) + self.assertEqual([t.getSpan() for t in x.l], + [s.slice(Integer(i), Integer(j)).getSpan() + for (i, j) in + [(0, 3), (5, 8), (10, 13)]]) + + def test_upcase(self): + s = self.makeString(u'Foo') + u = s.toUpperCase() + self.assertEqual(u.bare().s, u'FOO') + self.assertEqual(u.getSpan(), s.getSpan()) + + def test_downcase(self): + s = self.makeString(u'Foo') + u = s.toLowerCase() + self.assertEqual(u.bare().s, u'foo') + self.assertEqual(u.getSpan(), s.getSpan()) + + def test_size(self): + s = self.makeString(u'foo baz') + self.assertEqual(s.size(), Integer(7)) + + +class StringTests(_TwineTests, unittest.TestCase): + + def makeString(self, s): + return String(s) + + makeString2 = makeString + + def test_getPartAt(self): + s = String(u"foo") + self.assertEqual(s.getPartAt(Integer(0)), + ConstList([Integer(0), Integer(0)])) + self.assertEqual(s.getPartAt(Integer(2)), + ConstList([Integer(0), Integer(2)])) + + def test_getSourceMap(self): + s = String(u"foo") + self.assertEqual(s.getSourceMap(), ConstMap({})) + + def test_infect(self): + s = String(u"foo") + t = String(u"baz") + self.assertEqual(t, s.infect(t)) + + def test_quote(self): + s = self.makeString(u'foo\xa0baz') + q = s.quote() + self.assertEqual(q.bare().s, u'"foo\\u00a0baz"') + self.assertEqual(q.slice(Integer(0), Integer(1)).getSpan(), null) + self.assertEqual(q.slice(Integer(10), Integer(14)).getSpan(), null) + self.assertEqual(q.slice(Integer(1), Integer(9)).getSpan(), null) + self.assertEqual(q.slice(Integer(10), Integer(13)).getSpan(), null) def test_slice(self): self.assertEqual(monte_eval(dedent(""" @@ -1137,6 +1298,162 @@ def x := "abcd" x.slice(1) == "bcd" """)), true) + def test_replace(self): + s = self.makeString(u'foo blee boz') + s2 = self.makeString2(u'baz') + t = s.replace(String(u'blee'), s2) + self.assertEqual(t.bare().s, u'foo baz boz') + self.assertEqual(t.slice(Integer(0), Integer(4)).getSpan(), null) + self.assertEqual(t.slice(Integer(4), Integer(7)).getSpan(), null) + self.assertEqual(t.slice(Integer(7), Integer(11)).getSpan(), null) + + +class LocatedTwineTests(_TwineTests, unittest.TestCase): + + def makeString(self, s): + return String(s).asFrom(String(u'test string'), Integer(1), Integer(0)) + + def makeString2(self, s): + return String(s).asFrom(String(u'test string 2'), + Integer(1), Integer(0)) + + def test_getPartAt(self): + s = self.makeString(u'foo') + self.assertEqual(s.getPartAt(Integer(0)), + ConstList([Integer(0), Integer(0)])) + self.assertEqual(s.getPartAt(Integer(2)), + ConstList([Integer(0), Integer(2)])) + + def test_getSourceMap(self): + s = self.makeString(u'foo') + self.assertEqual( + s.getSourceMap(), + ConstMap({ConstList([Integer(0), Integer(3)]): s.getSpan()})) + + def test_infect(self): + s = self.makeString(u'foo') + t = self.makeString2(u'baz') + u = s.infect(t) + self.assertEqual(t.bare().s, u.bare().s) + self.assertEqual(s.getSpan().notOneToOne(), u.getSpan()) + + def test_replace(self): + s = self.makeString(u'foo blee boz') + s2 = self.makeString2(u'baz') + t = s.replace(String(u'blee'), s2) + self.assertEqual(t.bare().s, u'foo baz boz') + self.assertEqual(t.slice(Integer(0), Integer(4)).getSpan(), + s.slice(Integer(0), Integer(4)).getSpan()) + self.assertEqual(t.slice(Integer(4), Integer(7)).getSpan(), + SourceSpan(String(u'test string'), false, + Integer(1), Integer(4), + Integer(1), Integer(7))) + self.assertEqual(t.slice(Integer(8), Integer(11)).getSpan(), + s.slice(Integer(9), Integer(12)).getSpan()) + + def test_slice(self): + s = self.makeString(u'abcd') + t = s.slice(Integer(1)) + self.assertEqual(t.bare().s, s.bare().s[1:]) + self.assertEqual(t.getSpan(), + SourceSpan(String(u'test string'), + true, + Integer(1), Integer(1), + Integer(1), Integer(3))) + +class CompositeTwineTests(_TwineTests, unittest.TestCase): + + def makeString(self, s): + left = String(s[:-1]).asFrom(String(u'test string'), + Integer(1), Integer(0)) + right = String(s[-1:]).asFrom(String(u'test string'), + Integer(2), Integer(0)) + return left.add(right) + + def makeString2(self, s): + left = String(s[:-1]).asFrom(String(u'test string 2'), + Integer(1), Integer(0)) + right = String(s[-1:]).asFrom(String(u'test string 2'), + Integer(2), Integer(0)) + return left.add(right) + + def test_getPartAt(self): + s = self.makeString(u'foo') + self.assertEqual(s.getPartAt(Integer(0)), + ConstList([Integer(0), Integer(0)])) + self.assertEqual(s.getPartAt(Integer(1)), + ConstList([Integer(0), Integer(1)])) + self.assertEqual(s.getPartAt(Integer(2)), + ConstList([Integer(1), Integer(0)])) + + def test_getSourceMap(self): + s = self.makeString(u'foo baz') + k1 = ConstList([Integer(0), Integer(6)]) + k2 = ConstList([Integer(6), Integer(7)]) + self.assertEqual( + s.getSourceMap(), + ConstMap( + {k1: + SourceSpan(String(u'test string'), true, + Integer(1), Integer(0), + Integer(1), Integer(5)), + k2: + SourceSpan(String(u'test string'), true, + Integer(2), Integer(0), + Integer(2), Integer(0)), + }, [k1, k2])) + + def test_add_merge(self): + s = String(u'baz\nfoo').asFrom(String(u'test.txt'), + Integer(1), Integer(0)) + s2 = String(u'bar\nblee').asFrom(String(u'test.txt'), + Integer(2), Integer(3)) + parts = s.add(s2).getParts() + self.assertEqual(len(parts.l), 3) + self.assertEqual(parts.l[0].s, u'baz\n') + self.assertEqual(parts.l[1].s, u'foobar\n') + self.assertEqual(parts.l[1].getSpan(), + SourceSpan(String(u'test.txt'), true, + Integer(2), Integer(0), + Integer(2), Integer(6))) + self.assertEqual(parts.l[2].s, u'blee') + + def test_add_half_bare(self): + s = String(u'baz\nfoo').asFrom(String(u'test.txt'), + Integer(1), Integer(0)) + s2 = String(u'bar\nblee') + parts = s.add(s2).getParts() + self.assertEqual(len(parts.l), 3) + self.assertEqual(parts.l[0].s, u'baz\n') + self.assertEqual(parts.l[1].s, u'foo') + self.assertEqual(parts.l[2].s, u'bar\nblee') + + def test_infect(self): + s = self.makeString(u'foo\nblee') + t = self.makeString2(u'baz\nboz') + u = s.infect(t) + self.assertEqual(t.bare().s, u.bare().s) + self.assertEqual(s.getSpan().notOneToOne(), u.getSpan()) + + def test_replace(self): + s = String(u'foo\nblee boz').asFrom("test string") + s2 = String(u'foo\nbaz b') + t = s.replace(String(u'foo\nb'), s2) + self.assertEqual(t.bare().s, u'foo\nbaz blee boz') + self.assertEqual(t.slice(Integer(0), Integer(9)).getSpan(), + s.slice(Integer(0), Integer(5)).getSpan()) + self.assertEqual(t.slice(Integer(9), Integer(16)).getSpan(), + s.slice(Integer(5), Integer(12)).getSpan()) + + def test_slice(self): + s = self.makeString(u'abcd') + t = s.slice(Integer(1)) + self.assertEqual(t.bare().s, s.bare().s[1:]) + self.assertEqual(t.getSpan(), + SourceSpan(String(u'test string'), + false, + Integer(1), Integer(1), + Integer(2), Integer(0))) class RefTests(unittest.TestCase): def test_print(self): @@ -1188,12 +1505,12 @@ def test_loadFromSubdir(self): def test_require(self): pkg = PackageMangler("test", MODULE_TEST_DIR, bootScope, None) m = pkg.require(String(u'b')) - self.assertEqual(m.requires, ['b']) + self.assertEqual(m.requires, set(['b'])) def test_makeModuleEmpty(self): pkg = PackageMangler("test", MODULE_TEST_DIR, bootScope, None) mod = pkg.makeModule(ConstMap({})) - self.assertEqual(mod.imports, []) + self.assertEqual(mod.imports, set([])) self.assertEqual(mod.exports, []) def test_makeModuleTree(self): @@ -1202,13 +1519,13 @@ def test_makeModuleTree(self): modB = pkg.makeModule(ConstMap({})) modC = pkg.makeModule(ConstMap({String(u'a'): modA, String(u'b'): modB}, [String(u'a'), String(u'b')])) - self.assertEqual(modC.imports, []) + self.assertEqual(modC.imports, set([])) self.assertEqual(modC.exports, ['a', 'b']) def test_makeModuleRequire(self): pkg = PackageMangler("test", MODULE_TEST_DIR, bootScope, None) mod = pkg.makeModule(ConstMap({String(u'a'): pkg.require(String(u'b'))})) - self.assertEqual(mod.imports, ['b']) + self.assertEqual(mod.imports, set(['b'])) self.assertEqual(mod.exports, ['a']) def test_testCollectorCollects(self):