From f429a4de5c003578b40675621e63d152ef3061da Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 28 May 2019 18:02:56 -0400 Subject: [PATCH 01/84] Add grammar --- src/js/base/pyret-grammar.bnf | 8 ++++++++ t.arr | 1 + 2 files changed, 9 insertions(+) create mode 100644 t.arr diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index a94248e28c..1c41b9367a 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -130,6 +130,14 @@ id-expr: NAME prim-expr: num-expr | frac-expr | rfrac-expr | bool-expr | string-expr +dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) | (LANGLE|LT) NUMBER (RANGLE|GT) +unit-expr: (PARENNOSPACE|PARENAFTERBRACE) unit-expr | RPAREN + | unit-expr CARET NUMBER + | unit-expr TIMES unit-expr + | unit-expr SLASH unit-expr + | id-expr + + num-expr: NUMBER frac-expr: RATIONAL rfrac-expr: ROUGHRATIONAL diff --git a/t.arr b/t.arr new file mode 100644 index 0000000000..4ebf01727b --- /dev/null +++ b/t.arr @@ -0,0 +1 @@ +1 + 1 From 05b3a9ccfe75ec4b59c794ed6b6cb631c8f92410 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 28 May 2019 18:13:29 -0400 Subject: [PATCH 02/84] Get a number syntax that parses --- src/js/base/pyret-grammar.bnf | 2 +- t.arr | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index 1c41b9367a..bd02b249bc 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -138,7 +138,7 @@ unit-expr: (PARENNOSPACE|PARENAFTERBRACE) unit-expr | RPAREN | id-expr -num-expr: NUMBER +num-expr: NUMBER [PERCENT dim-expr] frac-expr: RATIONAL rfrac-expr: ROUGHRATIONAL bool-expr: TRUE | FALSE diff --git a/t.arr b/t.arr index 4ebf01727b..6417b50c96 100644 --- a/t.arr +++ b/t.arr @@ -1 +1,3 @@ -1 + 1 +var result = 1% + 1% + +print(result) From cb2f411231a83820c59bc0f9e7c846ca78f4a81c Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 28 May 2019 18:17:58 -0400 Subject: [PATCH 03/84] Update AST, currently getting parse error --- src/arr/trove/ast.arr | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index ef34d96d8e..45ab2257e3 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -928,13 +928,13 @@ data Expr: | s-srcloc(l :: Loc, loc :: Loc) with: method label(self): "s-srcloc" end, method tosource(self): PP.str(torepr(self.loc)) end - | s-num(l :: Loc, n :: Number) with: + | s-num(l :: Loc, n :: Number, u :: Option) with: method label(self): "s-num" end, method tosource(self): PP.number(self.n) end - | s-frac(l :: Loc, num :: NumInteger, den :: NumInteger) with: + | s-frac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option) with: method label(self): "s-frac" end, method tosource(self): PP.number(self.num) + PP.str("/") + PP.number(self.den) end - | s-rfrac(l :: Loc, num :: NumInteger, den :: NumInteger) with: + | s-rfrac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option) with: method label(self): "s-rfrac" end, method tosource(self): PP.str("~") + PP.number(self.num) + PP.str("/") + PP.number(self.den) end | s-bool(l :: Loc, b :: Boolean) with: @@ -1625,6 +1625,16 @@ sharing: end end +data Unit: + | u-one(l :: Loc) + | u-base(l :: Loc, id :: Name) + | u-mul(l :: Loc, lhs :: Unit, rhs :: Unit) + | u-div(l :: Loc, lhs :: Unit, rhs :: Unit) + | u-pow(l :: Loc, u :: Unit, n :: NumInteger) + | u-paren(l :: Loc, u :: Unit) +end + + data Ann: | a-blank with: method label(self): "a-blank" end, From 2907a0eebec276401eaf377907302338a255a91e Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 28 May 2019 23:30:41 -0400 Subject: [PATCH 04/84] Update s-num matches to work (ignoring units) --- pitometer/programs/ast.arr | 10 +++--- src/arr/compiler/anf.arr | 2 +- src/arr/compiler/desugar.arr | 10 +++--- src/arr/compiler/resolve-scope.arr | 36 +++++++++---------- src/arr/compiler/type-check.arr | 4 +-- src/arr/compiler/well-formed.arr | 2 +- src/arr/trove/ast.arr | 10 +++--- src/js/base/pyret-grammar.bnf | 2 +- src/js/trove/parse-pyret.js | 16 +++++++-- .../regression/parens-after-comments.arr | 4 +-- .../regression/pretty-print-instantiate.arr | 2 +- 11 files changed, 55 insertions(+), 43 deletions(-) diff --git a/pitometer/programs/ast.arr b/pitometer/programs/ast.arr index 22c3f05c6a..bab26a52a5 100644 --- a/pitometer/programs/ast.arr +++ b/pitometer/programs/ast.arr @@ -891,7 +891,7 @@ data Expr: | s-srcloc(l :: Loc, loc :: Loc) with: method label(self): "s-srcloc" end, method tosource(self): PP.str(torepr(self.loc)) end - | s-num(l :: Loc, n :: Number) with: + | s-num(l :: Loc, n :: Number, u :: Option) with: method label(self): "s-num" end, method tosource(self): PP.number(self.n) end | s-frac(l :: Loc, num :: Number, den :: Number) with: @@ -2023,8 +2023,8 @@ default-map-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(l, loc) end, - method s-num(self, l :: Loc, n :: Number): - s-num(l, n) + method s-num(self, l :: Loc, n :: Number, u :: Option): + s-num(l, n, u) end, method s-frac(self, l :: Loc, num :: Number, den :: Number): s-frac(l, num, den) @@ -3070,8 +3070,8 @@ dummy-loc-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(dummy-loc, loc) end, - method s-num(self, l :: Loc, n :: Number): - s-num(dummy-loc, n) + method s-num(self, l :: Loc, n :: Number, u :: Option): + s-num(dummy-loc, n, u) end, method s-frac(self, l :: Loc, num :: Number, den :: Number): s-frac(dummy-loc, num, den) diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index 00b7b46d0a..8aba0ead62 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -166,7 +166,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: end) end) - | s-num(l, n) => k(N.a-val(l, N.a-num(l, n))) + | s-num(l, n, _) => k(N.a-val(l, N.a-num(l, n))) # num, den are exact ints, and s-frac desugars to the exact rational num/den | s-frac(l, num, den) => k(N.a-val(l, N.a-num(l, num / den))) # Possibly unneeded if removed by desugar? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den diff --git a/src/arr/compiler/desugar.arr b/src/arr/compiler/desugar.arr index 93042cbc93..af8837671a 100644 --- a/src/arr/compiler/desugar.arr +++ b/src/arr/compiler/desugar.arr @@ -528,11 +528,11 @@ fun desugar-expr(expr :: A.Expr): | s-id-var(l, x) => expr | s-id-letrec(_, _, _) => expr | s-srcloc(_, _) => expr - | s-num(_, _) => expr + | s-num(_, _, _) => expr # num, den are exact ints, and s-frac desugars to the exact rational num/den - | s-frac(l, num, den) => A.s-num(l, num / den) # NOTE: Possibly must preserve further? + | s-frac(l, num, den) => A.s-num(l, num / den, none) # NOTE: Possibly must preserve further? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den - | s-rfrac(l, num, den) => A.s-num(l, num-to-roughnum(num / den)) # NOTE: Possibly must preserve further? + | s-rfrac(l, num, den) => A.s-num(l, num-to-roughnum(num / den), none) # NOTE: Possibly must preserve further? | s-str(_, _) => expr | s-bool(_, _) => expr | s-obj(l, fields) => A.s-obj(l, fields.map(desugar-member)) @@ -958,8 +958,8 @@ where: p = lam(str): PP.surface-parse(str, "test").block.visit(A.dummy-loc-visitor) end ds = lam(prog): desugar-expr(prog).visit(unglobal).visit(A.dummy-loc-visitor) end id = lam(s): A.s-id(d, A.s-name(d, s)) end - one = A.s-num(d, 1) - two = A.s-num(d, 2) + one = A.s-num(d, 1, none) + two = A.s-num(d, 2, none) pretty = lam(prog): prog.tosource().pretty(80).join-str("\n") end if-else = "if true: 5 else: 6 end" diff --git a/src/arr/compiler/resolve-scope.arr b/src/arr/compiler/resolve-scope.arr index 743bfbbd59..8cf6d2e4fb 100644 --- a/src/arr/compiler/resolve-scope.arr +++ b/src/arr/compiler/resolve-scope.arr @@ -471,28 +471,28 @@ where: compare1 = - A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 15)), - A.s-let-bind(d, b("y"), A.s-num(d, 10))], + A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 15, none)), + A.s-let-bind(d, b("y"), A.s-num(d, 10, none))], id("y"), false) dsb(p("x = 15 y = 10 y").stmts).visit(A.dummy-loc-visitor) is compare1 dsb(p("x = 55 var y = 10 y").stmts).visit(A.dummy-loc-visitor) - is A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 55)), - A.s-var-bind(d, b("y"), A.s-num(d, 10))], id("y"), false) + is A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 55, none)), + A.s-var-bind(d, b("y"), A.s-num(d, 10, none))], id("y"), false) bs("x = 7 print(2) var y = 10 y") is - A.s-let-expr(d, [list:A.s-let-bind(d, b("x"), A.s-num(d, 7))], + A.s-let-expr(d, [list:A.s-let-bind(d, b("x"), A.s-num(d, 7, none))], A.s-block(d, [list: - A.s-app(d, id("print"), [list:A.s-num(d, 2)]), - A.s-let-expr(d, [list:A.s-var-bind(d, b("y"), A.s-num(d, 10))], + A.s-app(d, id("print"), [list:A.s-num(d, 2, none)]), + A.s-let-expr(d, [list:A.s-var-bind(d, b("y"), A.s-num(d, 10, none))], id("y"), false) ]), false) prog = bs("fun f(): 4 end fun g(): 5 end f()") prog is A.s-letrec(d, [list: - A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4))), - A.s-letrec-bind(d, b("g"), thunk(A.s-num(d, 5))) + A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, none))), + A.s-letrec-bind(d, b("g"), thunk(A.s-num(d, 5, none))) ], A.s-app(d, id("f"), [list: ]), false) @@ -501,13 +501,13 @@ where: prog2 = bs("print(1) fun f(): 4 end fun g(): 5 end fun h(): 6 end x = 3 print(x)") prog2 is A.s-block(d, - [list: p-s(A.s-num(d, 1)), + [list: p-s(A.s-num(d, 1, none)), A.s-letrec(d, [list: - A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4))), - A.s-letrec-bind(d, b("g"), thunk(A.s-num(d, 5))), - A.s-letrec-bind(d, b("h"), thunk(A.s-num(d, 6))) + A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, none))), + A.s-letrec-bind(d, b("g"), thunk(A.s-num(d, 5, none))), + A.s-letrec-bind(d, b("h"), thunk(A.s-num(d, 6, none))) ], - A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 3))], p-s(id("x")), false), + A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 3, none))], p-s(id("x")), false), false)]) dsb([list: prog2]) is prog2 @@ -519,17 +519,17 @@ where: prog3 is A.s-block(d, [list: p-s(id("x")), - A.s-assign(d, A.s-name(d, "x"), A.s-num(d, 3)), + A.s-assign(d, A.s-name(d, "x"), A.s-num(d, 3, none)), p-s(id("x")) ]) prog4 = bs("var x = 10 fun f(): 4 end f()") prog4 is A.s-let-expr(d, [list: - A.s-var-bind(d, b("x"), A.s-num(d, 10)) + A.s-var-bind(d, b("x"), A.s-num(d, 10, none)) ], A.s-letrec(d, [list: - A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4))) + A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, none))) ], A.s-app(d, id("f"), [list: ]), false), false @@ -707,7 +707,7 @@ where: ds = lam(prog): desugar-scope(prog, C.no-builtins).ast.visit(A.dummy-loc-visitor) end compare1 = A.s-program(d, A.s-provide-none(d), A.s-provide-types-none(d), [list: ], A.s-let-expr(d, [list: - A.s-let-bind(d, b("x"), A.s-num(d, 10)) + A.s-let-bind(d, b("x"), A.s-num(d, 10, none)) ], A.s-module(d, id("nothing"), empty, empty, id("x"), [list:], checks), false) ) diff --git a/src/arr/compiler/type-check.arr b/src/arr/compiler/type-check.arr index 7cc04f33ce..0eebad91f7 100644 --- a/src/arr/compiler/type-check.arr +++ b/src/arr/compiler/type-check.arr @@ -566,7 +566,7 @@ fun _checking(e :: Expr, expect-type :: Type, top-level :: Boolean, context :: C raise("checking for s-undefined not implemented") | s-srcloc(l, loc) => check-synthesis(e, expect-type, top-level, context) - | s-num(l, n) => + | s-num(l, n, _) => check-synthesis(e, expect-type, top-level, context) | s-frac(l, num, den) => check-synthesis(e, expect-type, top-level, context) @@ -828,7 +828,7 @@ fun _synthesis(e :: Expr, top-level :: Boolean, context :: Context) -> TypingRes raise("synthesis for s-undefined not implemented") | s-srcloc(l, loc) => typing-result(e, t-srcloc(l), context) - | s-num(l, n) => + | s-num(l, n, _) => typing-result(e, t-number(l), context) | s-frac(l, num, den) => typing-result(e, t-number(l), context) diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index afeacbb60d..815d7ce942 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -347,7 +347,7 @@ fun reject-standalone-exprs(stmts :: List%(is-link), ignore-last :: Boolean) blo ED.text(" operator expression probably isn't intentional.")]], l) end | s-id(_, _) => wf-error([list: [ED.para: ED.text("A standalone variable name probably isn't intentional.")]], l) - | s-num(_, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) + | s-num(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-frac(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-rfrac(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-bool(_, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index 45ab2257e3..21274475af 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -2140,8 +2140,8 @@ default-map-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(l, loc) end, - method s-num(self, l :: Loc, n :: Number): - s-num(l, n) + method s-num(self, l :: Loc, n :: Number, u :: Option): + s-num(l, n, u) end, method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger): s-frac(l, num, den) @@ -2700,7 +2700,7 @@ default-iter-visitor = { method s-srcloc(self, l, shadow loc): true end, - method s-num(self, l :: Loc, n :: Number): + method s-num(self, l :: Loc, n :: Number, u :: Option): true end, method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger): @@ -3237,8 +3237,8 @@ dummy-loc-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(dummy-loc, loc) end, - method s-num(self, l :: Loc, n :: Number): - s-num(dummy-loc, n) + method s-num(self, l :: Loc, n :: Number, u :: Option): + s-num(dummy-loc, n, u) end, method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger): s-frac(dummy-loc, num, den) diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index bd02b249bc..c1565f26a1 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -131,7 +131,7 @@ id-expr: NAME prim-expr: num-expr | frac-expr | rfrac-expr | bool-expr | string-expr dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) | (LANGLE|LT) NUMBER (RANGLE|GT) -unit-expr: (PARENNOSPACE|PARENAFTERBRACE) unit-expr | RPAREN +unit-expr: (PARENNOSPACE|PARENAFTERBRACE) unit-expr RPAREN | unit-expr CARET NUMBER | unit-expr TIMES unit-expr | unit-expr SLASH unit-expr diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 2593f19196..7768475102 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -968,6 +968,18 @@ // (prim-expr e) return tr(node.kids[0]); }, + 'dim-expr': function(node) { + // (LANGLE|LT) unit-expr (RANGLE|GT) | (LANGLE|LT) NUMBER (RANGLE|GT) + // TODO: Handle numbers here somehow + return tr(node.kids[1]) + }, + 'unit-expr': function(node) { + if (node.kids.length === 1) { + // (unit-expr ident) + return RUNTIME.getField(ast, 'u-base') + .app(pos(node.pos), tr(node.kids[0])) + } + }, 'tuple-expr': function(node) { return RUNTIME.getField(ast, 's-tuple') .app(pos(node.pos), tr(node.kids[1])) @@ -1139,9 +1151,9 @@ } }, 'num-expr': function(node) { - // (num-expr n) + // (num-expr n [PERCENT dim-expr]) return RUNTIME.getField(ast, 's-num') - .app(pos(node.pos), number(node.kids[0])); + .app(pos(node.pos), number(node.kids[0]), RUNTIME.ffi.makeNone()); }, 'frac-expr': function(node) { // (frac-expr n) diff --git a/tests/pyret/regression/parens-after-comments.arr b/tests/pyret/regression/parens-after-comments.arr index 2c3977717e..b361fc306c 100644 --- a/tests/pyret/regression/parens-after-comments.arr +++ b/tests/pyret/regression/parens-after-comments.arr @@ -22,8 +22,8 @@ check "https://github.com/brownplt/pyret-lang/issues/828": (1 + 2) ```).visit(A.dummy-loc-visitor) is program-block([list: - A.s-op(A.dummy-loc, A.dummy-loc, "op*", A.s-num(A.dummy-loc, 3), A.s-num(A.dummy-loc, 2)), - A.s-paren(A.dummy-loc, A.s-op(A.dummy-loc, A.dummy-loc, "op+", A.s-num(A.dummy-loc, 1), A.s-num(A.dummy-loc, 2))) + A.s-op(A.dummy-loc, A.dummy-loc, "op*", A.s-num(A.dummy-loc, 3, none), A.s-num(A.dummy-loc, 2, none)), + A.s-paren(A.dummy-loc, A.s-op(A.dummy-loc, A.dummy-loc, "op+", A.s-num(A.dummy-loc, 1, none), A.s-num(A.dummy-loc, 2, none))) ]) P.get-parse-result("ab#|...|#cd").visit(A.dummy-loc-visitor) diff --git a/tests/pyret/regression/pretty-print-instantiate.arr b/tests/pyret/regression/pretty-print-instantiate.arr index 92f07abb77..c5255b0a93 100644 --- a/tests/pyret/regression/pretty-print-instantiate.arr +++ b/tests/pyret/regression/pretty-print-instantiate.arr @@ -2,5 +2,5 @@ import ast as A check: d = A.dummy-loc - A.s-instantiate(d, A.s-num(d, 0), [list: A.a-any(d)]).tosource().pretty(80) is [list: "0"] + A.s-instantiate(d, A.s-num(d, 0, none), [list: A.a-any(d)]).tosource().pretty(80) is [list: "0"] end From 1988ae233fef2d856af51a3a6b8d8b8d8726ffb3 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 29 May 2019 13:13:00 -0400 Subject: [PATCH 05/84] Add some labels/tosource --- src/arr/trove/ast.arr | 41 +++++++++++++++++++++++++++++------ src/js/base/pyret-grammar.bnf | 4 ++-- src/js/trove/parse-pyret.js | 30 +++++++++++++++++++++---- t.arr | 2 +- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index 21274475af..9b92bbc657 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -104,6 +104,9 @@ str-extract = PP.str("extract") str-load-table = PP.str("load-table:") str-src = PP.str("source:") str-sanitize = PP.str("sanitize") +str-one = PP.str("1") +str-times = PP.str("*") +str-divide = PP.str("/") data Name: | s-underscore(l :: Loc) with: @@ -930,7 +933,13 @@ data Expr: method tosource(self): PP.str(torepr(self.loc)) end | s-num(l :: Loc, n :: Number, u :: Option) with: method label(self): "s-num" end, - method tosource(self): PP.number(self.n) end + method tosource(self): + cases(Option) self.u: + | none => PP.number(self.n) + | some(u) => PP.separate(str-percent, + [list: PP.number(self.n), PP.surround(INDENT, 0, PP.langle, u.tosource(), PP.rangle)]) + end + end | s-frac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option) with: method label(self): "s-frac" end, method tosource(self): PP.number(self.num) + PP.str("/") + PP.number(self.den) end @@ -1626,12 +1635,30 @@ sharing: end data Unit: - | u-one(l :: Loc) - | u-base(l :: Loc, id :: Name) - | u-mul(l :: Loc, lhs :: Unit, rhs :: Unit) - | u-div(l :: Loc, lhs :: Unit, rhs :: Unit) - | u-pow(l :: Loc, u :: Unit, n :: NumInteger) - | u-paren(l :: Loc, u :: Unit) + | u-one(l :: Loc) with: + method label(self): "u-one" end, + method tosource(self): str-one end + | u-base(l :: Loc, id :: Name) with: + method label(self): "u-base" end, + method tosource(self): self.id.tosource() end + | u-mul(l :: Loc, lhs :: Unit, rhs :: Unit) with: + method label(self): "u-mul" end, + method tosource(self): + PP.separate(str-space, [list: self.lhs.tosource(), str-times, self.rhs.tosource()]) + end + | u-div(l :: Loc, lhs :: Unit, rhs :: Unit) with: + method label(self): "u-div" end, + method tosource(self): + PP.separate(str-space, [list: self.lhs.tosource(), str-divide, self.rhs.tosource()]) + end + | u-pow(l :: Loc, u :: Unit, n :: NumInteger) with: + method label(self): "u-div" end, + method tosource(self): + PP.separate(str-space, [list: self.u.tosource(), str-caret, PP.number(self.n)]) + end + | u-paren(l :: Loc, u :: Unit) with: + method label(self): "u-paren" end, + method tosource(self): PP.paren(self.u.tosource()) end end diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index c1565f26a1..9eeb689a3e 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -133,9 +133,9 @@ prim-expr: num-expr | frac-expr | rfrac-expr | bool-expr | string-expr dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) | (LANGLE|LT) NUMBER (RANGLE|GT) unit-expr: (PARENNOSPACE|PARENAFTERBRACE) unit-expr RPAREN | unit-expr CARET NUMBER - | unit-expr TIMES unit-expr + | unit-expr STAR unit-expr | unit-expr SLASH unit-expr - | id-expr + | NAME num-expr: NUMBER [PERCENT dim-expr] diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 7768475102..3141c4fa95 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -977,7 +977,23 @@ if (node.kids.length === 1) { // (unit-expr ident) return RUNTIME.getField(ast, 'u-base') - .app(pos(node.pos), tr(node.kids[0])) + .app(pos(node.pos), name(node.kids[0])) + } else if (node.kids[1].name === 'CARET') { + // (unit-expr unit-expr CARET unit-expr) + return RUNTIME.getField(ast, 'u-pow') + .app(pos(node.pos), tr(node.kids[0]), number(node.kids[2])) + } else if (node.kids[1].name === 'STAR') { + // (unit-expr unit-expr TIMES unit-expr) + return RUNTIME.getField(ast, 'u-mul') + .app(pos(node.pos), tr(node.kids[0]), tr(node.kids[2])) + } else if (node.kids[1].name === 'SLASH') { + // (unit-expr unit-expr DIVIDE unit-expr) + return RUNTIME.getField(ast, 'u-div') + .app(pos(node.pos), tr(node.kids[0]), tr(node.kids[2])) + } else { + // (unit-expr PAREN unit-expr PAREN) + return RUNTIME.getField(ast, 'u-paren') + .app(pos(node.pos), tr(node.kids[1])) } }, 'tuple-expr': function(node) { @@ -1151,9 +1167,15 @@ } }, 'num-expr': function(node) { - // (num-expr n [PERCENT dim-expr]) - return RUNTIME.getField(ast, 's-num') - .app(pos(node.pos), number(node.kids[0]), RUNTIME.ffi.makeNone()); + if (node.kids.length == 1) { + // (num-expr n) + return RUNTIME.getField(ast, 's-num') + .app(pos(node.pos), number(node.kids[0]), RUNTIME.ffi.makeNone()); + } else { + // (num-expr n [PERCENT dim-expr]) + return RUNTIME.getField(ast, 's-num') + .app(pos(node.pos), number(node.kids[0]), RUNTIME.ffi.makeSome(tr(node.kids[2]))); + } }, 'frac-expr': function(node) { // (frac-expr n) diff --git a/t.arr b/t.arr index 6417b50c96..b8b8f4b49f 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,3 @@ -var result = 1% + 1% +var result = 1% + 1%<(s * m) ^ 2> print(result) From 4c48e62b3d91a72ea012748538b15928cfa167cb Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 29 May 2019 13:22:48 -0400 Subject: [PATCH 06/84] Add parse tests --- src/js/base/pyret-grammar.bnf | 2 +- t.arr | 2 +- tests/pyret/tests/test-parse.arr | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index 9eeb689a3e..f48cd9a069 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -131,7 +131,7 @@ id-expr: NAME prim-expr: num-expr | frac-expr | rfrac-expr | bool-expr | string-expr dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) | (LANGLE|LT) NUMBER (RANGLE|GT) -unit-expr: (PARENNOSPACE|PARENAFTERBRACE) unit-expr RPAREN +unit-expr: (PARENNOSPACE|PARENSPACE) unit-expr RPAREN | unit-expr CARET NUMBER | unit-expr STAR unit-expr | unit-expr SLASH unit-expr diff --git a/t.arr b/t.arr index b8b8f4b49f..6bbac9face 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,3 @@ -var result = 1% + 1%<(s * m) ^ 2> +var result = 2%<(m / n) ^ -5 * (o ^ 10)> print(result) diff --git a/tests/pyret/tests/test-parse.arr b/tests/pyret/tests/test-parse.arr index c5e9ff6fb6..3e76bef566 100644 --- a/tests/pyret/tests/test-parse.arr +++ b/tests/pyret/tests/test-parse.arr @@ -468,3 +468,24 @@ check "should parse reactors": does-parse("reactor: end") is false does-parse("reactor end") is false end + +check "should parse unit-annotated numbers": + # TODO(benmusch): Figure out a non-ambiguous grammar to get rid of the need + # for %'s + does-parse("2%") is true + does-parse("2%<(m)>") is true + does-parse("2%") is true + does-parse("2%") is true + does-parse("2%") is true + does-parse("2%<(m / n) ^ -5 * (o ^ 10)>") is true + + does-parse("2%<>") is false + does-parse("2%<()>") is false + does-parse("2%") is false + does-parse("2%") is false + does-parse("2%") is false + does-parse("2%") is false + does-parse("2%") is false + does-parse("2%") is false + does-parse("2%") is false +end From e908e279ac55552f3fe9d69cb965301a77f2e41a Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 29 May 2019 15:04:21 -0400 Subject: [PATCH 07/84] Add wf-checks for units --- src/arr/compiler/compile-structs.arr | 25 ++++++++++++++++ src/arr/compiler/well-formed.arr | 44 ++++++++++++++++++++++++++++ src/arr/trove/ast.arr | 10 ++++++- t.arr | 2 +- tests/pyret/tests/test-parse.arr | 2 ++ 5 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/arr/compiler/compile-structs.arr b/src/arr/compiler/compile-structs.arr index ca8a85f90b..b7cba36f76 100644 --- a/src/arr/compiler/compile-structs.arr +++ b/src/arr/compiler/compile-structs.arr @@ -670,6 +670,31 @@ data CompileError: ED.loc(self.loc), ED.text(" because its denominator is zero.")]] end + | invalid-unit-power(loc, power) with: + method render-fancy-reason(self): + [ED.error: + [ED.para: + ED.text("Reading a "), + ED.highlight(ED.text("unit annotation"), [ED.locs: self.loc], 0), + ED.text(" errored:")], + ED.cmcode(self.loc), + [ED.para: + ED.text("The exponent "), + ED.embed(self.power), + ED.text("is not allowed in unit expressions. "), + ED.text("Make sure to use a non-zero integer value.")]] + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.text("Pyret disallows units with the exponent")], + [ED.para: + ED.embed(self.power)], + [ED.para: + ED.text("at "), + ED.loc(self.loc), + ED.text(". Make sure to use a non-zero integer value.")]] + end | mixed-binops(exp-loc, op-a-name, op-a-loc, op-b-name, op-b-loc) with: method render-fancy-reason(self): [ED.error: diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index 815d7ce942..797bf7f15c 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -279,6 +279,43 @@ fun ensure-unique-variant-ids(variants :: List, name :: String, data- end end +fun unit-opname(u :: A.Unit): + cases(A.Unit) u: + | u-mul(l, _, _) => "*" + | u-div(l, _, _) => "/" + | u-pow(l, _, _) => "^" + | else => raise("unit-opname called on non-operator") + end +end +fun wf-unit(u :: A.Unit, parent-maybe :: Option) block: + cases(Option) parent-maybe block: + | some(p) => + if u.is-operation() and p.is-operation() and not(u.label() == p.label()): + add-error(C.mixed-binops(p.l, unit-opname(p), u.l, unit-opname(u), u.l)) + else: + nothing + end + | none => nothing + end + + cases(A.Unit) u block: + | u-mul(_, lhs, rhs) => + wf-unit(lhs, some(u)) + wf-unit(rhs, some(u)) + | u-div(_, lhs, rhs) => + wf-unit(lhs, some(u)) + wf-unit(rhs, some(u)) + | u-paren(_, paren-u) => wf-unit(paren-u, some(u)) + | u-pow(l, pow-u, n) => + if (n == 0) or not(num-is-integer(n)): + add-error(C.invalid-unit-power(l, n)) + else: + nothing + end + wf-unit(pow-u, some(u)) + | else => nothing + end +end fun wf-last-stmt(block-loc, stmt :: A.Expr): cases(A.Expr) stmt: @@ -989,6 +1026,13 @@ well-formed-visitor = A.default-iter-visitor.{ add-error(C.underscore-as-ann(l)) end true + end, + method s-num(self, l, n, unit-maybe) block: + cases(Option) unit-maybe: + | none => nothing + | some(u) => wf-unit(u, none) + end + true end } diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index 9b92bbc657..22ea1ee0a6 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -1637,28 +1637,36 @@ end data Unit: | u-one(l :: Loc) with: method label(self): "u-one" end, + method is-operation(self): false end, method tosource(self): str-one end | u-base(l :: Loc, id :: Name) with: method label(self): "u-base" end, + method is-operation(self): false end, method tosource(self): self.id.tosource() end | u-mul(l :: Loc, lhs :: Unit, rhs :: Unit) with: method label(self): "u-mul" end, + method is-operation(self): true end, method tosource(self): PP.separate(str-space, [list: self.lhs.tosource(), str-times, self.rhs.tosource()]) end | u-div(l :: Loc, lhs :: Unit, rhs :: Unit) with: method label(self): "u-div" end, + method is-operation(self): true end, method tosource(self): PP.separate(str-space, [list: self.lhs.tosource(), str-divide, self.rhs.tosource()]) end - | u-pow(l :: Loc, u :: Unit, n :: NumInteger) with: + | u-pow(l :: Loc, u :: Unit, n :: Number) with: method label(self): "u-div" end, + method is-operation(self): true end, method tosource(self): PP.separate(str-space, [list: self.u.tosource(), str-caret, PP.number(self.n)]) end | u-paren(l :: Loc, u :: Unit) with: method label(self): "u-paren" end, + method is-operation(sel): false end, method tosource(self): PP.paren(self.u.tosource()) end +sharing: + method loc(self): self.l end end diff --git a/t.arr b/t.arr index 6bbac9face..ae1d9bbd4c 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,3 @@ -var result = 2%<(m / n) ^ -5 * (o ^ 10)> +var result = 2%<((m / n) ^ -5) * (o ^ 2)> print(result) diff --git a/tests/pyret/tests/test-parse.arr b/tests/pyret/tests/test-parse.arr index 3e76bef566..0f10cbf8ec 100644 --- a/tests/pyret/tests/test-parse.arr +++ b/tests/pyret/tests/test-parse.arr @@ -477,6 +477,8 @@ check "should parse unit-annotated numbers": does-parse("2%") is true does-parse("2%") is true does-parse("2%") is true + does-parse("2%") is true # should be wf-error + does-parse("2%") is true # should be wf-error does-parse("2%<(m / n) ^ -5 * (o ^ 10)>") is true does-parse("2%<>") is false From e0da7e745ec063164a0e63dd0173654ddc92f0de Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 29 May 2019 16:53:14 -0400 Subject: [PATCH 08/84] Fix bugs, add well-formedness tests --- src/arr/compiler/well-formed.arr | 27 +++++++++++++++--------- src/arr/trove/ast.arr | 15 ++++--------- src/js/base/pyret-grammar.bnf | 8 +++---- src/js/trove/parse-pyret.js | 29 +++++++++++++++----------- t.arr | 4 +--- tests/pyret/tests/test-parse.arr | 3 ++- tests/pyret/tests/test-well-formed.arr | 9 ++++++++ 7 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index 797bf7f15c..d55719620f 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -279,19 +279,22 @@ fun ensure-unique-variant-ids(variants :: List, name :: String, data- end end -fun unit-opname(u :: A.Unit): +fun unit-is-op(u :: A.Unit): + # NOTE(benmusch): Should we include powers here? + A.is-u-mul(u) or A.is-u-div(u) or A.is-u-pow(u) +end +fun unit-opname(u :: A.Unit%(unit-is-op)): cases(A.Unit) u: - | u-mul(l, _, _) => "*" - | u-div(l, _, _) => "/" - | u-pow(l, _, _) => "^" - | else => raise("unit-opname called on non-operator") + | u-mul(_, _, _, _) => "*" + | u-div(_, _, _, _) => "/" + | u-pow(_, _, _, _) => "^" end end fun wf-unit(u :: A.Unit, parent-maybe :: Option) block: cases(Option) parent-maybe block: | some(p) => - if u.is-operation() and p.is-operation() and not(u.label() == p.label()): - add-error(C.mixed-binops(p.l, unit-opname(p), u.l, unit-opname(u), u.l)) + if unit-is-op(u) and unit-is-op(p) and not(u.label() == p.label()): + add-error(C.mixed-binops(p.l, unit-opname(u), u.op-l, unit-opname(p), p.op-l)) else: nothing end @@ -299,14 +302,14 @@ fun wf-unit(u :: A.Unit, parent-maybe :: Option) block: end cases(A.Unit) u block: - | u-mul(_, lhs, rhs) => + | u-mul(_, _, lhs, rhs) => wf-unit(lhs, some(u)) wf-unit(rhs, some(u)) - | u-div(_, lhs, rhs) => + | u-div(_, _, lhs, rhs) => wf-unit(lhs, some(u)) wf-unit(rhs, some(u)) | u-paren(_, paren-u) => wf-unit(paren-u, some(u)) - | u-pow(l, pow-u, n) => + | u-pow(l, _, pow-u, n) => if (n == 0) or not(num-is-integer(n)): add-error(C.invalid-unit-power(l, n)) else: @@ -344,6 +347,7 @@ fun reachable-ops(self, l, op-l, op, ast): reachable-ops(self, l, op-l, op, right2) else: add-error(C.mixed-binops(l, opname(op), op-l, opname(op2), op-l2)) + nothing end true | else => ast.visit(self) @@ -1374,6 +1378,9 @@ top-level-visitor = A.default-iter-visitor.{ end, method a-field(_, l, name, ann): well-formed-visitor.a-field(l, name, ann) + end, + method s-num(self, l, n, unit-maybe): + well-formed-visitor.s-num(l, n, unit-maybe) end } diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index 22ea1ee0a6..bb80933f92 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -1637,36 +1637,29 @@ end data Unit: | u-one(l :: Loc) with: method label(self): "u-one" end, - method is-operation(self): false end, method tosource(self): str-one end | u-base(l :: Loc, id :: Name) with: method label(self): "u-base" end, - method is-operation(self): false end, method tosource(self): self.id.tosource() end - | u-mul(l :: Loc, lhs :: Unit, rhs :: Unit) with: + | u-mul(l :: Loc, op-l :: Loc, lhs :: Unit, rhs :: Unit) with: method label(self): "u-mul" end, - method is-operation(self): true end, method tosource(self): PP.separate(str-space, [list: self.lhs.tosource(), str-times, self.rhs.tosource()]) end - | u-div(l :: Loc, lhs :: Unit, rhs :: Unit) with: + | u-div(l :: Loc, op-l :: Loc, lhs :: Unit, rhs :: Unit) with: method label(self): "u-div" end, - method is-operation(self): true end, method tosource(self): PP.separate(str-space, [list: self.lhs.tosource(), str-divide, self.rhs.tosource()]) end - | u-pow(l :: Loc, u :: Unit, n :: Number) with: - method label(self): "u-div" end, + | u-pow(l :: Loc, op-l :: Loc, u :: Unit, n :: Number) with: + method label(self): "u-pow" end, method is-operation(self): true end, method tosource(self): PP.separate(str-space, [list: self.u.tosource(), str-caret, PP.number(self.n)]) end | u-paren(l :: Loc, u :: Unit) with: method label(self): "u-paren" end, - method is-operation(sel): false end, method tosource(self): PP.paren(self.u.tosource()) end -sharing: - method loc(self): self.l end end diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index f48cd9a069..f5ab384f96 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -130,12 +130,12 @@ id-expr: NAME prim-expr: num-expr | frac-expr | rfrac-expr | bool-expr | string-expr -dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) | (LANGLE|LT) NUMBER (RANGLE|GT) -unit-expr: (PARENNOSPACE|PARENSPACE) unit-expr RPAREN +dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) +# TODO(benmusch): Consider adding | (LANGLE|LT) NUMBER (RANGLE|GT) +unit-lhs: (PARENNOSPACE|PARENSPACE) unit-expr RPAREN | unit-expr CARET NUMBER - | unit-expr STAR unit-expr - | unit-expr SLASH unit-expr | NAME +unit-expr: unit-lhs | unit-lhs STAR unit-expr | unit-lhs SLASH unit-expr num-expr: NUMBER [PERCENT dim-expr] diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 3141c4fa95..79a60d13fa 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -973,29 +973,34 @@ // TODO: Handle numbers here somehow return tr(node.kids[1]) }, - 'unit-expr': function(node) { + 'unit-lhs': function(node) { if (node.kids.length === 1) { - // (unit-expr ident) return RUNTIME.getField(ast, 'u-base') .app(pos(node.pos), name(node.kids[0])) } else if (node.kids[1].name === 'CARET') { - // (unit-expr unit-expr CARET unit-expr) + // (unit-expr unit-lhs CARET unit-expr) return RUNTIME.getField(ast, 'u-pow') - .app(pos(node.pos), tr(node.kids[0]), number(node.kids[2])) - } else if (node.kids[1].name === 'STAR') { - // (unit-expr unit-expr TIMES unit-expr) - return RUNTIME.getField(ast, 'u-mul') - .app(pos(node.pos), tr(node.kids[0]), tr(node.kids[2])) - } else if (node.kids[1].name === 'SLASH') { - // (unit-expr unit-expr DIVIDE unit-expr) - return RUNTIME.getField(ast, 'u-div') - .app(pos(node.pos), tr(node.kids[0]), tr(node.kids[2])) + .app(pos(node.pos), pos(node.kids[1].pos), tr(node.kids[0]), number(node.kids[2])) } else { // (unit-expr PAREN unit-expr PAREN) return RUNTIME.getField(ast, 'u-paren') .app(pos(node.pos), tr(node.kids[1])) } }, + 'unit-expr': function(node) { + if (node.kids.length === 1) { + // (unit-expr unit-lhs) + return tr(node.kids[0]) + } else if (node.kids[1].name === 'STAR') { + // (unit-expr unit-lhs TIMES unit-expr) + return RUNTIME.getField(ast, 'u-mul') + .app(pos(node.pos), pos(node.kids[1].pos), tr(node.kids[0]), tr(node.kids[2])) + } else if (node.kids[1].name === 'SLASH') { + // (unit-expr unit-lhs DIVIDE unit-expr) + return RUNTIME.getField(ast, 'u-div') + .app(pos(node.pos), pos(node.kids[1].pos), tr(node.kids[0]), tr(node.kids[2])) + } + }, 'tuple-expr': function(node) { return RUNTIME.getField(ast, 's-tuple') .app(pos(node.pos), tr(node.kids[1])) diff --git a/t.arr b/t.arr index ae1d9bbd4c..9e4df26f38 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1 @@ -var result = 2%<((m / n) ^ -5) * (o ^ 2)> - -print(result) +2% diff --git a/tests/pyret/tests/test-parse.arr b/tests/pyret/tests/test-parse.arr index 0f10cbf8ec..c79a2a1e4b 100644 --- a/tests/pyret/tests/test-parse.arr +++ b/tests/pyret/tests/test-parse.arr @@ -476,9 +476,9 @@ check "should parse unit-annotated numbers": does-parse("2%<(m)>") is true does-parse("2%") is true does-parse("2%") is true + does-parse("2%") is true does-parse("2%") is true does-parse("2%") is true # should be wf-error - does-parse("2%") is true # should be wf-error does-parse("2%<(m / n) ^ -5 * (o ^ 10)>") is true does-parse("2%<>") is false @@ -488,6 +488,7 @@ check "should parse unit-annotated numbers": does-parse("2%") is false does-parse("2%") is false does-parse("2%") is false + does-parse("2%") is false does-parse("2%") is false does-parse("2%") is false end diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index b23c3d7afd..5797a63953 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -263,6 +263,15 @@ check "underscores": run-str("{a: 1}._") is%(output) compile-error(CS.is-underscore-as) end +check "unit annotations": + run-str("2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("2%") is%(output) compile-error(CS.is-mixed-binops) + + run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) +end + #| it("should notice empty blocks", function(done) { P.checkCompileError("lam(): end", function(e) { From 8b1f14817a885017ab30b520aee4e68172dc41c4 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 29 May 2019 23:08:12 -0400 Subject: [PATCH 09/84] Add units to a-num --- src/arr/compiler/anf-loop-compiler.arr | 19 ++++++------ src/arr/compiler/anf.arr | 14 ++++++--- src/arr/compiler/ast-anf.arr | 42 +++++++++++++++++++++----- src/arr/compiler/well-formed.arr | 4 +++ t.arr | 2 +- 5 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index e25e2b275a..b3eb26d11a 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -1667,7 +1667,7 @@ compiler-visitor = { method a-srcloc(self, l, loc): c-exp(self.get-loc(loc), cl-empty) end, - method a-num(self, l :: Loc, n :: Number): + method a-num(self, l :: Loc, n :: Number, u :: N.AUnit): if num-is-fixnum(n): c-exp(j-parens(j-num(n)), cl-empty) else: @@ -1907,21 +1907,22 @@ remove-useless-if-visitor = N.default-map-visitor.{ check: d = N.dummy-loc + u-one = N.a-unit-one true1 = N.a-if(d, N.a-bool(d, true), - N.a-lettable(d, N.a-val(d, N.a-num(d, 1))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 2)))) - true1.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 1)) + N.a-lettable(d, N.a-val(d, N.a-num(d, 1, u-one))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 2, u-one)))) + true1.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 1, u-one)) false4 = N.a-if(d, N.a-bool(d, false), - N.a-lettable(d, N.a-val(d, N.a-num(d, 3))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 4)))) - false4.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 4)) + N.a-lettable(d, N.a-val(d, N.a-num(d, 3, u-one))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 4, u-one)))) + false4.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 4, u-one)) N.a-if(d, N.a-id(d, A.s-name(d, "x")), N.a-lettable(d, true1), N.a-lettable(d, false4) ).visit(remove-useless-if-visitor) is N.a-if(d, N.a-id(d, A.s-name(d, "x")), - N.a-lettable(d, N.a-val(d, N.a-num(d, 1))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 4)))) + N.a-lettable(d, N.a-val(d, N.a-num(d, 1, u-one))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 4, u-one)))) end |# diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index 8aba0ead62..2bc3b08342 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -142,6 +142,11 @@ fun anf-block(es-init :: List, k :: ANFCont): anf-block-help(es-init) end +fun anf-unit(u-maybe :: Option) -> N.AUnit: + # TODO: Fill this in + N.a-unit-one +end + fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: cases(A.Expr) e: | s-module(l, answer, dvs, dts, provides, types, checks) => @@ -166,11 +171,12 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: end) end) - | s-num(l, n, _) => k(N.a-val(l, N.a-num(l, n))) + | s-num(l, n, u-maybe) => + k(N.a-val(l, N.a-num(l, n, anf-unit(u-maybe)))) # num, den are exact ints, and s-frac desugars to the exact rational num/den - | s-frac(l, num, den) => k(N.a-val(l, N.a-num(l, num / den))) # Possibly unneeded if removed by desugar? + | s-frac(l, num, den) => k(N.a-val(l, N.a-num(l, num / den, N.a-unit-one))) # Possibly unneeded if removed by desugar? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den - | s-rfrac(l, num, den) => k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den)))) # Possibly unneeded if removed by desugar? + | s-rfrac(l, num, den) => k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den), N.a-unit-one))) # Possibly unneeded if removed by desugar? | s-str(l, s) => k(N.a-val(l, N.a-str(l, s))) | s-undefined(l) => k(N.a-val(l, N.a-undefined(l))) | s-bool(l, b) => k(N.a-val(l, N.a-bool(l, b))) @@ -342,7 +348,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: N.a-let( l, bind(l, array-id), - N.a-prim-app(l, "makeArrayN", [list: N.a-num(l, values.length())], flat-prim-app), + N.a-prim-app(l, "makeArrayN", [list: N.a-num(l, values.length(), N.a-unit-one)], flat-prim-app), anf-name-arr-rec(values, array-id, 0, lam(): k(N.a-val(l, N.a-id(l, array-id))) end)) diff --git a/src/arr/compiler/ast-anf.arr b/src/arr/compiler/ast-anf.arr index 182c85d9e4..521e9aae1e 100644 --- a/src/arr/compiler/ast-anf.arr +++ b/src/arr/compiler/ast-anf.arr @@ -41,6 +41,9 @@ str-provide = PP.str("provide") str-as = PP.str("as") str-from = PP.str("from") str-newtype = PP.str("newtype ") +str-percent = PP.str("%") +str-caret = PP.str("^") +str-space = PP.str(" ") dummy-loc = SL.builtin("dummy-location") is-s-provide-complete = A.is-s-provide-complete @@ -60,6 +63,17 @@ sharing: end end +# normalized form of a unit as a linked-list of names and powers +data AUnit: + | a-unit-one with: + method tosource(self): PP.str("") end + | a-unit-name(l :: Loc, id :: A.Name, power :: Number, rest :: AUnit) with: + method tosource(self): + PP.separate(str-space, [list: + self.id.tosource(), str-caret, PP.number(self.power), self.rest.tosource()]) + end +end + data AImportType: | a-import-builtin(l :: Loc, lib :: String) with: method tosource(self): PP.str(self.lib) end @@ -461,9 +475,15 @@ data AVal: | a-srcloc(l :: Loc, loc :: Loc) with: method label(self): "a-srcloc" end, method tosource(self): PP.str(torepr(self.loc)) end - | a-num(l :: Loc, n :: Number) with: + | a-num(l :: Loc, n :: Number, u :: AUnit) with: method label(self): "a-num" end, - method tosource(self): PP.number(self.n) end + method tosource(self): + cases(AUnit) self.u: + | a-unit-one(_) => PP.number(self.n) + | a-unit-name(_, _, _, _) => PP.separate(str-percent, + [list: PP.number(self.n), PP.surround(INDENT, 0, PP.langle, self.u.tosource(), PP.rangle)]) + end + end | a-str(l :: Loc, s :: String) with: method label(self): "a-str" end, method tosource(self): PP.str(torepr(self.s)) end @@ -574,10 +594,18 @@ fun strip-loc-field(field :: AField): end end +fun strip-loc-unit(u :: AUnit): + cases(AUnit) u: + | a-unit-one => a-unit-one + | a-unit-name(_, id, power, rest) => + a-unit-name(dummy-loc, id, power, strip-loc-unit(rest)) + end +end + fun strip-loc-val(val :: AVal): cases(AVal) val: | a-srcloc(_, l) => a-srcloc(dummy-loc, l) - | a-num(_, n) => a-num(dummy-loc, n) + | a-num(_, n, u) => a-num(dummy-loc, n, u) | a-str(_, s) => a-str(dummy-loc, s) | a-bool(_, b) => a-bool(dummy-loc, b) | a-undefined(_) => a-undefined(dummy-loc) @@ -705,8 +733,8 @@ default-map-visitor = { method a-srcloc(self, l, loc): a-srcloc(l, loc) end, - method a-num(self, l :: Loc, n :: Number): - a-num(l, n) + method a-num(self, l :: Loc, n :: Number, u :: AUnit): + a-num(l, n, u) end, method a-str(self, l :: Loc, s :: String): a-str(l, s) @@ -811,7 +839,7 @@ where: x = n("x") y = n("y") freevars-e( - a-let(d, a-bind(d, x, A.a-blank), a-val(d, a-num(d, 4)), + a-let(d, a-bind(d, x, A.a-blank), a-val(d, a-num(d, 4, a-unit-one)), a-lettable(d, a-val(d, a-id(d, y))))).keys-list() is [list: y.key()] end @@ -968,7 +996,7 @@ fun freevars-v-acc(v :: AVal, seen-so-far :: NameDict) -> NameDict seen-so-far - | a-num(_, _) => seen-so-far + | a-num(_, _, _) => seen-so-far | a-str(_, _) => seen-so-far | a-bool(_, _) => seen-so-far | a-undefined(_) => seen-so-far diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index d55719620f..edf558436f 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -290,6 +290,10 @@ fun unit-opname(u :: A.Unit%(unit-is-op)): | u-pow(_, _, _, _) => "^" end end + + +# TODO(benmuch): Consider refactoring to make this a proper visitor +# and shadow the reachable-ops pattern fun wf-unit(u :: A.Unit, parent-maybe :: Option) block: cases(Option) parent-maybe block: | some(p) => diff --git a/t.arr b/t.arr index 9e4df26f38..3aad08ab5c 100644 --- a/t.arr +++ b/t.arr @@ -1 +1 @@ -2% +2%<(m ^ 1) / o> From a39fe86127fd23c85965597e2ae226be21789b5b Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 29 May 2019 23:45:54 -0400 Subject: [PATCH 10/84] Add logic to normalize the units --- src/arr/compiler/anf.arr | 37 ++++++++++++++++++++++++++++++++++-- src/arr/compiler/ast-anf.arr | 12 ++---------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index 2bc3b08342..d5e64f7436 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -6,6 +6,7 @@ provide-types * import ast as A import srcloc as SL import file("ast-anf.arr") as N +import string-dict as SD type Loc = SL.Srcloc @@ -142,9 +143,41 @@ fun anf-block(es-init :: List, k :: ANFCont): anf-block-help(es-init) end +# return all of the names in a unit +fun unit-names(u :: A.Unit) -> Set: + cases (A.Unit) u: + | u-one(_) => [list-set: ] + | u-base(_, id) => [list-set: id] + | u-mul(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) + | u-div(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) + | u-pow(_, _, shadow u, n) => unit-names(u) + | u-paren(_, _, shadow u, n) => unit-names(u) + end +end + +fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: + cases (A.Unit) u: + | u-one(_) => 0 + | u-base(_, id) => + if id == target-id: 1 else: 0 end + | u-mul(_, _, lhs, rhs) => unit-power(target-id, lhs) + unit-power(target-id, rhs) + | u-div(_, _, lhs, rhs) => unit-power(target-id, lhs) + (-1 * unit-power(target-id, rhs)) + | u-pow(_, _, shadow u, n) => unit-power(target-id, u) * n + | u-paren(_, _, shadow u, n) => unit-power(target-id, u) + end +end + fun anf-unit(u-maybe :: Option) -> N.AUnit: - # TODO: Fill this in - N.a-unit-one + cases(Option) u-maybe: + | none => N.a-unit-one + | some(u) => + unit-names(u).fold( + lam(acc, id): + power = unit-power(id, u) + if power == 0: acc else: N.a-unit-one(id, power, acc) end + end, + N.a-unit-one) + end end fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: diff --git a/src/arr/compiler/ast-anf.arr b/src/arr/compiler/ast-anf.arr index 521e9aae1e..6046a84cc7 100644 --- a/src/arr/compiler/ast-anf.arr +++ b/src/arr/compiler/ast-anf.arr @@ -67,7 +67,7 @@ end data AUnit: | a-unit-one with: method tosource(self): PP.str("") end - | a-unit-name(l :: Loc, id :: A.Name, power :: Number, rest :: AUnit) with: + | a-unit-name(id :: A.Name, power :: Number, rest :: AUnit) with: method tosource(self): PP.separate(str-space, [list: self.id.tosource(), str-caret, PP.number(self.power), self.rest.tosource()]) @@ -480,7 +480,7 @@ data AVal: method tosource(self): cases(AUnit) self.u: | a-unit-one(_) => PP.number(self.n) - | a-unit-name(_, _, _, _) => PP.separate(str-percent, + | a-unit-name(_, _, _) => PP.separate(str-percent, [list: PP.number(self.n), PP.surround(INDENT, 0, PP.langle, self.u.tosource(), PP.rangle)]) end end @@ -594,14 +594,6 @@ fun strip-loc-field(field :: AField): end end -fun strip-loc-unit(u :: AUnit): - cases(AUnit) u: - | a-unit-one => a-unit-one - | a-unit-name(_, id, power, rest) => - a-unit-name(dummy-loc, id, power, strip-loc-unit(rest)) - end -end - fun strip-loc-val(val :: AVal): cases(AVal) val: | a-srcloc(_, l) => a-srcloc(dummy-loc, l) From f8a0548238e16f95cdb7dd7038f4fdcda4d974a1 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 30 May 2019 18:10:31 -0400 Subject: [PATCH 11/84] WIP: Why won't this fail? --- src/arr/compiler/anf-loop-compiler.arr | 23 ++- src/arr/compiler/anf.arr | 10 +- src/js/base/js-numbers.js | 261 ++++++++++++++++++++++++- src/js/base/runtime.js | 13 +- src/js/trove/parse-pyret.js | 6 +- src/js/trove/s-exp.js | 2 +- t.arr | 4 +- tests/pyret/main2.arr | 3 + tests/pyret/tests/test-units.arr | 4 + 9 files changed, 308 insertions(+), 18 deletions(-) create mode 100644 tests/pyret/tests/test-units.arr diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index b3eb26d11a..4a0aa67222 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -1325,6 +1325,14 @@ fun is-id-fn-name(flatness-env :: D.MutableStringDict>, name :: S flatness-env.has-key-now(name) end +fun compile-unit(u :: N.AUnit, acc :: CList) -> CList: + cases(N.AUnit) u: + | a-unit-one => J.j-obj(acc) + | a-unit-name(id, power, rest) => + compile-unit(rest, CL.concat-cons(j-field(tostring(u), j-num(power)), acc)) + end +end + fun compile-a-app(l :: N.Loc, f :: N.AVal, args :: List, compiler, b :: Option, @@ -1639,7 +1647,9 @@ compiler-visitor = { )) method-expr = if len < 9: rt-method(string-append("makeMethod", tostring(len - 1)), [clist: j-id(temp-full), j-str(name)]) - else: + else: #raise("don't do that") + # TODO(benmusch): what's going on here? Why did I need to comment that + # line? rt-method("makeMethodN", [clist: j-id(temp-full), j-str(name)]) end c-exp(method-expr, [clist: full-var]) @@ -1667,11 +1677,14 @@ compiler-visitor = { method a-srcloc(self, l, loc): c-exp(self.get-loc(loc), cl-empty) end, - method a-num(self, l :: Loc, n :: Number, u :: N.AUnit): - if num-is-fixnum(n): - c-exp(j-parens(j-num(n)), cl-empty) + method a-num(self, l :: Loc, n :: Number, u :: N.AUnit) block: + print("\n...hellooo....\n") + 1 + "" + if num-is-fixnum(n) and N.is-a-unit-one(u): + c-exp(j-parens(j-num(100)), cl-empty) else: - c-exp(rt-method("makeNumberFromString", [clist: j-str(tostring(n))]), cl-empty) + args = [clist: j-str("1000"), compile-unit(u, CL.empty)] + c-exp(rt-method("makeNumberFromString", args), cl-empty) end end, method a-str(self, l :: Loc, s :: String): diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index d5e64f7436..ef6544210f 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -174,13 +174,14 @@ fun anf-unit(u-maybe :: Option) -> N.AUnit: unit-names(u).fold( lam(acc, id): power = unit-power(id, u) - if power == 0: acc else: N.a-unit-one(id, power, acc) end + if power == 0: acc else: N.a-unit-name(id, power, acc) end end, N.a-unit-one) end end -fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: +fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: + 1 + "abc" cases(A.Expr) e: | s-module(l, answer, dvs, dts, provides, types, checks) => adts = for map(dt from dts): @@ -205,7 +206,10 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: end) | s-num(l, n, u-maybe) => - k(N.a-val(l, N.a-num(l, n, anf-unit(u-maybe)))) + block: + 1 + "abc" + k(N.a-val(l, N.a-num(l, n, anf-unit(u-maybe)))) + end # num, den are exact ints, and s-frac desugars to the exact rational num/den | s-frac(l, num, den) => k(N.a-val(l, N.a-num(l, num / den, N.a-unit-one))) # Possibly unneeded if removed by desugar? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index d89c0b96ee..a16ee45830 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -98,7 +98,7 @@ pyretnum := fixnum | boxnum A fixnum is simply a JS double, and we prefer to use them whenever possible, viz., for integers that are small enough. -boxnum := BigInteger | Rational | Roughnum. +boxnum := BigInteger | Rational | Roughnum | Unitnum. An integer is either a fixnum or a BigInteger. @@ -113,6 +113,7 @@ define("pyret-base/js/js-numbers", function() { // Creates a binary function that works either on fixnums or boxnums. // Applies the appropriate binary function, ensuring that both pyretnums are // coerced to be the same kind. + // TODO(benmusch): integrate units here var makeNumericBinop = function(onFixnums, onBoxednums, options) { options = options || {}; return function(x, y, errbacks) { @@ -240,7 +241,8 @@ define("pyret-base/js/js-numbers", function() { return (typeof(thing) === 'number' || (thing instanceof Rational || thing instanceof Roughnum || - thing instanceof BigInteger)); + thing instanceof BigInteger || + thing instanceof Unitnum)); }; // isRational: pyretnum -> boolean @@ -1046,6 +1048,85 @@ define("pyret-base/js/js-numbers", function() { ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// + // Unit operations + + var _removeUnitFromNumber = function(n) { + if (n instanceof Unitnum) { + return _removeUnitFromNumber(n.n); + } else { + return n; + } + } + + var _unitToString = function(u) { + unitStrs = []; + for (unitName in Object.keys(u)) { + power = u[unitName]; + if (power === 1) { + unitStrs = unitStrs.concat(unitName) + } else if (power !== 0) { + unitStrs = unitStrs.concat(unitName + " ^ " + power) + } + } + + if (unitStrs.length === 0) { + return "1"; + } else { + return unitStrs.join(" * "); + } + }; + + var _unitOf = function(n) { + if (n instanceof Unitnum) { + return n.u; + } else { + return {}; + } + }; + + var _unitExponentOf = function(u, key) { + if (key in u) { + return u[key]; + } else { + return 0; + } + }; + + var _unitMap = function(u, f) { + newUnit = {}; + for (unitName in Object.keys(u)) { + newUnit[unitName] = f(u[unitName]); + } + return newUnit; + } + + var _unitEquals = function(u1, u2) { + for (unitName in Object.keys(Object.assign({}, u1, u2))) { + if (_unitExponentOf(u1, unitName) !== _unitExponentOf(u2, unitName)) { + return false; + } + } + return true; + }; + + var _unitMerge = function(u1, u2) { + newUnit = {}; + for (unitName in Object.keys(Object.assign({}, u1, u2))) { + newUnit[unitName] = _unitExponentOf(u1, unitName) + _unitExponentOf(u2, unitName); + } + return newUnit; + } + + var _unitInvert = function(u) { + return _unitMap(u, function(n) { return -1 * n }) + } + + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + // Integer operations // Integers are either represented as fixnums or as BigIntegers. @@ -1426,6 +1507,182 @@ define("pyret-base/js/js-numbers", function() { ////////////////////////////////////////////////////////////////////// + // Unitnums + var Unitnum = function(n, u) { + this.n = n; + this.u = u; + }; + + Unitnum.prototype.toString = function() { + unitStr = "<" + _unitToString(this.u) + ">" + if (typeof(this.n) === "number") { + return this.n.toString(10) + unitStr; + } else { + return this.n.toString() + unitStr; + } + }; + + Unitnum.prototype.isFinite = function() { + return typeof(this.n) === "number" || this.n.isFinite(); + }; + + Unitnum.prototype.isInteger = function() { + return isInteger(this.n); + }; + + Unitnum.prototype.isRational = function() { + return isRational(this.n); + }; + + Unitnum.prototype.isExact = Unitnum.prototype.isRational; + + Unitnum.prototype.toRational = function() { + return Unitnum(toRational(this.n), this.u); + } + + Unitnum.prototype.toExact = Unitnum.prototype.toRational; + + Unitnum.prototype.toRoughnum = function() { + return Unitnum(toRoughnum(this.n), u); + }; + + Unitnum.prototype.toFixnum = function() { + return this.n; + }; + + Unitnum.prototype.greaterThan = function(n) { + // TODO(benmusch): Should this check for unit mis-match? + return greaterThan(this.n, n); + }; + + Unitnum.prototype.greaterThanOrEqual = function(n) { + // TODO(benmusch): Should this check for unit mis-match? + return greaterThanOrEqual(this.n, n); + }; + + Unitnum.prototype.lessThan = function(n) { + // TODO(benmusch): Should this check for unit mis-match? + return lessThan(this.n, n); + }; + + Unitnum.prototype.lessThanOrEqual = function(n) { + // TODO(benmusch): Should this check for unit mis-match? + return lessThanOrEqual(this.n, n); + }; + + Unitnum.prototype.add = function(n) { + console.log("Oh hello") + if (!_unitEquals(this.u, _unitOf(n))) { + return 10000000000; + } else { + result = add(_removeUnitFromNumber(this.n), _removeUnitFromNumber(n)); + return Unitnum(result, this.u); + } + }; + + Unitnum.prototype.subtract = function(n) { + if (!_unitEquals(this.u, _unitOf(n))) { + return 10000000000; + } else { + result = subtract(_removeUnitFromNumber(this.n), _removeUnitFromNumber(n)); + return Unitnum(result, this.u); + } + }; + + Unitnum.prototype.multiply = function(n) { + result = subtract(_removeUnitFromNumber(this.n), _removeUnitFromNumber(n)); + newUnit = _unitMerge(_unitOf(this), _unitOf(n)); + return Unitnum(result, newUnit); + }; + + Unitnum.prototype.divide = function(n) { + result = subtract(_removeUnitFromNumber(this.n), _removeUnitFromNumber(n)); + newUnit = _unitMerge(_unitOf(this), _unitInvert(_unitOf(n))); + return Unitnum(result, newUnit); + }; + + Unitnum.prototype.numerator = function() { + return Unitnum(numerator(this.n), this.u); + }; + + Unitnum.prototype.denominator = function() { + return Unitnum(denominator(this.n), this.u); + }; + + Unitnum.prototype.integerSqrt = function() { + // TODO: Check valid units for this + newUnit = _unitMap(this.u, function(n) { return n / 2 }); + return Unitnum(integerSqrt(this.n), newUnit); + }; + + Unitnum.prototype.sqrt = function() { + // TODO: Check valid units for this + newUnit = _unitMap(this.u, function(n) { return n / 2 }); + return Unitnum(sqrt(this.n), newUnit); + }; + + Unitnum.prototype.floor = function() { + return Unitnum(floor(this.n), this.u); + }; + + Unitnum.prototype.floor = function() { + return Unitnum(floor(this.n), this.u); + }; + + Unitnum.prototype.ceiling = function() { + return Unitnum(ceiling(this.n), this.u); + }; + + Unitnum.prototype.ceiling = function() { + // TODO(benmush): how should this behave??? + return Unitnum(log(this.n), this.u); + }; + + Unitnum.prototype.atan = function() { + // TODO(benmush): how should this behave??? + return Unitnum(atan(this.n), this.u); + }; + + Unitnum.prototype.cos = function() { + // TODO(benmush): how should this behave??? + return Unitnum(cos(this.n), this.u); + }; + + Unitnum.prototype.sin = function() { + // TODO(benmush): how should this behave??? + return Unitnum(sin(this.n), this.u); + }; + + Unitnum.prototype.expt = function(n) { + // TODO(benmush): What even is this vs exp??? + newUnit = _unitMap(this.u, function(pow) { n * pow }); + return Unitnum(expt(this.n), newUnit); + }; + + Unitnum.prototype.exp = function() { + // TODO(benmush): What even is this vs expt??? + return Unitnum(exp(this.n), this.u); + }; + + Unitnum.prototype.acos = function() { + // TODO(benmush): how should this behave??? + return Unitnum(acos(this.n), this.u); + }; + + Unitnum.prototype.asin = function() { + // TODO(benmush): how should this behave??? + return Unitnum(asin(this.n), this.u); + }; + + Unitnum.prototype.round = function() { + return Unitnum(round(this.n), this.u); + }; + + Unitnum.prototype.equals = function(other) { + return _unitEquals(_unitOf(this), _unitOf(other)) && + equals(this.n, _removeUnitFromNumber(other)); + }; + // Rationals var Rational = function(n, d) { diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 2c3e17fa70..4d8f024a96 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -617,14 +617,21 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom /**Makes a PNumber using the given string @param {string} s - @return {!PNumber} with value n + @param {unit} u + @return {!PNumber} with value n and unit u */ - function makeNumberFromString(s) { + function makeNumberFromString(s, u) { + console.log("unit: ", u) var result = jsnums.fromString(s, NumberErrbacks); if(result === false) { thisRuntime.ffi.throwMessageException("Could not create number from: " + s); } - return result; + + if (Object.keys(u).length === 0) { + return result; + } else { + return Unitnum(result, u) + } } /********************* diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 79a60d13fa..8f7dff2c8a 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -104,7 +104,7 @@ else return RUNTIME.makeString(tok.value.slice(1, -1)); } - function number(tok) { return RUNTIME.makeNumberFromString(tok.value); } + function number(tok) { return RUNTIME.makeNumberFromString(tok.value, {}); } const translators = { 'program': function(node) { var prelude = tr(node.kids[0]); @@ -1186,13 +1186,13 @@ // (frac-expr n) var numden = node.kids[0].value.split("/"); return RUNTIME.getField(ast, 's-frac') - .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0]), RUNTIME.makeNumberFromString(numden[1])); + .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1])); }, 'rfrac-expr': function(node) { // (rfrac-expr n) var numden = node.kids[0].value.substring(1).split("/"); return RUNTIME.getField(ast, 's-rfrac') - .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0]), RUNTIME.makeNumberFromString(numden[1])); + .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1])); }, 'string-expr': function(node) { return RUNTIME.getField(ast, 's-str') diff --git a/src/js/trove/s-exp.js b/src/js/trove/s-exp.js index 70336a4025..3803c6ae9a 100644 --- a/src/js/trove/s-exp.js +++ b/src/js/trove/s-exp.js @@ -19,7 +19,7 @@ var sSym = gf(vals, "s-sym"); var list = function(l) { return sList.app(RUNTIME.ffi.makeList(l)); } var str = function(s) { return sStr.app(RUNTIME.makeString(s)); } - var num = function(nstr) { return sNum.app(RUNTIME.makeNumberFromString(nstr)); } + var num = function(nstr) { return sNum.app(RUNTIME.makeNumberFromString(nstr, {})); } var sym = function(x) { return sSym.app(RUNTIME.makeString(x)); } function convert(v) { if(v instanceof String) { diff --git a/t.arr b/t.arr index 3aad08ab5c..7054d1ff39 100644 --- a/t.arr +++ b/t.arr @@ -1 +1,3 @@ -2%<(m ^ 1) / o> +var result = 1% + 1% +print(result) +print("\n") diff --git a/tests/pyret/main2.arr b/tests/pyret/main2.arr index 81f206a374..a01a08d62e 100644 --- a/tests/pyret/main2.arr +++ b/tests/pyret/main2.arr @@ -1,3 +1,4 @@ +#| import file("./tests/test-strings.arr") as _ import file("./tests/test-format.arr") as _ import file("./tests/test-numbers.arr") as _ @@ -41,3 +42,5 @@ import file("./tests/test-tail-call.arr") as _ import file("./tests/test-parse.arr") as _ import file("./tests/test-parse-errors.arr") as _ import file("./tests/test-flatness.arr") as _ +|# +import file("./tests/test-units.arr") as _ diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr new file mode 100644 index 0000000000..8a93b51d4b --- /dev/null +++ b/tests/pyret/tests/test-units.arr @@ -0,0 +1,4 @@ +check: + 2% + 2% is 4% + (2% + 2%) is 4 +end From 25d0aa73d1efef6a88d64d46ca9def3fce19b89f Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 30 May 2019 20:54:55 -0400 Subject: [PATCH 12/84] Some stuff is working, code needs cleanup and more thorough tests --- build/phase0/js/runtime.js | 1 + src/arr/compiler/anf-loop-compiler.arr | 10 +- src/arr/compiler/anf.arr | 6 +- src/js/base/js-numbers.js | 188 +++++++++++++++---------- src/js/base/runtime.js | 4 +- t.arr | 2 +- 6 files changed, 124 insertions(+), 87 deletions(-) diff --git a/build/phase0/js/runtime.js b/build/phase0/js/runtime.js index cd3742b3e1..378726505d 100644 --- a/build/phase0/js/runtime.js +++ b/build/phase0/js/runtime.js @@ -3791,6 +3791,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throwSqrtNegative: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwLogNonPositive: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwIncomparableValues: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, + throwIncompatibleUnits: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwInternalError: function(msg) { thisRuntime.ffi.throwInternalError(msg); }, }; diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index 4a0aa67222..8adb2e2db7 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -1325,11 +1325,11 @@ fun is-id-fn-name(flatness-env :: D.MutableStringDict>, name :: S flatness-env.has-key-now(name) end -fun compile-unit(u :: N.AUnit, acc :: CList) -> CList: +fun compile-unit(u :: N.AUnit, acc :: CList) -> J.JExpr: cases(N.AUnit) u: | a-unit-one => J.j-obj(acc) | a-unit-name(id, power, rest) => - compile-unit(rest, CL.concat-cons(j-field(tostring(u), j-num(power)), acc)) + compile-unit(rest, CL.concat-cons(j-field(tostring(id), j-num(power)), acc)) end end @@ -1678,12 +1678,10 @@ compiler-visitor = { c-exp(self.get-loc(loc), cl-empty) end, method a-num(self, l :: Loc, n :: Number, u :: N.AUnit) block: - print("\n...hellooo....\n") - 1 + "" if num-is-fixnum(n) and N.is-a-unit-one(u): - c-exp(j-parens(j-num(100)), cl-empty) + c-exp(j-parens(j-num(n)), cl-empty) else: - args = [clist: j-str("1000"), compile-unit(u, CL.empty)] + args = [clist: j-str(tostring(n)), compile-unit(u, CL.concat-empty)] c-exp(rt-method("makeNumberFromString", args), cl-empty) end end, diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index ef6544210f..97604df647 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -23,7 +23,7 @@ fun mk-id(loc, base): { id: t, id-b: bind(loc, t), id-e: N.a-id(loc, t) } end -fun anf-term(e :: A.Expr) -> N.AExpr: +fun anf-term(e :: A.Expr) -> N.AExpr block: anf(e, lam(x): N.a-lettable(x.l, x) end) end @@ -181,7 +181,6 @@ fun anf-unit(u-maybe :: Option) -> N.AUnit: end fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: - 1 + "abc" cases(A.Expr) e: | s-module(l, answer, dvs, dts, provides, types, checks) => adts = for map(dt from dts): @@ -206,10 +205,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: end) | s-num(l, n, u-maybe) => - block: - 1 + "abc" k(N.a-val(l, N.a-num(l, n, anf-unit(u-maybe)))) - end # num, den are exact ints, and s-frac desugars to the exact rational num/den | s-frac(l, num, den) => k(N.a-val(l, N.a-num(l, num / den, N.a-unit-one))) # Possibly unneeded if removed by desugar? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index a16ee45830..7fdd775f2c 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -117,15 +117,32 @@ define("pyret-base/js/js-numbers", function() { var makeNumericBinop = function(onFixnums, onBoxednums, options) { options = options || {}; return function(x, y, errbacks) { + var xUnits = _unitOf(x) + var yUnits = _unitOf(y) + if (options.forceSameUnits && !_unitEquals(xUnits, yUnits)) { + errbacks.throwIncompatibleUnits("Got incompatible units: " + + _unitToString(xUnits) + " and " + _unitToString(yUnits)); + } else { + console.log("Units were compatible", xUnits, yUnits, _unitEquals(xUnits, yUnits)); + } + + var getFinalUnits = options.getFinalUnits || function(x, y, errbacks) { return {} } + var ensureUnits = function(result) { + var u = getFinalUnits(xUnits, yUnits, errbacks); + return _withUnit(result, u); + } + + x = _withoutUnit(x) + y = _withoutUnit(y) if (options.isXSpecialCase && options.isXSpecialCase(x, errbacks)) - return options.onXSpecialCase(x, y, errbacks); + return ensureUnits(options.onXSpecialCase(x, y, errbacks)); if (options.isYSpecialCase && options.isYSpecialCase(y, errbacks)) - return options.onYSpecialCase(x, y, errbacks); + return ensureUnits(options.onYSpecialCase(x, y, errbacks)); if (typeof(x) === 'number' && typeof(y) === 'number') { - return onFixnums(x, y, errbacks); + return ensureUnits(onFixnums(x, y, errbacks)); } if (typeof(x) === 'number') { @@ -155,7 +172,7 @@ define("pyret-base/js/js-numbers", function() { x = new Rational(x, 1); } - return onBoxednums(x, y, errbacks); + return ensureUnits(onBoxednums(x, y, errbacks)); }; }; @@ -365,7 +382,9 @@ define("pyret-base/js/js-numbers", function() { onXSpecialCase: function(x, y, errbacks) { return y; }, isYSpecialCase: function(y, errbacks) { return isInteger(y) && _integerIsZero(y) }, - onYSpecialCase: function(x, y, errbacks) { return x; } + onYSpecialCase: function(x, y, errbacks) { return x; }, + forceSameUnits: true, + getFinalUnits: function(xUnits, yUnits, errbacks) { return xUnits } }); var subtract = function(x, y, errbacks) { @@ -398,7 +417,9 @@ define("pyret-base/js/js-numbers", function() { onXSpecialCase: function(x, y, errbacks) { return negate(y, errbacks); }, isYSpecialCase: function(y, errbacks) { return isInteger(y) && _integerIsZero(y) }, - onYSpecialCase: function(x, y, errbacks) { return x; } + onYSpecialCase: function(x, y, errbacks) { return x; }, + forceSameUnits: true, + getFinalUnits: function(xUnits, yUnits, errbacks) { return xUnits } }); // mulitply: pyretnum pyretnum -> pyretnum @@ -447,7 +468,8 @@ define("pyret-base/js/js-numbers", function() { return x; if (_integerIsNegativeOne(y)) return negate(x, errbacks); - } + }, + getFinalUnits: _unitMerge }); // divide: pyretnum pyretnum -> pyretnum @@ -485,6 +507,9 @@ define("pyret-base/js/js-numbers", function() { }, onYSpecialCase: function(x, y, errbacks) { errbacks.throwDivByZero("/: division by zero, " + x + ' ' + y); + }, + getFinalUnits: function(xUnits, yUnits, errbacks) { + return _unitMerge(xUnits, _unitInvert(yUnits)) } }); @@ -1050,18 +1075,30 @@ define("pyret-base/js/js-numbers", function() { // Unit operations - var _removeUnitFromNumber = function(n) { + var _withUnit = function(n, u) { + if (_unitEquals(u, {})) { + return n + } else if (n instanceof Unitnum) { + return new Unitnum(n.n, u); + } else { + return new Unitnum(n, u); + } + } + + var _withoutUnit = function(n) { if (n instanceof Unitnum) { - return _removeUnitFromNumber(n.n); + return _withoutUnit(n.n); } else { return n; } } var _unitToString = function(u) { - unitStrs = []; - for (unitName in Object.keys(u)) { - power = u[unitName]; + var unitStrs = []; + for (var unitName in u) { + if (!u.hasOwnProperty(unitName)) continue + + var power = u[unitName]; if (power === 1) { unitStrs = unitStrs.concat(unitName) } else if (power !== 0) { @@ -1085,7 +1122,7 @@ define("pyret-base/js/js-numbers", function() { }; var _unitExponentOf = function(u, key) { - if (key in u) { + if (u.hasOwnProperty(key)) { return u[key]; } else { return 0; @@ -1093,15 +1130,27 @@ define("pyret-base/js/js-numbers", function() { }; var _unitMap = function(u, f) { - newUnit = {}; - for (unitName in Object.keys(u)) { + var newUnit = {}; + for (var unitName in u) { + if (!u.hasOwnProperty(unitName)) continue newUnit[unitName] = f(u[unitName]); } return newUnit; } var _unitEquals = function(u1, u2) { - for (unitName in Object.keys(Object.assign({}, u1, u2))) { + for (var unitName in u1) { + if (!u1.hasOwnProperty(unitName)) continue + + if (_unitExponentOf(u1, unitName) !== _unitExponentOf(u2, unitName)) { + return false; + } + } + + // TODO(benmusch): speed this up by avoiding duplicate checks + for (var unitName in u2) { + if (!u2.hasOwnProperty(unitName)) continue + if (_unitExponentOf(u1, unitName) !== _unitExponentOf(u2, unitName)) { return false; } @@ -1110,8 +1159,10 @@ define("pyret-base/js/js-numbers", function() { }; var _unitMerge = function(u1, u2) { - newUnit = {}; - for (unitName in Object.keys(Object.assign({}, u1, u2))) { + var newUnit = {}; + for (var unitName in Object.assign({}, u1, u2)) { + if (!u1.hasOwnProperty(unitName) && !u2.hasOwnProperty(unitName)) continue + newUnit[unitName] = _unitExponentOf(u1, unitName) + _unitExponentOf(u2, unitName); } return newUnit; @@ -1168,6 +1219,7 @@ define("pyret-base/js/js-numbers", function() { var makeIntegerUnOp = function(onFixnums, onBignums, options, errbacks) { options = options || {}; return (function(m) { + m = _withoutUnit(m) if (m instanceof Rational) { m = numerator(m); } @@ -1220,7 +1272,7 @@ define("pyret-base/js/js-numbers", function() { return n === 0; }, function(n) { - return bnEquals.call(n, BigInteger.ZERO); + return bnEquals.call(_withoutUnit(n), BigInteger.ZERO); } ); @@ -1230,7 +1282,7 @@ define("pyret-base/js/js-numbers", function() { return n === 1; }, function(n) { - return bnEquals.call(n, BigInteger.ONE); + return bnEquals.call(_withoutUnit(n), BigInteger.ONE); }); // _integerIsNegativeOne: integer-pyretnum -> boolean @@ -1239,7 +1291,7 @@ define("pyret-base/js/js-numbers", function() { return n === -1; }, function(n) { - return bnEquals.call(n, BigInteger.NEGATIVE_ONE); + return bnEquals.call(_withoutUnit(n), BigInteger.NEGATIVE_ONE); }); // _integerAdd: integer-pyretnum integer-pyretnum -> integer-pyretnum @@ -1248,7 +1300,7 @@ define("pyret-base/js/js-numbers", function() { return m + n; }, function(m, n) { - return bnAdd.call(m, n); + return bnAdd.call(_withoutUnit(m), _withoutUnit(n)); }); // _integerSubtract: integer-pyretnum integer-pyretnum -> integer-pyretnum @@ -1257,7 +1309,7 @@ define("pyret-base/js/js-numbers", function() { return m - n; }, function(m, n) { - return bnSubtract.call(m, n); + return bnSubtract.call(_withoutUnit(m), _withoutUnit(n)); }); // _integerMultiply: integer-pyretnum integer-pyretnum -> integer-pyretnum @@ -1266,7 +1318,7 @@ define("pyret-base/js/js-numbers", function() { return m * n; }, function(m, n) { - return bnMultiply.call(m, n); + return bnMultiply.call(_withoutUnit(m), _withoutUnit(n)); }); //_integerQuotient: integer-pyretnum integer-pyretnum -> integer-pyretnum @@ -1275,7 +1327,7 @@ define("pyret-base/js/js-numbers", function() { return ((m - (m % n))/ n); }, function(m, n) { - return bnDivide.call(m, n); + return bnDivide.call(_withoutUnit(m), _withoutUnit(n)); }); var _integerRemainder = makeIntegerBinop( @@ -1283,7 +1335,7 @@ define("pyret-base/js/js-numbers", function() { return m % n; }, function(m, n) { - return bnRemainder.call(m, n); + return bnRemainder.call(_withoutUnit(m), _withoutUnit(n)); }); // splitIntIntoMantissaExpt: integer-pyretnum -> [JS-double, JS-int] @@ -1356,7 +1408,7 @@ define("pyret-base/js/js-numbers", function() { return m === n; }, function(m, n) { - return bnEquals.call(m, n); + return bnEquals.call(_withoutUnit(n), _withoutUnit(n)); }, {doNotCoerceToFloating: true}); @@ -1366,7 +1418,7 @@ define("pyret-base/js/js-numbers", function() { return m > n; }, function(m, n) { - return bnCompareTo.call(m, n) > 0; + return bnCompareTo.call(_withoutUnit(m), _withoutUnit(n)) > 0; }, {doNotCoerceToFloating: true}); @@ -1376,7 +1428,7 @@ define("pyret-base/js/js-numbers", function() { return m < n; }, function(m, n) { - return bnCompareTo.call(m, n) < 0; + return bnCompareTo.call(_withoutUnit(m), _withoutUnit(n)) < 0; }, {doNotCoerceToFloating: true}); @@ -1386,7 +1438,7 @@ define("pyret-base/js/js-numbers", function() { return m >= n; }, function(m, n) { - return bnCompareTo.call(m, n) >= 0; + return bnCompareTo.call(_withoutUnit(m), _withoutUnit(n)) >= 0; }, {doNotCoerceToFloating: true}); @@ -1396,7 +1448,7 @@ define("pyret-base/js/js-numbers", function() { return m <= n; }, function(m, n) { - return bnCompareTo.call(m, n) <= 0; + return bnCompareTo.call(_withoutUnit(m), _withoutUnit(n)) <= 0; }, {doNotCoerceToFloating: true}); @@ -1514,7 +1566,7 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.toString = function() { - unitStr = "<" + _unitToString(this.u) + ">" + var unitStr = "<" + _unitToString(this.u) + ">" if (typeof(this.n) === "number") { return this.n.toString(10) + unitStr; } else { @@ -1537,13 +1589,13 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.isExact = Unitnum.prototype.isRational; Unitnum.prototype.toRational = function() { - return Unitnum(toRational(this.n), this.u); + return new Unitnum(toRational(this.n), this.u); } Unitnum.prototype.toExact = Unitnum.prototype.toRational; Unitnum.prototype.toRoughnum = function() { - return Unitnum(toRoughnum(this.n), u); + return new Unitnum(toRoughnum(this.n), u); }; Unitnum.prototype.toFixnum = function() { @@ -1571,116 +1623,101 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.add = function(n) { - console.log("Oh hello") - if (!_unitEquals(this.u, _unitOf(n))) { - return 10000000000; - } else { - result = add(_removeUnitFromNumber(this.n), _removeUnitFromNumber(n)); - return Unitnum(result, this.u); - } + return add(this, n); }; Unitnum.prototype.subtract = function(n) { - if (!_unitEquals(this.u, _unitOf(n))) { - return 10000000000; - } else { - result = subtract(_removeUnitFromNumber(this.n), _removeUnitFromNumber(n)); - return Unitnum(result, this.u); - } + return subtract(this, n); }; Unitnum.prototype.multiply = function(n) { - result = subtract(_removeUnitFromNumber(this.n), _removeUnitFromNumber(n)); - newUnit = _unitMerge(_unitOf(this), _unitOf(n)); - return Unitnum(result, newUnit); + return multiply(this, n); }; Unitnum.prototype.divide = function(n) { - result = subtract(_removeUnitFromNumber(this.n), _removeUnitFromNumber(n)); - newUnit = _unitMerge(_unitOf(this), _unitInvert(_unitOf(n))); - return Unitnum(result, newUnit); + return divide(this, n); }; Unitnum.prototype.numerator = function() { - return Unitnum(numerator(this.n), this.u); + return new Unitnum(numerator(this.n), this.u); }; Unitnum.prototype.denominator = function() { - return Unitnum(denominator(this.n), this.u); + return new Unitnum(denominator(this.n), this.u); }; Unitnum.prototype.integerSqrt = function() { // TODO: Check valid units for this - newUnit = _unitMap(this.u, function(n) { return n / 2 }); - return Unitnum(integerSqrt(this.n), newUnit); + var newUnit = _unitMap(this.u, function(n) { return n / 2 }); + return new Unitnum(integerSqrt(this.n), newUnit); }; Unitnum.prototype.sqrt = function() { // TODO: Check valid units for this - newUnit = _unitMap(this.u, function(n) { return n / 2 }); - return Unitnum(sqrt(this.n), newUnit); + var newUnit = _unitMap(this.u, function(n) { return n / 2 }); + return new Unitnum(sqrt(this.n), newUnit); }; Unitnum.prototype.floor = function() { - return Unitnum(floor(this.n), this.u); + return new Unitnum(floor(this.n), this.u); }; Unitnum.prototype.floor = function() { - return Unitnum(floor(this.n), this.u); + return new Unitnum(floor(this.n), this.u); }; Unitnum.prototype.ceiling = function() { - return Unitnum(ceiling(this.n), this.u); + return new Unitnum(ceiling(this.n), this.u); }; Unitnum.prototype.ceiling = function() { // TODO(benmush): how should this behave??? - return Unitnum(log(this.n), this.u); + return new Unitnum(log(this.n), this.u); }; Unitnum.prototype.atan = function() { // TODO(benmush): how should this behave??? - return Unitnum(atan(this.n), this.u); + return new Unitnum(atan(this.n), this.u); }; Unitnum.prototype.cos = function() { // TODO(benmush): how should this behave??? - return Unitnum(cos(this.n), this.u); + return new Unitnum(cos(this.n), this.u); }; Unitnum.prototype.sin = function() { // TODO(benmush): how should this behave??? - return Unitnum(sin(this.n), this.u); + return new Unitnum(sin(this.n), this.u); }; Unitnum.prototype.expt = function(n) { // TODO(benmush): What even is this vs exp??? - newUnit = _unitMap(this.u, function(pow) { n * pow }); - return Unitnum(expt(this.n), newUnit); + var newUnit = _unitMap(this.u, function(pow) { n * pow }); + return new Unitnum(expt(this.n), newUnit); }; Unitnum.prototype.exp = function() { // TODO(benmush): What even is this vs expt??? - return Unitnum(exp(this.n), this.u); + return new Unitnum(exp(this.n), this.u); }; Unitnum.prototype.acos = function() { // TODO(benmush): how should this behave??? - return Unitnum(acos(this.n), this.u); + return new Unitnum(acos(this.n), this.u); }; Unitnum.prototype.asin = function() { // TODO(benmush): how should this behave??? - return Unitnum(asin(this.n), this.u); + return new Unitnum(asin(this.n), this.u); }; Unitnum.prototype.round = function() { - return Unitnum(round(this.n), this.u); + return new Unitnum(round(this.n), this.u); }; Unitnum.prototype.equals = function(other) { return _unitEquals(_unitOf(this), _unitOf(other)) && - equals(this.n, _removeUnitFromNumber(other)); + equals(this.n, _withoutUnit(other)); }; // Rationals @@ -2362,6 +2399,10 @@ define("pyret-base/js/js-numbers", function() { }; + var addUnit = function(n, u) { + return new Unitnum(n, u); + }; + /////////////////////////////////////////////////////////// // recognizing numbers in (We)Scheme syntax: @@ -4221,6 +4262,7 @@ define("pyret-base/js/js-numbers", function() { Numbers['fromFixnum'] = fromFixnum; Numbers['fromString'] = fromString; + Numbers['addUnit'] = addUnit; Numbers['fromSchemeString'] = fromSchemeString; Numbers['makeBignum'] = makeBignum; Numbers['makeRational'] = Rational.makeInstance; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 4d8f024a96..4bd3e1da95 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -621,7 +621,6 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom @return {!PNumber} with value n and unit u */ function makeNumberFromString(s, u) { - console.log("unit: ", u) var result = jsnums.fromString(s, NumberErrbacks); if(result === false) { thisRuntime.ffi.throwMessageException("Could not create number from: " + s); @@ -630,7 +629,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if (Object.keys(u).length === 0) { return result; } else { - return Unitnum(result, u) + return jsnums.addUnit(result, u) } } @@ -3868,6 +3867,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throwSqrtNegative: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwLogNonPositive: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwIncomparableValues: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, + throwIncompatibleUnits: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwInternalError: function(msg) { thisRuntime.ffi.throwInternalError(msg); }, }; diff --git a/t.arr b/t.arr index 7054d1ff39..63152a947f 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,3 @@ -var result = 1% + 1% +var result = 1% + 1% print(result) print("\n") From 274d27fda29b4526f65c4917e839f21fd79479ca Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 30 May 2019 20:58:57 -0400 Subject: [PATCH 13/84] Remove a print --- src/js/base/js-numbers.js | 2 -- t.arr | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 7fdd775f2c..f362cbb0be 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -123,8 +123,6 @@ define("pyret-base/js/js-numbers", function() { if (options.forceSameUnits && !_unitEquals(xUnits, yUnits)) { errbacks.throwIncompatibleUnits("Got incompatible units: " + _unitToString(xUnits) + " and " + _unitToString(yUnits)); - } else { - console.log("Units were compatible", xUnits, yUnits, _unitEquals(xUnits, yUnits)); } var getFinalUnits = options.getFinalUnits || function(x, y, errbacks) { return {} } diff --git a/t.arr b/t.arr index 63152a947f..e96a7ceab9 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,3 @@ -var result = 1% + 1% +var result = 1% / 1% print(result) print("\n") From 1ad0ef84a4e829a4c37b81567caec536afbbf145 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 30 May 2019 22:30:12 -0400 Subject: [PATCH 14/84] Remove an is-operation that wasn't being used --- src/arr/trove/ast.arr | 1 - 1 file changed, 1 deletion(-) diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index bb80933f92..3158bfecfd 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -1653,7 +1653,6 @@ data Unit: end | u-pow(l :: Loc, op-l :: Loc, u :: Unit, n :: Number) with: method label(self): "u-pow" end, - method is-operation(self): true end, method tosource(self): PP.separate(str-space, [list: self.u.tosource(), str-caret, PP.number(self.n)]) end From 0e9bc039de8379ec1028cb93116010833c8dd24c Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Sat, 1 Jun 2019 11:12:15 -0400 Subject: [PATCH 15/84] Delegate more responsibility to the Unitnums --- src/js/base/js-numbers.js | 91 ++++++++++++++++++--------------------- t.arr | 8 +++- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index f362cbb0be..d1afc9df8b 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -113,34 +113,17 @@ define("pyret-base/js/js-numbers", function() { // Creates a binary function that works either on fixnums or boxnums. // Applies the appropriate binary function, ensuring that both pyretnums are // coerced to be the same kind. - // TODO(benmusch): integrate units here var makeNumericBinop = function(onFixnums, onBoxednums, options) { options = options || {}; return function(x, y, errbacks) { - var xUnits = _unitOf(x) - var yUnits = _unitOf(y) - - if (options.forceSameUnits && !_unitEquals(xUnits, yUnits)) { - errbacks.throwIncompatibleUnits("Got incompatible units: " + - _unitToString(xUnits) + " and " + _unitToString(yUnits)); - } - - var getFinalUnits = options.getFinalUnits || function(x, y, errbacks) { return {} } - var ensureUnits = function(result) { - var u = getFinalUnits(xUnits, yUnits, errbacks); - return _withUnit(result, u); - } - - x = _withoutUnit(x) - y = _withoutUnit(y) if (options.isXSpecialCase && options.isXSpecialCase(x, errbacks)) - return ensureUnits(options.onXSpecialCase(x, y, errbacks)); + return options.onXSpecialCase(x, y, errbacks); if (options.isYSpecialCase && options.isYSpecialCase(y, errbacks)) - return ensureUnits(options.onYSpecialCase(x, y, errbacks)); + return options.onYSpecialCase(x, y, errbacks); if (typeof(x) === 'number' && typeof(y) === 'number') { - return ensureUnits(onFixnums(x, y, errbacks)); + return onFixnums(x, y, errbacks); } if (typeof(x) === 'number') { @@ -150,7 +133,11 @@ define("pyret-base/js/js-numbers", function() { y = liftFixnumInteger(y, x); } - if (x instanceof Roughnum) { + if (x instanceof Unitnum || y instanceof Unitnum) { + // if x or y have units, ensure they are both wrapped in unitnums + x = _withUnit(x, _unitOf(x)); + y = _withUnit(y, _unitOf(y)); + } else if (x instanceof Roughnum) { // y is rough, rat or bigint if (!(y instanceof Roughnum)) { // y is rat or bigint @@ -170,7 +157,7 @@ define("pyret-base/js/js-numbers", function() { x = new Rational(x, 1); } - return ensureUnits(onBoxednums(x, y, errbacks)); + return onBoxednums(x, y, errbacks); }; }; @@ -373,16 +360,14 @@ define("pyret-base/js/js-numbers", function() { } }, function(x, y, errbacks) { - return x.add(y); + return x.add(y, errbacks); }, {isXSpecialCase: function(x, errbacks) { return isInteger(x) && _integerIsZero(x) }, onXSpecialCase: function(x, y, errbacks) { return y; }, isYSpecialCase: function(y, errbacks) { return isInteger(y) && _integerIsZero(y) }, - onYSpecialCase: function(x, y, errbacks) { return x; }, - forceSameUnits: true, - getFinalUnits: function(xUnits, yUnits, errbacks) { return xUnits } + onYSpecialCase: function(x, y, errbacks) { return x; } }); var subtract = function(x, y, errbacks) { @@ -408,16 +393,14 @@ define("pyret-base/js/js-numbers", function() { } }, function(x, y, errbacks) { - return x.subtract(y); + return x.subtract(y, errbacks); }, {isXSpecialCase: function(x, errbacks) { return isInteger(x) && _integerIsZero(x) }, onXSpecialCase: function(x, y, errbacks) { return negate(y, errbacks); }, isYSpecialCase: function(y, errbacks) { return isInteger(y) && _integerIsZero(y) }, - onYSpecialCase: function(x, y, errbacks) { return x; }, - forceSameUnits: true, - getFinalUnits: function(xUnits, yUnits, errbacks) { return xUnits } + onYSpecialCase: function(x, y, errbacks) { return x; } }); // mulitply: pyretnum pyretnum -> pyretnum @@ -446,7 +429,7 @@ define("pyret-base/js/js-numbers", function() { return x.multiply(y, errbacks); }, {isXSpecialCase: function(x, errbacks) { - return (isInteger(x) && + return (isInteger(x) && !(x instanceof Unitnum) && (_integerIsZero(x) || _integerIsOne(x) || _integerIsNegativeOne(x))) }, onXSpecialCase: function(x, y, errbacks) { if (_integerIsZero(x)) @@ -457,7 +440,7 @@ define("pyret-base/js/js-numbers", function() { return negate(y, errbacks); }, isYSpecialCase: function(y, errbacks) { - return (isInteger(y) && + return (isInteger(y) && !(y instanceof Unitnum) && (_integerIsZero(y) || _integerIsOne(y) || _integerIsNegativeOne(y)))}, onYSpecialCase: function(x, y, errbacks) { if (_integerIsZero(y)) @@ -466,8 +449,7 @@ define("pyret-base/js/js-numbers", function() { return x; if (_integerIsNegativeOne(y)) return negate(x, errbacks); - }, - getFinalUnits: _unitMerge + } }); // divide: pyretnum pyretnum -> pyretnum @@ -505,9 +487,6 @@ define("pyret-base/js/js-numbers", function() { }, onYSpecialCase: function(x, y, errbacks) { errbacks.throwDivByZero("/: division by zero, " + x + ' ' + y); - }, - getFinalUnits: function(xUnits, yUnits, errbacks) { - return _unitMerge(xUnits, _unitInvert(yUnits)) } }); @@ -1074,9 +1053,7 @@ define("pyret-base/js/js-numbers", function() { // Unit operations var _withUnit = function(n, u) { - if (_unitEquals(u, {})) { - return n - } else if (n instanceof Unitnum) { + if (n instanceof Unitnum) { return new Unitnum(n.n, u); } else { return new Unitnum(n, u); @@ -1464,6 +1441,9 @@ define("pyret-base/js/js-numbers", function() { // isRational: -> boolean // Produce true if the number is rational. + // isRoughnum: -> boolean + // Produce true if the number is roughnum. + // isExact === isRational // isReal: -> boolean @@ -1584,6 +1564,10 @@ define("pyret-base/js/js-numbers", function() { return isRational(this.n); }; + Unitnum.prototype.isRoughnum = function() { + return isRoughnum(this.n); + }; + Unitnum.prototype.isExact = Unitnum.prototype.isRational; Unitnum.prototype.toRational = function() { @@ -1620,20 +1604,31 @@ define("pyret-base/js/js-numbers", function() { return lessThanOrEqual(this.n, n); }; - Unitnum.prototype.add = function(n) { - return add(this, n); + Unitnum.prototype.add = function(n, errbacks) { + if (!_unitEquals(this.u, n.u)) { + errbacks.throwIncompatibleUnits("Cannot add units: " + + _unitToString(this.u) + " and " + _unitToString(n.u)); + } + return _withUnit(add(_withoutUnit(this), _withoutUnit(n)), this.u); }; - Unitnum.prototype.subtract = function(n) { - return subtract(this, n); + Unitnum.prototype.subtract = function(n, errbacks) { + if (!_unitEquals(this.u, n.u)) { + errbacks.throwIncompatibleUnits("Cannot add units: " + + _unitToString(this.u) + " and " + _unitToString(n.u)); + } + return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n)), this.u); }; - Unitnum.prototype.multiply = function(n) { - return multiply(this, n); + Unitnum.prototype.multiply = function(n, errbacks) { + var newUnit = _unitMerge(this.u, n.u); + // TODO(benmusch): This & divide should un-box the numbers if the unit is 1 + return _withUnit(multiply(_withoutUnit(this), _withoutUnit(n)), newUnit); }; - Unitnum.prototype.divide = function(n) { - return divide(this, n); + Unitnum.prototype.divide = function(n, errbacks) { + var newUnit = _unitMerge(this.u, _unitInvert(n.u)); + return _withUnit(divide(_withoutUnit(this), _withoutUnit(n)), newUnit); }; Unitnum.prototype.numerator = function() { diff --git a/t.arr b/t.arr index e96a7ceab9..fe913e455e 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,7 @@ -var result = 1% / 1% -print(result) +print(1% / 1%) print("\n") +print(1% * 1%) +print("\n") +print(1% + 1%) +print("\n") +print(1% - 1%) From 6ce0ce054763e82c3ac579ece0429eeab2c76b01 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Sat, 1 Jun 2019 11:40:24 -0400 Subject: [PATCH 16/84] Fix a bug --- src/js/base/js-numbers.js | 8 ++++---- t.arr | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index d1afc9df8b..d8c9613d9e 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1609,7 +1609,7 @@ define("pyret-base/js/js-numbers", function() { errbacks.throwIncompatibleUnits("Cannot add units: " + _unitToString(this.u) + " and " + _unitToString(n.u)); } - return _withUnit(add(_withoutUnit(this), _withoutUnit(n)), this.u); + return _withUnit(add(_withoutUnit(this), _withoutUnit(n), errbacks), this.u); }; Unitnum.prototype.subtract = function(n, errbacks) { @@ -1617,18 +1617,18 @@ define("pyret-base/js/js-numbers", function() { errbacks.throwIncompatibleUnits("Cannot add units: " + _unitToString(this.u) + " and " + _unitToString(n.u)); } - return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n)), this.u); + return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n), errbacks), this.u); }; Unitnum.prototype.multiply = function(n, errbacks) { var newUnit = _unitMerge(this.u, n.u); // TODO(benmusch): This & divide should un-box the numbers if the unit is 1 - return _withUnit(multiply(_withoutUnit(this), _withoutUnit(n)), newUnit); + return _withUnit(multiply(_withoutUnit(this), _withoutUnit(n), errbacks), newUnit); }; Unitnum.prototype.divide = function(n, errbacks) { var newUnit = _unitMerge(this.u, _unitInvert(n.u)); - return _withUnit(divide(_withoutUnit(this), _withoutUnit(n)), newUnit); + return _withUnit(divide(_withoutUnit(this), _withoutUnit(n), errbacks), newUnit); }; Unitnum.prototype.numerator = function() { diff --git a/t.arr b/t.arr index fe913e455e..a1fe8e3f22 100644 --- a/t.arr +++ b/t.arr @@ -4,4 +4,6 @@ print(1% * 1%) print("\n") print(1% + 1%) print("\n") -print(1% - 1%) +print(10 / 0%) +print("\n") +print(1 - 1%) From bdde18693148bb1728890960b803bb355dfb96eb Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 3 Jun 2019 10:03:56 -0400 Subject: [PATCH 17/84] Add some basic tests, fix a bug --- src/arr/compiler/anf.arr | 4 ++-- src/js/base/js-numbers.js | 8 +++++--- tests/pyret/tests/test-units.arr | 14 +++++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index 97604df647..559def83cf 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -151,7 +151,7 @@ fun unit-names(u :: A.Unit) -> Set: | u-mul(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) | u-div(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) | u-pow(_, _, shadow u, n) => unit-names(u) - | u-paren(_, _, shadow u, n) => unit-names(u) + | u-paren(_, shadow u) => unit-names(u) end end @@ -163,7 +163,7 @@ fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: | u-mul(_, _, lhs, rhs) => unit-power(target-id, lhs) + unit-power(target-id, rhs) | u-div(_, _, lhs, rhs) => unit-power(target-id, lhs) + (-1 * unit-power(target-id, rhs)) | u-pow(_, _, shadow u, n) => unit-power(target-id, u) * n - | u-paren(_, _, shadow u, n) => unit-power(target-id, u) + | u-paren(_, shadow u) => unit-power(target-id, u) end end diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index d8c9613d9e..f3332761fe 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -511,7 +511,7 @@ define("pyret-base/js/js-numbers", function() { var equalsAnyZero = function(x, errbacks) { if (typeof(x) === 'number') return x === 0; if (isRoughnum(x)) return x.n === 0; - return x.equals(0, errbacks); + return equals(x, 0, errbacks); }; // eqv: pyretnum pyretnum -> boolean @@ -1709,8 +1709,10 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.equals = function(other) { - return _unitEquals(_unitOf(this), _unitOf(other)) && - equals(this.n, _withoutUnit(other)); + // TODO(benmusch): should this support a polymorphic zero which ignores + // units? + return _unitEquals(this.u, _unitOf(other)) && + equals(this.n, other.n); }; // Rationals diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 8a93b51d4b..4cf7fb4aad 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -1,4 +1,16 @@ check: 2% + 2% is 4% - (2% + 2%) is 4 + (2% + 2%) raises "Cannot add units: m and s" + + 2% - 2% is 0% + (2% - 2%) raises "Cannot add units: m and s" + + 2% * 2% is 4% + 2% * 2% is 4 + 2% * 2% is 4% + 2 * 2% is 4% + + 2% / 2% is 1 + 2% / 2% is 1% + 2% / 2% is 1% end From 685c160e866f04a5efa1dbfa8a605b2a16c6ac87 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 3 Jun 2019 10:30:20 -0400 Subject: [PATCH 18/84] Fix grammar ambiguity, more tests --- src/js/base/pyret-grammar.bnf | 6 ++++-- src/js/trove/parse-pyret.js | 3 +-- tests/pyret/tests/test-units.arr | 8 ++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index f5ab384f96..c88a8eaf87 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -133,9 +133,11 @@ prim-expr: num-expr | frac-expr | rfrac-expr | bool-expr | string-expr dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) # TODO(benmusch): Consider adding | (LANGLE|LT) NUMBER (RANGLE|GT) unit-lhs: (PARENNOSPACE|PARENSPACE) unit-expr RPAREN - | unit-expr CARET NUMBER | NAME -unit-expr: unit-lhs | unit-lhs STAR unit-expr | unit-lhs SLASH unit-expr + | unit-lhs CARET NUMBER +unit-expr: unit-lhs + | unit-lhs STAR unit-expr + | unit-lhs SLASH unit-expr num-expr: NUMBER [PERCENT dim-expr] diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 8f7dff2c8a..fd05e690ed 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -977,8 +977,7 @@ if (node.kids.length === 1) { return RUNTIME.getField(ast, 'u-base') .app(pos(node.pos), name(node.kids[0])) - } else if (node.kids[1].name === 'CARET') { - // (unit-expr unit-lhs CARET unit-expr) + } else if (node.kids.length === 3 && node.kids[1].name === 'CARET') { return RUNTIME.getField(ast, 'u-pow') .app(pos(node.pos), pos(node.kids[1].pos), tr(node.kids[0]), number(node.kids[2])) } else { diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 4cf7fb4aad..422067d7ed 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -13,4 +13,12 @@ check: 2% / 2% is 1 2% / 2% is 1% 2% / 2% is 1% + + 2 == 2% is false + 2% == 2% is false + + 2% is 2% + 2 is 2% + 2% is 2% + 2%<(((s * m) ^ 2) / t) ^ 2> is 2% end From 0ea5f337925820d00efdf7e505ed4e608a512252 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 3 Jun 2019 13:53:28 -0400 Subject: [PATCH 19/84] WIP --- src/js/base/js-numbers.js | 126 +++++++++++++++++-------------- tests/pyret/tests/test-units.arr | 66 +++++++++++++--- 2 files changed, 126 insertions(+), 66 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index f3332761fe..0adabac005 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -601,7 +601,7 @@ define("pyret-base/js/js-numbers", function() { return x >= y; } return makeNumericBinop(undefined, function(x, y, errbacks) { - return x.greaterThanOrEqual(y); + return x.greaterThanOrEqual(y, errbacks); })(x, y, errbacks); } @@ -611,7 +611,7 @@ define("pyret-base/js/js-numbers", function() { return x <= y; } return makeNumericBinop(undefined, function(x, y, errbacks) { - return x.lessThanOrEqual(y); + return x.lessThanOrEqual(y, errbacks); })(x, y, errbacks); }; @@ -621,7 +621,7 @@ define("pyret-base/js/js-numbers", function() { return x > y; } return makeNumericBinop(undefined, function(x, y, errbacks) { - return x.greaterThan(y); + return x.greaterThan(y, errbacks); })(x, y, errbacks); }; @@ -631,7 +631,7 @@ define("pyret-base/js/js-numbers", function() { return x < y; } return makeNumericBinop(undefined, function(x, y, errbacks) { - return x.lessThan(y); + return x.lessThan(y, errbacks); })(x, y, errbacks); }; @@ -1147,6 +1147,26 @@ define("pyret-base/js/js-numbers", function() { return _unitMap(u, function(n) { return -1 * n }) } + var _ensureSameUnits = function(u1, u2, errbacks, opName) { + var msg; + if (opName !== undefined) { + msg = "Cannot perform " + opName + " operation due to unit mis-match: "; + } else { + msg = "Cannot perform operation due to unit mis-match: "; + } + msg += (_unitToString(u1) + " and " + _unitToString(u2)); + + if (!_unitEquals(u1, u2)) { + errbacks.throwIncompatibleUnits(msg); + } + } + + var _makeUnsupportedUnop = function(u, errbacks, opName) { + // TODO(benmusch): Use another errback function + errbacks.throwIncompatibleUnits("The " + opName + " operation does not support units" + + " but was given an argument with the unit " + _unitToString(u)) + } + ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// @@ -1581,42 +1601,36 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.toFixnum = function() { - return this.n; + return toFixnum(this.n); }; - Unitnum.prototype.greaterThan = function(n) { - // TODO(benmusch): Should this check for unit mis-match? - return greaterThan(this.n, n); + Unitnum.prototype.greaterThan = function(n, errbacks) { + _ensureSameUnits(this.u, _unitOf(n), errbacks, ">"); + return greaterThan(_withoutUnit(this.n), _withoutUnit(n)); }; - Unitnum.prototype.greaterThanOrEqual = function(n) { - // TODO(benmusch): Should this check for unit mis-match? - return greaterThanOrEqual(this.n, n); + Unitnum.prototype.greaterThanOrEqual = function(n, errbacks) { + _ensureSameUnits(this.u, _unitOf(n), errbacks, ">="); + return greaterThanOrEqual(_withoutUnit(this.n), _withoutUnit(n)); }; - Unitnum.prototype.lessThan = function(n) { - // TODO(benmusch): Should this check for unit mis-match? - return lessThan(this.n, n); + Unitnum.prototype.lessThan = function(n, errbacks) { + _ensureSameUnits(this.u, _unitOf(n), errbacks, "<"); + return lessThan(_withoutUnit(this.n), _withoutUnit(n)); }; - Unitnum.prototype.lessThanOrEqual = function(n) { - // TODO(benmusch): Should this check for unit mis-match? - return lessThanOrEqual(this.n, n); + Unitnum.prototype.lessThanOrEqual = function(n, errbacks) { + _ensureSameUnits(this.u, _unitOf(n), errbacks, "<="); + return lessThanOrEqual(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.add = function(n, errbacks) { - if (!_unitEquals(this.u, n.u)) { - errbacks.throwIncompatibleUnits("Cannot add units: " + - _unitToString(this.u) + " and " + _unitToString(n.u)); - } + _ensureSameUnits(this.u, _unitOf(n), errbacks, "+"); return _withUnit(add(_withoutUnit(this), _withoutUnit(n), errbacks), this.u); }; Unitnum.prototype.subtract = function(n, errbacks) { - if (!_unitEquals(this.u, n.u)) { - errbacks.throwIncompatibleUnits("Cannot add units: " + - _unitToString(this.u) + " and " + _unitToString(n.u)); - } + _ensureSameUnits(this.u, _unitOf(n), errbacks, "-"); return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n), errbacks), this.u); }; @@ -1631,28 +1645,28 @@ define("pyret-base/js/js-numbers", function() { return _withUnit(divide(_withoutUnit(this), _withoutUnit(n), errbacks), newUnit); }; - Unitnum.prototype.numerator = function() { + Unitnum.prototype.numerator = function(errbacks) { + // TODO(benmusch): How should this behave? Maybe remove the unit? return new Unitnum(numerator(this.n), this.u); }; - Unitnum.prototype.denominator = function() { + Unitnum.prototype.denominator = function(errbacks) { + // TODO(benmusch): How should this behave? Maybe remove the unit? return new Unitnum(denominator(this.n), this.u); }; - Unitnum.prototype.integerSqrt = function() { - // TODO: Check valid units for this - var newUnit = _unitMap(this.u, function(n) { return n / 2 }); - return new Unitnum(integerSqrt(this.n), newUnit); + Unitnum.prototype.integerSqrt = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "integer square root"); }; - Unitnum.prototype.sqrt = function() { - // TODO: Check valid units for this - var newUnit = _unitMap(this.u, function(n) { return n / 2 }); - return new Unitnum(sqrt(this.n), newUnit); + Unitnum.prototype.sqrt = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "square root"); }; - Unitnum.prototype.floor = function() { - return new Unitnum(floor(this.n), this.u); + Unitnum.prototype.abs = function() { + return new Unitnum(abs(this.n), this.u); }; Unitnum.prototype.floor = function() { @@ -1663,45 +1677,43 @@ define("pyret-base/js/js-numbers", function() { return new Unitnum(ceiling(this.n), this.u); }; - Unitnum.prototype.ceiling = function() { - // TODO(benmush): how should this behave??? - return new Unitnum(log(this.n), this.u); + Unitnum.prototype.log = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "log"); }; - Unitnum.prototype.atan = function() { - // TODO(benmush): how should this behave??? - return new Unitnum(atan(this.n), this.u); + Unitnum.prototype.atan = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "atan"); }; - Unitnum.prototype.cos = function() { - // TODO(benmush): how should this behave??? - return new Unitnum(cos(this.n), this.u); + Unitnum.prototype.cos = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "cos"); }; - Unitnum.prototype.sin = function() { - // TODO(benmush): how should this behave??? - return new Unitnum(sin(this.n), this.u); + Unitnum.prototype.sin = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "sin"); }; Unitnum.prototype.expt = function(n) { - // TODO(benmush): What even is this vs exp??? var newUnit = _unitMap(this.u, function(pow) { n * pow }); return new Unitnum(expt(this.n), newUnit); }; Unitnum.prototype.exp = function() { - // TODO(benmush): What even is this vs expt??? return new Unitnum(exp(this.n), this.u); }; - Unitnum.prototype.acos = function() { - // TODO(benmush): how should this behave??? - return new Unitnum(acos(this.n), this.u); + Unitnum.prototype.acos = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "acos"); }; - Unitnum.prototype.asin = function() { - // TODO(benmush): how should this behave??? - return new Unitnum(asin(this.n), this.u); + Unitnum.prototype.asin = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "asin"); }; Unitnum.prototype.round = function() { diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 422067d7ed..65a6c64c31 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -1,9 +1,19 @@ -check: +check "Unit equality": + 2 == 2% is false + 2% == 2% is false + + 2% is 2% + 2 is 2% + 2% is 2% + 2%<(((s * m) ^ 2) / t) ^ 2> is 2% +end + +check "Arithmetic binops": 2% + 2% is 4% - (2% + 2%) raises "Cannot add units: m and s" + (2% + 2%) raises "Cannot perform + operation due to unit mis-match: m and s" 2% - 2% is 0% - (2% - 2%) raises "Cannot add units: m and s" + (2% - 2%) raises "Cannot perform - operation due to unit mis-match: m and s" 2% * 2% is 4% 2% * 2% is 4 @@ -13,12 +23,50 @@ check: 2% / 2% is 1 2% / 2% is 1% 2% / 2% is 1% +end - 2 == 2% is false - 2% == 2% is false +check "Logical binops": + 2% > 1% is true + 2% > 2% is false + 2% > 2% raises "Cannot perform > operation due to unit mis-match: m and s" - 2% is 2% - 2 is 2% - 2% is 2% - 2%<(((s * m) ^ 2) / t) ^ 2> is 2% + 2% >= 2% is true + 2% >= 3% is false + 2% >= 2% raises "Cannot perform >= operation due to unit mis-match: m and s" + + 1% < 2% is true + 2% < 2% is false + 2% < 2% raises "Cannot perform < operation due to unit mis-match: m and s" + + 2% <= 2% is true + 2% <= 1% is false + 2% <= 2% raises "Cannot perform <= operation due to unit mis-match: m and s" +end + +check "Other supported operations": + #| + TODO: + numerator() + denominator() + abs() + floor() + expt(n) + round() + |# +end + +check "Unsupported unops": + #| + TODO: + integerSqrt() - is there any way to trigger this or is this unused? + |# + num-sqrt(2%) raises "sdkfjsdljfsld" + num-log(2%) raises "sdkfjsdljfsld" + num-atan(2%) raises "sdkfjsdljfsld" + num-asin(2%) raises "sdkfjsdljfsld" + num-acos(2%) raises "sdkfjsdljfsld" + num-sin(2%) raises "sdkfjsdljfsld" + num-tan(2%) raises "sdkfjsdljfsld" + num-cos(2%) raises "sdkfjsdljfsld" + num-exp(2%) raises "sdkfjsdljfsld" end From c81f57a2e7c3fe8b6db0bd4fd034489e53c16b44 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 3 Jun 2019 14:34:21 -0400 Subject: [PATCH 20/84] Unsupported operation tests --- src/js/base/js-numbers.js | 51 +++++++++++++++++++++++++++----- tests/pyret/tests/test-units.arr | 19 ++++++------ 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 0adabac005..611e5621e2 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -752,7 +752,7 @@ define("pyret-base/js/js-numbers", function() { // sqrt: pyretnum -> pyretnum var sqrt = function(n, errbacks) { - if (lessThan(n, 0, errbacks)) { + if (isNegative(n)) { errbacks.throwSqrtNegative('sqrt: negative argument ' + n); } if (typeof(n) === 'number') { @@ -809,7 +809,7 @@ define("pyret-base/js/js-numbers", function() { if ( eqv(n, 1, errbacks) ) { return 0; } - if (lessThanOrEqual(n, 0, errbacks)) { + if (isNonPositive(n)) { errbacks.throwLogNonPositive('log: non-positive argument ' + n); } if (typeof(n) === 'number') { @@ -884,7 +884,7 @@ define("pyret-base/js/js-numbers", function() { // acos: pyretnum -> pyretnum var acos = function(n, errbacks) { if (eqv(n, 1, errbacks)) { return 0; } - if (lessThan(n, -1, errbacks) || greaterThan(n, 1, errbacks)) { + if (lessThan(_withoutUnit(n), -1, errbacks) || greaterThan(_withoutUnit(n), 1, errbacks)) { errbacks.throwDomainError('acos: out of domain argument ' + n); } if (typeof(n) === 'number') { @@ -896,7 +896,7 @@ define("pyret-base/js/js-numbers", function() { // asin: pyretnum -> pyretnum var asin = function(n, errbacks) { if (eqv(n, 0, errbacks)) { return 0; } - if (lessThan(n, -1, errbacks) || greaterThan(n, 1, errbacks)) { + if (lessThan(_withoutUnit(n), -1, errbacks) || greaterThan(_withoutUnit(n), 1, errbacks)) { errbacks.throwDomainError('asin: out of domain argument ' + n); } if (typeof(n) === 'number') { @@ -1161,7 +1161,7 @@ define("pyret-base/js/js-numbers", function() { } } - var _makeUnsupportedUnop = function(u, errbacks, opName) { + var _throwUnitsUnsupported = function(u, errbacks, opName) { // TODO(benmusch): Use another errback function errbacks.throwIncompatibleUnits("The " + opName + " operation does not support units" + " but was given an argument with the unit " + _unitToString(u)) @@ -1469,6 +1469,18 @@ define("pyret-base/js/js-numbers", function() { // isReal: -> boolean // Produce true if the number is real. + // isPositive: -> boolean + // Produce true if the number is positive. + + // isNonNegative: -> boolean + // Produce true if the number is positive. + + // isNegative: -> boolean + // Produce true if the number is positive. + + // isNonPositive: -> boolean + // Produce true if the number is positive. + // toRational: -> pyretnum // Produce an exact number. @@ -1531,6 +1543,9 @@ define("pyret-base/js/js-numbers", function() { // atan: -> pyretnum // Produce the arc tangent. + // tan: -> pyretnum + // Produce the arc tangent. + // cos: -> pyretnum // Produce the cosine. @@ -1590,6 +1605,22 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.isExact = Unitnum.prototype.isRational; + Unitnum.prototype.isPositive = function() { + return isPositive(this.n); + }; + + Unitnum.prototype.isNonNegative = function() { + return isNonNegative(this.n); + }; + + Unitnum.prototype.isNegative = function() { + return isNegative(this.n); + }; + + Unitnum.prototype.isNonPositive = function() { + return isNonPositive(this.n); + }; + Unitnum.prototype.toRational = function() { return new Unitnum(toRational(this.n), this.u); } @@ -1678,10 +1709,14 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.log = function(errbacks) { - // TODO(benmusch): potentially support units here _throwUnitsUnsupported(this.u, errbacks, "log"); }; + Unitnum.prototype.tan = function(errbacks) { + // TODO(benmusch): potentially support units here + _throwUnitsUnsupported(this.u, errbacks, "tan"); + }; + Unitnum.prototype.atan = function(errbacks) { // TODO(benmusch): potentially support units here _throwUnitsUnsupported(this.u, errbacks, "atan"); @@ -1702,8 +1737,8 @@ define("pyret-base/js/js-numbers", function() { return new Unitnum(expt(this.n), newUnit); }; - Unitnum.prototype.exp = function() { - return new Unitnum(exp(this.n), this.u); + Unitnum.prototype.exp = function(errbacks) { + _throwUnitsUnsupported(this.u, errbacks, "exp"); }; Unitnum.prototype.acos = function(errbacks) { diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 65a6c64c31..8dd2284299 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -53,6 +53,7 @@ check "Other supported operations": expt(n) round() |# + true is true end check "Unsupported unops": @@ -60,13 +61,13 @@ check "Unsupported unops": TODO: integerSqrt() - is there any way to trigger this or is this unused? |# - num-sqrt(2%) raises "sdkfjsdljfsld" - num-log(2%) raises "sdkfjsdljfsld" - num-atan(2%) raises "sdkfjsdljfsld" - num-asin(2%) raises "sdkfjsdljfsld" - num-acos(2%) raises "sdkfjsdljfsld" - num-sin(2%) raises "sdkfjsdljfsld" - num-tan(2%) raises "sdkfjsdljfsld" - num-cos(2%) raises "sdkfjsdljfsld" - num-exp(2%) raises "sdkfjsdljfsld" + num-sqrt(2%) raises "The square root operation does not support units but was given an argument with the unit m" + num-log(2%) raises "The log operation does not support units but was given an argument with the unit m" + num-atan(2%) raises "The atan operation does not support units but was given an argument with the unit m" + num-asin(0%) raises "The asin operation does not support units but was given an argument with the unit m" + num-acos(0%) raises "The acos operation does not support units but was given an argument with the unit m" + num-sin(2%) raises "The sin operation does not support units but was given an argument with the unit m" + num-tan(2%) raises "The tan operation does not support units but was given an argument with the unit m" + num-cos(2%) raises "The cos operation does not support units but was given an argument with the unit m" + num-exp(2%) raises "The exp operation does not support units but was given an argument with the unit m" end From 6efe7cb9d2e9b40115e8a13d663d2f47c1e0c527 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 3 Jun 2019 16:29:19 -0400 Subject: [PATCH 21/84] Add units to fracs, test some numeric operations --- src/arr/compiler/anf.arr | 3 ++- src/arr/compiler/desugar.arr | 2 +- src/arr/compiler/type-check.arr | 6 +++--- src/arr/compiler/well-formed.arr | 14 ++++++++++---- src/arr/trove/ast.arr | 19 +++++++++++++------ src/js/base/js-numbers.js | 21 ++++++++++++++++----- src/js/base/pyret-grammar.bnf | 4 ++-- src/js/trove/parse-pyret.js | 17 +++++++++++++---- tests/pyret/tests/test-units.arr | 14 +++++++++----- tests/pyret/tests/test-well-formed.arr | 7 ++++++- 10 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index 559def83cf..e1d18090f1 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -207,7 +207,8 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: | s-num(l, n, u-maybe) => k(N.a-val(l, N.a-num(l, n, anf-unit(u-maybe)))) # num, den are exact ints, and s-frac desugars to the exact rational num/den - | s-frac(l, num, den) => k(N.a-val(l, N.a-num(l, num / den, N.a-unit-one))) # Possibly unneeded if removed by desugar? + | s-frac(l, num, den, u-maybe) => + k(N.a-val(l, N.a-num(l, num / den, anf-unit(u-maybe)))) # Possibly unneeded if removed by desugar? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den | s-rfrac(l, num, den) => k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den), N.a-unit-one))) # Possibly unneeded if removed by desugar? | s-str(l, s) => k(N.a-val(l, N.a-str(l, s))) diff --git a/src/arr/compiler/desugar.arr b/src/arr/compiler/desugar.arr index af8837671a..2ee3157002 100644 --- a/src/arr/compiler/desugar.arr +++ b/src/arr/compiler/desugar.arr @@ -530,7 +530,7 @@ fun desugar-expr(expr :: A.Expr): | s-srcloc(_, _) => expr | s-num(_, _, _) => expr # num, den are exact ints, and s-frac desugars to the exact rational num/den - | s-frac(l, num, den) => A.s-num(l, num / den, none) # NOTE: Possibly must preserve further? + | s-frac(l, num, den, u) => A.s-num(l, num / den, u) # NOTE: Possibly must preserve further? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den | s-rfrac(l, num, den) => A.s-num(l, num-to-roughnum(num / den), none) # NOTE: Possibly must preserve further? | s-str(_, _) => expr diff --git a/src/arr/compiler/type-check.arr b/src/arr/compiler/type-check.arr index 0eebad91f7..111ef09ced 100644 --- a/src/arr/compiler/type-check.arr +++ b/src/arr/compiler/type-check.arr @@ -566,9 +566,9 @@ fun _checking(e :: Expr, expect-type :: Type, top-level :: Boolean, context :: C raise("checking for s-undefined not implemented") | s-srcloc(l, loc) => check-synthesis(e, expect-type, top-level, context) - | s-num(l, n, _) => + | s-num(l, n, u) => check-synthesis(e, expect-type, top-level, context) - | s-frac(l, num, den) => + | s-frac(l, num, den, u) => check-synthesis(e, expect-type, top-level, context) | s-rfrac(l, num, den) => check-synthesis(e, expect-type, top-level, context) @@ -830,7 +830,7 @@ fun _synthesis(e :: Expr, top-level :: Boolean, context :: Context) -> TypingRes typing-result(e, t-srcloc(l), context) | s-num(l, n, _) => typing-result(e, t-number(l), context) - | s-frac(l, num, den) => + | s-frac(l, num, den, u) => typing-result(e, t-number(l), context) | s-rfrac(l, num, den) => typing-result(e, t-number(l), context) diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index edf558436f..17fb85e3c8 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -393,7 +393,7 @@ fun reject-standalone-exprs(stmts :: List%(is-link), ignore-last :: Boolean) blo end | s-id(_, _) => wf-error([list: [ED.para: ED.text("A standalone variable name probably isn't intentional.")]], l) | s-num(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) - | s-frac(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) + | s-frac(_, _, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-rfrac(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-bool(_, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-str(_, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) @@ -902,10 +902,16 @@ well-formed-visitor = A.default-iter-visitor.{ end iterator.visit(self) and lists.all(_.visit(self), bindings) and ann.visit(self) and body.visit(self) end, - method s-frac(self, l, num, den) block: + method s-frac(self, l, num, den, unit-maybe) block: when den == 0: add-error(C.zero-fraction(l, num)) end + + cases(Option) unit-maybe: + | none => nothing + | some(u) => wf-unit(u, none) + end + true end, method s-rfrac(self, l, num, den) block: @@ -1299,8 +1305,8 @@ top-level-visitor = A.default-iter-visitor.{ method s-prim-app(_, l :: Loc, _fun :: String, args :: List, app-info :: A.PrimAppInfo): well-formed-visitor.s-prim-app(l, _fun, args, app-info) end, - method s-frac(_, l :: Loc, num, den): - well-formed-visitor.s-frac(l, num, den) + method s-frac(_, l :: Loc, num, den, u): + well-formed-visitor.s-frac(l, num, den, u) end, method s-reactor(self, l, fields): well-formed-visitor.s-reactor(l, fields) diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index 3158bfecfd..72434f2bd8 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -942,7 +942,14 @@ data Expr: end | s-frac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option) with: method label(self): "s-frac" end, - method tosource(self): PP.number(self.num) + PP.str("/") + PP.number(self.den) end + method tosource(self) block: + base-str = PP.number(self.num) + PP.str("/") + PP.number(self.den) + cases(Option) self.u: + | none => base-str + | some(u) => PP.separate(str-percent, + [list: base-str, PP.surround(INDENT, 0, PP.langle, u.tosource(), PP.rangle)]) + end + end | s-rfrac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option) with: method label(self): "s-rfrac" end, method tosource(self): PP.str("~") + PP.number(self.num) + PP.str("/") + PP.number(self.den) end @@ -2170,8 +2177,8 @@ default-map-visitor = { method s-num(self, l :: Loc, n :: Number, u :: Option): s-num(l, n, u) end, - method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger): - s-frac(l, num, den) + method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): + s-frac(l, num, den, u) end, method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger): s-rfrac(l, num, den) @@ -2730,7 +2737,7 @@ default-iter-visitor = { method s-num(self, l :: Loc, n :: Number, u :: Option): true end, - method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger): + method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): true end, method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger): @@ -3267,8 +3274,8 @@ dummy-loc-visitor = { method s-num(self, l :: Loc, n :: Number, u :: Option): s-num(dummy-loc, n, u) end, - method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger): - s-frac(dummy-loc, num, den) + method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): + s-frac(dummy-loc, num, den, u) end, method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger): s-rfrac(dummy-loc, num, den) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 611e5621e2..b2bf4c6cb5 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -646,13 +646,20 @@ define("pyret-base/js/js-numbers", function() { } }, function(x, y, errbacks) { - return x.expt(y, errbacks); + if (!_unitEquals(_unitOf(y), {})) { + errbacks.throwIncompatibleUnits("expt: power cannot have a unit") + } + return x.expt(_withoutUnit(y), errbacks); }, { isXSpecialCase: function(x, errbacks) { - return eqv(x, 0, errbacks) || eqv(x, 1, errbacks); + return (eqv(x, 0, errbacks) || eqv(x, 1, errbacks)); }, onXSpecialCase: function(x, y, errbacks) { + if (!_unitEquals(_unitOf(y), {})) { + errbacks.throwIncompatibleUnits("expt: power cannot have a unit") + } + if (eqv(x, 0, errbacks)) { if (eqv(y, 0, errbacks)) { return 1; @@ -667,9 +674,13 @@ define("pyret-base/js/js-numbers", function() { }, isYSpecialCase: function(y, errbacks) { - return eqv(y, 0, errbacks) || lessThan(y, 0, errbacks); + return !(y instanceof Unitnum) && (eqv(y, 0, errbacks) || lessThan(y, 0, errbacks)); }, onYSpecialCase: function(x, y, errbacks) { + if (!_unitEquals(_unitOf(y), {})) { + errbacks.throwIncompatibleUnits("expt: power cannot have a unit") + } + if (eqv(y, 0, errbacks)) { return 1; } else { // i.e., y is negative @@ -1733,8 +1744,8 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.expt = function(n) { - var newUnit = _unitMap(this.u, function(pow) { n * pow }); - return new Unitnum(expt(this.n), newUnit); + var newUnit = _unitMap(this.u, function(pow) { return n * pow }); + return new Unitnum(expt(this.n, n), newUnit); }; Unitnum.prototype.exp = function(errbacks) { diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index c88a8eaf87..ec24b3a67f 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -141,8 +141,8 @@ unit-expr: unit-lhs num-expr: NUMBER [PERCENT dim-expr] -frac-expr: RATIONAL -rfrac-expr: ROUGHRATIONAL +frac-expr: RATIONAL [PERCENT dim-expr] +rfrac-expr: ROUGHRATIONAL [PERCENT dim-expr] bool-expr: TRUE | FALSE string-expr: STRING diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index fd05e690ed..3e73fcd8f1 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -1176,16 +1176,25 @@ return RUNTIME.getField(ast, 's-num') .app(pos(node.pos), number(node.kids[0]), RUNTIME.ffi.makeNone()); } else { - // (num-expr n [PERCENT dim-expr]) + // (num-expr n PERCENT dim-expr) return RUNTIME.getField(ast, 's-num') .app(pos(node.pos), number(node.kids[0]), RUNTIME.ffi.makeSome(tr(node.kids[2]))); } }, 'frac-expr': function(node) { - // (frac-expr n) var numden = node.kids[0].value.split("/"); - return RUNTIME.getField(ast, 's-frac') - .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1])); + if (node.kids.length == 1) { + // (frac-expr n) + return RUNTIME.getField(ast, 's-frac') + .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1], {})); + } else { + // (frac-expr n PERCENT dim-expr) + return RUNTIME.getField(ast, 's-frac') + .app(pos(node.pos), + RUNTIME.makeNumberFromString(numden[0], {}), + RUNTIME.makeNumberFromString(numden[1], {}), + RUNTIME.ffi.makeSome(tr(node.kids[2]))); + } }, 'rfrac-expr': function(node) { // (rfrac-expr n) diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 8dd2284299..f436fe0c96 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -6,6 +6,8 @@ check "Unit equality": 2 is 2% 2% is 2% 2%<(((s * m) ^ 2) / t) ^ 2> is 2% + + 1/1% is 1% end check "Arithmetic binops": @@ -48,12 +50,14 @@ check "Other supported operations": TODO: numerator() denominator() - abs() - floor() - expt(n) - round() + What even is the best option for those? |# - true is true + num-abs(-2%) is 2% + num-floor(3/2%) is 1% + num-expt(2%, 3) is 8% + num-round(3/2%) is 2% + + num-expt(2%, 3%) raises "expt: power cannot have a unit" end check "Unsupported unops": diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index 5797a63953..4d1b0c996a 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -267,9 +267,14 @@ check "unit annotations": run-str("2%") is%(output) compile-error(CS.is-mixed-binops) run-str("2%") is%(output) compile-error(CS.is-mixed-binops) run-str("2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) - run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("1/2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("1/2%") is%(output) compile-error(CS.is-invalid-unit-power) end #| From 27a7d0043481c325d2f72ca04c2ef5b0f54d66da Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 3 Jun 2019 16:32:47 -0400 Subject: [PATCH 22/84] Add some tests --- tests/pyret/tests/test-units.arr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index f436fe0c96..51beab59ac 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -8,6 +8,10 @@ check "Unit equality": 2%<(((s * m) ^ 2) / t) ^ 2> is 2% 1/1% is 1% + + # tests for adding/removing units + (10 * 1%) + 1% is 11% + (10% / 1%) + 1 is 11 end check "Arithmetic binops": From da92df1c1dfa9a149ed29c58c5a88849836bc64a Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 3 Jun 2019 17:26:57 -0400 Subject: [PATCH 23/84] Add units to rfracs, fix bug, run all the tests --- src/arr/compiler/anf.arr | 3 ++- src/arr/compiler/desugar.arr | 2 +- src/arr/compiler/type-check.arr | 4 ++-- src/arr/compiler/well-formed.arr | 14 ++++++++++---- src/arr/trove/ast.arr | 19 +++++++++++++------ src/js/base/js-numbers.js | 5 +---- src/js/base/runtime.js | 1 + src/js/trove/parse-pyret.js | 14 ++++++++++++-- tests/pyret/main2.arr | 2 -- tests/pyret/tests/test-well-formed.arr | 5 +++++ 10 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index e1d18090f1..924c9c849f 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -210,7 +210,8 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: | s-frac(l, num, den, u-maybe) => k(N.a-val(l, N.a-num(l, num / den, anf-unit(u-maybe)))) # Possibly unneeded if removed by desugar? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den - | s-rfrac(l, num, den) => k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den), N.a-unit-one))) # Possibly unneeded if removed by desugar? + | s-rfrac(l, num, den, u-maybe) => + k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den), anf-unit(u-maybe)))) # Possibly unneeded if removed by desugar? | s-str(l, s) => k(N.a-val(l, N.a-str(l, s))) | s-undefined(l) => k(N.a-val(l, N.a-undefined(l))) | s-bool(l, b) => k(N.a-val(l, N.a-bool(l, b))) diff --git a/src/arr/compiler/desugar.arr b/src/arr/compiler/desugar.arr index 2ee3157002..178be37802 100644 --- a/src/arr/compiler/desugar.arr +++ b/src/arr/compiler/desugar.arr @@ -532,7 +532,7 @@ fun desugar-expr(expr :: A.Expr): # num, den are exact ints, and s-frac desugars to the exact rational num/den | s-frac(l, num, den, u) => A.s-num(l, num / den, u) # NOTE: Possibly must preserve further? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den - | s-rfrac(l, num, den) => A.s-num(l, num-to-roughnum(num / den), none) # NOTE: Possibly must preserve further? + | s-rfrac(l, num, den, u) => A.s-num(l, num-to-roughnum(num / den), u) # NOTE: Possibly must preserve further? | s-str(_, _) => expr | s-bool(_, _) => expr | s-obj(l, fields) => A.s-obj(l, fields.map(desugar-member)) diff --git a/src/arr/compiler/type-check.arr b/src/arr/compiler/type-check.arr index 111ef09ced..8e417da8a4 100644 --- a/src/arr/compiler/type-check.arr +++ b/src/arr/compiler/type-check.arr @@ -570,7 +570,7 @@ fun _checking(e :: Expr, expect-type :: Type, top-level :: Boolean, context :: C check-synthesis(e, expect-type, top-level, context) | s-frac(l, num, den, u) => check-synthesis(e, expect-type, top-level, context) - | s-rfrac(l, num, den) => + | s-rfrac(l, num, den, u) => check-synthesis(e, expect-type, top-level, context) | s-bool(l, b) => check-synthesis(e, expect-type, top-level, context) @@ -832,7 +832,7 @@ fun _synthesis(e :: Expr, top-level :: Boolean, context :: Context) -> TypingRes typing-result(e, t-number(l), context) | s-frac(l, num, den, u) => typing-result(e, t-number(l), context) - | s-rfrac(l, num, den) => + | s-rfrac(l, num, den, u) => typing-result(e, t-number(l), context) | s-bool(l, b) => typing-result(e, t-boolean(l), context) diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index 17fb85e3c8..efcfae9992 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -394,7 +394,7 @@ fun reject-standalone-exprs(stmts :: List%(is-link), ignore-last :: Boolean) blo | s-id(_, _) => wf-error([list: [ED.para: ED.text("A standalone variable name probably isn't intentional.")]], l) | s-num(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-frac(_, _, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) - | s-rfrac(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) + | s-rfrac(_, _, _, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-bool(_, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-str(_, _) => wf-error([list: [ED.para: ED.text("A standalone value probably isn't intentional.")]], l) | s-dot(_, _, _) => wf-error([list: [ED.para: ED.text("A standalone field-lookup expression probably isn't intentional.")]], l) @@ -914,10 +914,16 @@ well-formed-visitor = A.default-iter-visitor.{ true end, - method s-rfrac(self, l, num, den) block: + method s-rfrac(self, l, num, den, unit-maybe) block: when den == 0: add-error(C.zero-fraction(l, num)) end + + cases(Option) unit-maybe: + | none => nothing + | some(u) => wf-unit(u, none) + end + true end, method s-id(self, l, id) block: @@ -1311,8 +1317,8 @@ top-level-visitor = A.default-iter-visitor.{ method s-reactor(self, l, fields): well-formed-visitor.s-reactor(l, fields) end, - method s-rfrac(_, l :: Loc, num, den): - well-formed-visitor.s-rfrac(l, num, den) + method s-rfrac(_, l :: Loc, num, den, u): + well-formed-visitor.s-rfrac(l, num, den, u) end, method s-id(_, l :: Loc, id :: A.Name): well-formed-visitor.s-id(l, id) diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index 72434f2bd8..48c3f50e61 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -952,7 +952,14 @@ data Expr: end | s-rfrac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option) with: method label(self): "s-rfrac" end, - method tosource(self): PP.str("~") + PP.number(self.num) + PP.str("/") + PP.number(self.den) end + method tosource(self): + base-str = PP.str("~") + PP.number(self.num) + PP.str("/") + PP.number(self.den) + cases(Option) self.u: + | none => base-str + | some(u) => PP.separate(str-percent, + [list: base-str, PP.surround(INDENT, 0, PP.langle, u.tosource(), PP.rangle)]) + end + end | s-bool(l :: Loc, b :: Boolean) with: method label(self): "s-bool" end, method tosource(self): PP.str(tostring(self.b)) end @@ -2180,8 +2187,8 @@ default-map-visitor = { method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): s-frac(l, num, den, u) end, - method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger): - s-rfrac(l, num, den) + method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): + s-rfrac(l, num, den, u) end, method s-bool(self, l :: Loc, b :: Boolean): s-bool(l, b) @@ -2740,7 +2747,7 @@ default-iter-visitor = { method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): true end, - method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger): + method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): true end, method s-bool(self, l :: Loc, b :: Boolean): @@ -3277,8 +3284,8 @@ dummy-loc-visitor = { method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): s-frac(dummy-loc, num, den, u) end, - method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger): - s-rfrac(dummy-loc, num, den) + method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): + s-rfrac(dummy-loc, num, den, u) end, method s-bool(self, l :: Loc, b :: Boolean): s-bool(dummy-loc, b) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index b2bf4c6cb5..fce241bf5b 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -657,6 +657,7 @@ define("pyret-base/js/js-numbers", function() { }, onXSpecialCase: function(x, y, errbacks) { if (!_unitEquals(_unitOf(y), {})) { + // need this because the isXSpecialCase check cannot look at the y errbacks.throwIncompatibleUnits("expt: power cannot have a unit") } @@ -677,10 +678,6 @@ define("pyret-base/js/js-numbers", function() { return !(y instanceof Unitnum) && (eqv(y, 0, errbacks) || lessThan(y, 0, errbacks)); }, onYSpecialCase: function(x, y, errbacks) { - if (!_unitEquals(_unitOf(y), {})) { - errbacks.throwIncompatibleUnits("expt: power cannot have a unit") - } - if (eqv(y, 0, errbacks)) { return 1; } else { // i.e., y is negative diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 4bd3e1da95..2eb90468f5 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -621,6 +621,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom @return {!PNumber} with value n and unit u */ function makeNumberFromString(s, u) { + u = u || {} var result = jsnums.fromString(s, NumberErrbacks); if(result === false) { thisRuntime.ffi.throwMessageException("Could not create number from: " + s); diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 3e73fcd8f1..12744dfa29 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -1199,8 +1199,18 @@ 'rfrac-expr': function(node) { // (rfrac-expr n) var numden = node.kids[0].value.substring(1).split("/"); - return RUNTIME.getField(ast, 's-rfrac') - .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1])); + if (node.kids.length == 1) { + // (rfrac-expr n) + return RUNTIME.getField(ast, 's-rfrac') + .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1], {})); + } else { + // (rfrac-expr n PERCENT dim-expr) + return RUNTIME.getField(ast, 's-rfrac') + .app(pos(node.pos), + RUNTIME.makeNumberFromString(numden[0], {}), + RUNTIME.makeNumberFromString(numden[1], {}), + RUNTIME.ffi.makeSome(tr(node.kids[2]))); + } }, 'string-expr': function(node) { return RUNTIME.getField(ast, 's-str') diff --git a/tests/pyret/main2.arr b/tests/pyret/main2.arr index a01a08d62e..1066fce74c 100644 --- a/tests/pyret/main2.arr +++ b/tests/pyret/main2.arr @@ -1,4 +1,3 @@ -#| import file("./tests/test-strings.arr") as _ import file("./tests/test-format.arr") as _ import file("./tests/test-numbers.arr") as _ @@ -42,5 +41,4 @@ import file("./tests/test-tail-call.arr") as _ import file("./tests/test-parse.arr") as _ import file("./tests/test-parse-errors.arr") as _ import file("./tests/test-flatness.arr") as _ -|# import file("./tests/test-units.arr") as _ diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index 4d1b0c996a..eac22a2e8b 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -270,11 +270,16 @@ check "unit annotations": run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) run-str("1/2%") is%(output) compile-error(CS.is-invalid-unit-power) run-str("1/2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("~1/2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("~1/2%") is%(output) compile-error(CS.is-invalid-unit-power) end #| From 6aaa3147d472af6cd7f248cad435126452a28cd4 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 3 Jun 2019 18:25:30 -0400 Subject: [PATCH 24/84] Fix a test --- src/js/base/js-numbers.js | 2 +- tests/pyret/tests/test-units.arr | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index fce241bf5b..0660427896 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1636,7 +1636,7 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.toExact = Unitnum.prototype.toRational; Unitnum.prototype.toRoughnum = function() { - return new Unitnum(toRoughnum(this.n), u); + return new Unitnum(toRoughnum(this.n), this.u); }; Unitnum.prototype.toFixnum = function() { diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 51beab59ac..915e662e20 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -8,6 +8,7 @@ check "Unit equality": 2%<(((s * m) ^ 2) / t) ^ 2> is 2% 1/1% is 1% + ~1/2% is-roughly ~1/2% # tests for adding/removing units (10 * 1%) + 1% is 11% From 1720bccf3ee13a5719d896fe837a0b0650385c46 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 4 Jun 2019 10:08:27 -0400 Subject: [PATCH 25/84] Update grammar for predicates --- src/js/base/pyret-grammar.bnf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index ec24b3a67f..2be40f7529 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -261,6 +261,6 @@ app-ann: (name-ann|dot-ann) LANGLE comma-anns (RANGLE|GT) comma-anns: ann (COMMA ann)* -pred-ann: ann PERCENT (PARENSPACE|PARENNOSPACE) id-expr RPAREN +pred-ann: ann [PERCENT dim-expr] PERCENT (PARENSPACE|PARENNOSPACE) id-expr RPAREN dot-ann : NAME DOT NAME From 0629341c318629839898f12dd372f9ac44946469 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 4 Jun 2019 13:06:46 -0400 Subject: [PATCH 26/84] Add a-unit to Ann ast --- src/arr/compiler/anf-loop-compiler.arr | 3 +++ src/arr/compiler/ast-anf.arr | 1 + src/arr/compiler/ast-util.arr | 4 ++++ src/arr/compiler/desugar.arr | 2 ++ src/arr/compiler/flatness.arr | 1 + src/arr/compiler/resolve-scope.arr | 1 + src/arr/compiler/type-check.arr | 3 +++ src/arr/compiler/well-formed.arr | 9 ++++++--- src/arr/trove/ast.arr | 16 ++++++++++++++++ src/js/base/pyret-grammar.bnf | 7 +++++-- src/js/trove/parse-pyret.js | 5 +++++ 11 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index 8adb2e2db7..fe914dc6c1 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -395,6 +395,9 @@ fun compile-ann(ann :: A.Ann, visitor) -> DAG.CaseResults%(is-c-exp): rt-method(pred-maker, [clist: compiled-base.exp, compiled-exp.exp, j-str(name)]), cl-append(compiled-base.other-stmts, compiled-exp.other-stmts) ) + | a-unit(l, base, u) => + # TODO(benmusch): change this to compile units + compile-ann(base, visitor) | a-dot(l, m, field) => c-exp( rt-method("getDotAnn", [clist: diff --git a/src/arr/compiler/ast-anf.arr b/src/arr/compiler/ast-anf.arr index 6046a84cc7..6cc24dcdbf 100644 --- a/src/arr/compiler/ast-anf.arr +++ b/src/arr/compiler/ast-anf.arr @@ -781,6 +781,7 @@ fun freevars-ann-acc(ann :: A.Ann, seen-so-far :: NameDict) -> NameDict< | a-tuple(l, fields) => freevars-list-acc(fields, seen-so-far) | a-app(l, a, args) => freevars-list-acc(args, freevars-ann-acc(a, seen-so-far)) | a-method-app(l, a, _, args) => freevars-list-acc(args, freevars-ann-acc(a, seen-so-far)) + | a-unit(l, a, u) => freevars-ann-acc(a, seen-so-far) | a-pred(l, a, pred) => name = cases(A.Expr) pred: | s-id(_, n) => n diff --git a/src/arr/compiler/ast-util.arr b/src/arr/compiler/ast-util.arr index 4210f05269..c4c69f82de 100644 --- a/src/arr/compiler/ast-util.arr +++ b/src/arr/compiler/ast-util.arr @@ -679,6 +679,7 @@ fun is-stateful-ann(ann :: A.Ann) -> Boolean: | a-record(_, fields) => fields.map(_.ann).all(is-stateful-ann) | a-tuple(_, fields) => fields.all(is-stateful-ann) | a-app(_, inner, args) => is-stateful-ann(inner) + | a-unit(_, _, _) => true # TODO(Benmusch, 4 Jun 2019): true for now. Could refine later | a-pred(_, _, _) => true # TODO(Oak, 21 Jan 2016): true for now. Could refine later | a-dot(_, _, _) => true # TODO(Oak, 7 Feb 2016): true for now. Could refine later | a-checked(_, _) => raise("NYI") @@ -1041,6 +1042,9 @@ fun get-named-provides(resolved :: CS.NameResolution, uri :: URI, compile-env :: | a-pred(l, ann, exp) => # TODO(joe): give more info than this to type checker? only needed dynamically, right? ann-to-typ(ann) + | a-unit(l, ann, u) => + # TODO(benmusch): needs to change if/when units are added to the type checker + ann-to-typ(ann) | a-dot(l, obj, field) => maybe-b = resolved.type-bindings.get-now(obj.key()) cases(Option) maybe-b: diff --git a/src/arr/compiler/desugar.arr b/src/arr/compiler/desugar.arr index 178be37802..6126e11f55 100644 --- a/src/arr/compiler/desugar.arr +++ b/src/arr/compiler/desugar.arr @@ -108,6 +108,8 @@ fun desugar-ann(a :: A.Ann) -> A.Ann: A.a-record(l, fields.map(desugar-afield)) | a-tuple(l, fields) => A.a-tuple(l, fields.map(desugar-ann)) + | a-unit(l, ann, u) => + A.a-unit(l, desugar-ann(ann), u) | a-pred(l, ann, exp) => A.a-pred(l, desugar-ann(ann), desugar-expr(exp)) end diff --git a/src/arr/compiler/flatness.arr b/src/arr/compiler/flatness.arr index fa879e93f5..4c097ed411 100644 --- a/src/arr/compiler/flatness.arr +++ b/src/arr/compiler/flatness.arr @@ -69,6 +69,7 @@ fun ann-flatness(ann :: A.Ann, val-env :: FEnv, ann-env :: FEnv) -> Flatness: ann-flatness(base, val-env, ann-env), val-flatness ) + | a-unit(l, base, u) => some(0) | a-dot(l, obj, field) => none # TODO(joe): module-ids should make this doable... | a-checked(checked, residual) => none end diff --git a/src/arr/compiler/resolve-scope.arr b/src/arr/compiler/resolve-scope.arr index 8cf6d2e4fb..13d1b2d760 100644 --- a/src/arr/compiler/resolve-scope.arr +++ b/src/arr/compiler/resolve-scope.arr @@ -1325,6 +1325,7 @@ fun resolve-names(p :: A.Program, initial-env :: C.CompileEnvironment): method a-record(self, l, fields): A.a-record(l, fields.map(_.visit(self))) end, method a-app(self, l, ann, args): A.a-app(l, ann.visit(self), args.map(_.visit(self))) end, method a-pred(self, l, ann, exp): A.a-pred(l, ann.visit(self), exp.visit(self)) end, + method a-unit(self, l, ann, u): A.a-unit(l, ann.visit(self), u) end, method a-dot(self, l, obj, field) block: cases(A.Name) obj block: | s-name(nameloc, s) => diff --git a/src/arr/compiler/type-check.arr b/src/arr/compiler/type-check.arr index 8e417da8a4..a47e7b2161 100644 --- a/src/arr/compiler/type-check.arr +++ b/src/arr/compiler/type-check.arr @@ -2295,6 +2295,9 @@ fun to-type(in-ann :: A.Ann, context :: Context) -> FoldResult>: fold-errors([list: C.cant-typecheck("missing annotation on " + tostring(ann), l)]) end end) + | a-unit(l, ann, u) => + # TODO(benmusch): Change this to tc units + to-type(ann, context) | a-dot(l, obj, field) => key = obj.key() origin = context.module-names.get(key) diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index efcfae9992..6ab633e376 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -1371,6 +1371,9 @@ top-level-visitor = A.default-iter-visitor.{ method s-table-extend(_, l :: Loc, column-binds :: A.ColumnBinds, extensions :: List): well-formed-visitor.s-table-extend(l, column-binds, extensions) end, + method s-num(self, l, n, unit-maybe): + well-formed-visitor.s-num(l, n, unit-maybe) + end, method a-arrow(_, l, args, ret, use-parens): well-formed-visitor.a-arrow(l, args, ret, use-parens) end, @@ -1389,14 +1392,14 @@ top-level-visitor = A.default-iter-visitor.{ method a-pred(_, l, ann, exp): well-formed-visitor.a-pred(l, ann, exp) end, + method a-unit(_, l, ann, u): + well-formed-visitor.a-unit(l, ann, u) + end, method a-dot(_, l, obj, field): well-formed-visitor.a-dot(l, obj, field) end, method a-field(_, l, name, ann): well-formed-visitor.a-field(l, name, ann) - end, - method s-num(self, l, n, unit-maybe): - well-formed-visitor.s-num(l, n, unit-maybe) end } diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index 48c3f50e61..dddf5aec0e 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -1742,6 +1742,12 @@ data Ann: | a-pred(l :: Loc, ann :: Ann, exp :: Expr) with: method label(self): "a-pred" end, method tosource(self): self.ann.tosource() + str-percent + PP.parens(self.exp.tosource()) end, + | a-unit(l :: Loc, ann :: Ann, u :: Unit) with: + method label(self): "a-unit" end, + method tosource(self) block: + unit-str = PP.surround(INDENT, 0, PP.langle, self.u.tosource(), PP.rangle) + self.ann.tosource() + str-percent + unit-str + end | a-dot(l :: Loc, obj :: Name, field :: String) with: method label(self): "a-dot" end, method tosource(self): self.obj.tosource() + PP.str("." + self.field) end, @@ -2393,6 +2399,10 @@ default-map-visitor = { method a-pred(self, l, ann, exp): a-pred(l, ann.visit(self), exp.visit(self)) end, + method a-unit(self, l, ann, u): + # TODO(benmusch): Add units to the visitor? + a-unit(l, ann.visit(self), u) + end, method a-dot(self, l, obj, field): a-dot(l, obj.visit(self), field) end, @@ -2943,6 +2953,9 @@ default-iter-visitor = { method a-pred(self, l, ann, exp): ann.visit(self) and exp.visit(self) end, + method a-unit(self, l, ann, u): + ann.visit(self) + end, method a-dot(self, l, obj, field): obj.visit(self) end, @@ -3490,6 +3503,9 @@ dummy-loc-visitor = { method a-pred(self, l, ann, exp): a-pred(dummy-loc, ann.visit(self), exp.visit(self)) end, + method a-unit(self, l, ann, u): + a-unit(dummy-loc, ann.visit(self), u) + end, method a-dot(self, l, obj, field): a-dot(dummy-loc, obj, field) end, diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index 2be40f7529..6023ec8ada 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -243,7 +243,7 @@ load-table-spec: SOURCECOLON expr user-block-expr: BLOCK block END -ann: name-ann | record-ann | arrow-ann | app-ann | pred-ann | dot-ann | tuple-ann +ann: name-ann | record-ann | arrow-ann | app-ann | pred-ann | dot-ann | tuple-ann | unit-ann name-ann: NAME comma-ann-field: ann-field (COMMA ann-field)* @@ -261,6 +261,9 @@ app-ann: (name-ann|dot-ann) LANGLE comma-anns (RANGLE|GT) comma-anns: ann (COMMA ann)* -pred-ann: ann [PERCENT dim-expr] PERCENT (PARENSPACE|PARENNOSPACE) id-expr RPAREN +# TODO: Can we enforce ordering here? +unit-ann: ann PERCENT dim-expr + +pred-ann: ann PERCENT (PARENSPACE|PARENNOSPACE) id-expr RPAREN dot-ann : NAME DOT NAME diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 12744dfa29..1ce6575d0d 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -1390,6 +1390,11 @@ return RUNTIME.getField(ast, 'a-pred') .app(pos(node.pos), tr(node.kids[0]), tr(node.kids[3])); }, + 'unit-ann': function(node) { + // (unit-ann ann PERCENT dim-expr) + return RUNTIME.getField(ast, 'a-unit') + .app(pos(node.pos), tr(node.kids[0]), tr(node.kids[2])); + }, 'dot-ann': function(node) { // (dot-ann n1 PERIOD n2) return RUNTIME.getField(ast, 'a-dot') From 6ee5828bf81801c7138d6f1817211afceee28e8d Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 4 Jun 2019 14:30:27 -0400 Subject: [PATCH 27/84] Add parsing tests --- tests/pyret/tests/test-parse.arr | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/pyret/tests/test-parse.arr b/tests/pyret/tests/test-parse.arr index c79a2a1e4b..1fc6ec7e14 100644 --- a/tests/pyret/tests/test-parse.arr +++ b/tests/pyret/tests/test-parse.arr @@ -470,8 +470,6 @@ check "should parse reactors": end check "should parse unit-annotated numbers": - # TODO(benmusch): Figure out a non-ambiguous grammar to get rid of the need - # for %'s does-parse("2%") is true does-parse("2%<(m)>") is true does-parse("2%") is true @@ -492,3 +490,21 @@ check "should parse unit-annotated numbers": does-parse("2%") is false does-parse("2%") is false end + +check "should parse unit-anns numbers": + does-parse("n :: Number% = 0") is true + does-parse("n :: Number%%(is-even) = 0") is true + does-parse("n :: Number%%(is-two)%(is-even) = 0") is true + does-parse("n :: String% = 'foo'") is true + + # TODO(benmusch): Should these parse? + does-parse("n :: Number%(is-two)% = 0") is true + + # should be caught by wf: + does-parse("n :: Number%% = 0") is true + does-parse("n :: Number%%(is-even)% = 0") is true + does-parse("n :: Number% = 0") is true + + does-parse("n :: Number%<>%(is-even) = 0") is false + does-parse("n :: Number%<> = 0") is false +end From bc08458d306a7196c6adff40db391725ce49c3af Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 4 Jun 2019 15:39:45 -0400 Subject: [PATCH 28/84] Add wf-checks --- src/arr/compiler/compile-structs.arr | 17 +++++++++++++++++ src/arr/compiler/well-formed.arr | 16 ++++++++++++++++ tests/pyret/tests/test-well-formed.arr | 14 +++++++++++--- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/arr/compiler/compile-structs.arr b/src/arr/compiler/compile-structs.arr index b7cba36f76..d52414a79d 100644 --- a/src/arr/compiler/compile-structs.arr +++ b/src/arr/compiler/compile-structs.arr @@ -1055,6 +1055,23 @@ data CompileError: ED.loc(self.l), ED.text(" cannot be used where a type annotation is expected.")]] end + | multiple-unit-anns(l :: Loc, dup-l :: Loc) with: + method render-fancy-reason(self): + [ED.error: + [ED.para: + ED.text("The annotation had multiple "), + ED.code(ED.highlight(ED.text("unit annotations"), [ED.locs: self.l, self.dup-l])), + ED.text(", but only one is allowed")]] + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.text("Tried to add a unit annotation at "), + ED.loc(self.dup-l), + ED.text(", but one already exists at "), + ED.loc(self.l), + ED.text(". Do not add more than one unit annotation.")]] + end | block-needed(expr-loc :: Loc, blocks :: List) with: method render-fancy-reason(self): if self.blocks.length() > 1: diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index 6ab633e376..f77856856b 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -324,6 +324,14 @@ fun wf-unit(u :: A.Unit, parent-maybe :: Option) block: end end +fun unit-ann-loc(ann :: A.Ann) -> Option: + cases(A.Ann) ann: + | a-pred(l, base, exp) => unit-ann-loc(base) + | a-unit(l, base, u) => some(l) + | else => none + end +end + fun wf-last-stmt(block-loc, stmt :: A.Expr): cases(A.Expr) stmt: | s-let(l, _, _, _) => add-error(C.block-ending(l, block-loc, "let-binding")) @@ -1047,6 +1055,14 @@ well-formed-visitor = A.default-iter-visitor.{ end true end, + method a-unit(self, l, base, u) block: + wf-unit(u, none) + cases(Option) unit-ann-loc(base): + | none => nothing + | some(dup-l) => add-error(C.multiple-unit-anns(l, dup-l)) + end + true + end, method s-num(self, l, n, unit-maybe) block: cases(Option) unit-maybe: | none => nothing diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index eac22a2e8b..241b519b75 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -273,13 +273,21 @@ check "unit annotations": run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) + run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-binops) + run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-binops) + run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-binops) run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) - run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) - run-str("1/2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("1/2%") is%(output) compile-error(CS.is-invalid-unit-power) run-str("1/2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("~1/2%") is%(output) compile-error(CS.is-invalid-unit-power) run-str("~1/2%") is%(output) compile-error(CS.is-invalid-unit-power) - run-str("~1/2%") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-invalid-unit-power) + run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-invalid-unit-power) + + run-str("var n :: Number%% = 0") is%(output) compile-error(CS.is-multiple-unit-anns) + run-str("var n :: Number%%(num-is-integer)% = 0") is%(output) compile-error(CS.is-multiple-unit-anns) end #| From e468a908b14acdc4ce605f6a50eb7f6fccd33269 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 4 Jun 2019 16:10:18 -0400 Subject: [PATCH 29/84] Get rid of AUnits, normalize unit expressions at compile-time --- src/arr/compiler/anf-loop-compiler.arr | 67 +++++++++++++++++++------- src/arr/compiler/anf.arr | 45 ++--------------- src/arr/compiler/ast-anf.arr | 24 +++------ 3 files changed, 60 insertions(+), 76 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index fe914dc6c1..d1d0bfe9e2 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -1328,11 +1328,45 @@ fun is-id-fn-name(flatness-env :: D.MutableStringDict>, name :: S flatness-env.has-key-now(name) end -fun compile-unit(u :: N.AUnit, acc :: CList) -> J.JExpr: - cases(N.AUnit) u: - | a-unit-one => J.j-obj(acc) - | a-unit-name(id, power, rest) => - compile-unit(rest, CL.concat-cons(j-field(tostring(id), j-num(power)), acc)) +# return all of the names in a unit +fun unit-names(u :: A.Unit) -> Set: + cases (A.Unit) u: + | none(_) => [list-set: ] + | u-base(_, id) => [list-set: id] + | u-mul(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) + | u-div(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) + | u-pow(_, _, shadow u, n) => unit-names(u) + | u-paren(_, shadow u) => unit-names(u) + end +end + +fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: + cases (A.Unit) u: + | none(_) => 0 + | u-base(_, id) => + if id == target-id: 1 else: 0 end + | u-mul(_, _, lhs, rhs) => unit-power(target-id, lhs) + unit-power(target-id, rhs) + | u-div(_, _, lhs, rhs) => unit-power(target-id, lhs) + (-1 * unit-power(target-id, rhs)) + | u-pow(_, _, shadow u, n) => unit-power(target-id, u) * n + | u-paren(_, shadow u) => unit-power(target-id, u) + end +end + +fun compile-unit(u-maybe :: Option) -> J.JExpr: + cases(Option) u-maybe block: + | none => J.j-obj(CL.concat-empty) + | some(u) => + fields = unit-names(u).fold( + lam(acc, id): + power = unit-power(id, u) + if power == 0: + acc + else: + CL.concat-cons(j-field(tostring(id), j-num(power)), acc) + end + end, + CL.concat-empty) + j-obj(fields) end end @@ -1680,11 +1714,11 @@ compiler-visitor = { method a-srcloc(self, l, loc): c-exp(self.get-loc(loc), cl-empty) end, - method a-num(self, l :: Loc, n :: Number, u :: N.AUnit) block: - if num-is-fixnum(n) and N.is-a-unit-one(u): + method a-num(self, l :: Loc, n :: Number, u-maybe :: Option) block: + if num-is-fixnum(n) and A.is-u-one(u-maybe.or-else(A.u-one(N.dummy-loc))): c-exp(j-parens(j-num(n)), cl-empty) else: - args = [clist: j-str(tostring(n)), compile-unit(u, CL.concat-empty)] + args = [clist: j-str(tostring(n)), compile-unit(u-maybe)] c-exp(rt-method("makeNumberFromString", args), cl-empty) end end, @@ -1921,22 +1955,21 @@ remove-useless-if-visitor = N.default-map-visitor.{ check: d = N.dummy-loc - u-one = N.a-unit-one true1 = N.a-if(d, N.a-bool(d, true), - N.a-lettable(d, N.a-val(d, N.a-num(d, 1, u-one))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 2, u-one)))) - true1.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 1, u-one)) + N.a-lettable(d, N.a-val(d, N.a-num(d, 1, none))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 2, none)))) + true1.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 1, none)) false4 = N.a-if(d, N.a-bool(d, false), - N.a-lettable(d, N.a-val(d, N.a-num(d, 3, u-one))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 4, u-one)))) - false4.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 4, u-one)) + N.a-lettable(d, N.a-val(d, N.a-num(d, 3, none))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 4, none)))) + false4.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 4, none)) N.a-if(d, N.a-id(d, A.s-name(d, "x")), N.a-lettable(d, true1), N.a-lettable(d, false4) ).visit(remove-useless-if-visitor) is N.a-if(d, N.a-id(d, A.s-name(d, "x")), - N.a-lettable(d, N.a-val(d, N.a-num(d, 1, u-one))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 4, u-one)))) + N.a-lettable(d, N.a-val(d, N.a-num(d, 1, none))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 4, none)))) end |# diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index 924c9c849f..031d0fe067 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -143,43 +143,6 @@ fun anf-block(es-init :: List, k :: ANFCont): anf-block-help(es-init) end -# return all of the names in a unit -fun unit-names(u :: A.Unit) -> Set: - cases (A.Unit) u: - | u-one(_) => [list-set: ] - | u-base(_, id) => [list-set: id] - | u-mul(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) - | u-div(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) - | u-pow(_, _, shadow u, n) => unit-names(u) - | u-paren(_, shadow u) => unit-names(u) - end -end - -fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: - cases (A.Unit) u: - | u-one(_) => 0 - | u-base(_, id) => - if id == target-id: 1 else: 0 end - | u-mul(_, _, lhs, rhs) => unit-power(target-id, lhs) + unit-power(target-id, rhs) - | u-div(_, _, lhs, rhs) => unit-power(target-id, lhs) + (-1 * unit-power(target-id, rhs)) - | u-pow(_, _, shadow u, n) => unit-power(target-id, u) * n - | u-paren(_, shadow u) => unit-power(target-id, u) - end -end - -fun anf-unit(u-maybe :: Option) -> N.AUnit: - cases(Option) u-maybe: - | none => N.a-unit-one - | some(u) => - unit-names(u).fold( - lam(acc, id): - power = unit-power(id, u) - if power == 0: acc else: N.a-unit-name(id, power, acc) end - end, - N.a-unit-one) - end -end - fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: cases(A.Expr) e: | s-module(l, answer, dvs, dts, provides, types, checks) => @@ -205,13 +168,13 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: end) | s-num(l, n, u-maybe) => - k(N.a-val(l, N.a-num(l, n, anf-unit(u-maybe)))) + k(N.a-val(l, N.a-num(l, n, u-maybe))) # num, den are exact ints, and s-frac desugars to the exact rational num/den | s-frac(l, num, den, u-maybe) => - k(N.a-val(l, N.a-num(l, num / den, anf-unit(u-maybe)))) # Possibly unneeded if removed by desugar? + k(N.a-val(l, N.a-num(l, num / den, u-maybe))) # Possibly unneeded if removed by desugar? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den | s-rfrac(l, num, den, u-maybe) => - k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den), anf-unit(u-maybe)))) # Possibly unneeded if removed by desugar? + k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den), u-maybe))) # Possibly unneeded if removed by desugar? | s-str(l, s) => k(N.a-val(l, N.a-str(l, s))) | s-undefined(l) => k(N.a-val(l, N.a-undefined(l))) | s-bool(l, b) => k(N.a-val(l, N.a-bool(l, b))) @@ -383,7 +346,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: N.a-let( l, bind(l, array-id), - N.a-prim-app(l, "makeArrayN", [list: N.a-num(l, values.length(), N.a-unit-one)], flat-prim-app), + N.a-prim-app(l, "makeArrayN", [list: N.a-num(l, values.length(), none)], flat-prim-app), anf-name-arr-rec(values, array-id, 0, lam(): k(N.a-val(l, N.a-id(l, array-id))) end)) diff --git a/src/arr/compiler/ast-anf.arr b/src/arr/compiler/ast-anf.arr index 6cc24dcdbf..9e58472322 100644 --- a/src/arr/compiler/ast-anf.arr +++ b/src/arr/compiler/ast-anf.arr @@ -63,17 +63,6 @@ sharing: end end -# normalized form of a unit as a linked-list of names and powers -data AUnit: - | a-unit-one with: - method tosource(self): PP.str("") end - | a-unit-name(id :: A.Name, power :: Number, rest :: AUnit) with: - method tosource(self): - PP.separate(str-space, [list: - self.id.tosource(), str-caret, PP.number(self.power), self.rest.tosource()]) - end -end - data AImportType: | a-import-builtin(l :: Loc, lib :: String) with: method tosource(self): PP.str(self.lib) end @@ -475,13 +464,12 @@ data AVal: | a-srcloc(l :: Loc, loc :: Loc) with: method label(self): "a-srcloc" end, method tosource(self): PP.str(torepr(self.loc)) end - | a-num(l :: Loc, n :: Number, u :: AUnit) with: + | a-num(l :: Loc, n :: Number, u :: Option) with: method label(self): "a-num" end, method tosource(self): - cases(AUnit) self.u: - | a-unit-one(_) => PP.number(self.n) - | a-unit-name(_, _, _) => PP.separate(str-percent, - [list: PP.number(self.n), PP.surround(INDENT, 0, PP.langle, self.u.tosource(), PP.rangle)]) + cases(Option) self.u: + | none => PP.number(self.n) + | some(u) => PP.separate(str-percent, [list: PP.number(self.n), u.tosource()]) end end | a-str(l :: Loc, s :: String) with: @@ -725,7 +713,7 @@ default-map-visitor = { method a-srcloc(self, l, loc): a-srcloc(l, loc) end, - method a-num(self, l :: Loc, n :: Number, u :: AUnit): + method a-num(self, l :: Loc, n :: Number, u :: Option): a-num(l, n, u) end, method a-str(self, l :: Loc, s :: String): @@ -832,7 +820,7 @@ where: x = n("x") y = n("y") freevars-e( - a-let(d, a-bind(d, x, A.a-blank), a-val(d, a-num(d, 4, a-unit-one)), + a-let(d, a-bind(d, x, A.a-blank), a-val(d, a-num(d, 4, none)), a-lettable(d, a-val(d, a-id(d, y))))).keys-list() is [list: y.key()] end From ca760e6c49593dad051cb63b29fa13fcc04aef4c Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 5 Jun 2019 11:56:55 -0400 Subject: [PATCH 30/84] Hack to default units to 1 --- src/arr/compiler/anf-loop-compiler.arr | 89 +++++++++++++------------- src/js/base/js-numbers.js | 5 +- src/js/base/runtime.js | 79 +++++++++++++++-------- src/js/trove/parse-pyret.js | 10 ++- t.arr | 14 ++-- 5 files changed, 114 insertions(+), 83 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index d1d0bfe9e2..0224d81a43 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -330,7 +330,47 @@ fun is-function-flat(flatness-env :: FL.FEnv, fun-name :: String) -> Boolean: is-flat-enough(flatness-opt) end +# return all of the names in a unit +fun unit-names(u :: A.Unit) -> Set: + cases (A.Unit) u: + | none(_) => [list-set: ] + | u-base(_, id) => [list-set: id] + | u-mul(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) + | u-div(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) + | u-pow(_, _, shadow u, n) => unit-names(u) + | u-paren(_, shadow u) => unit-names(u) + end +end +fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: + cases (A.Unit) u: + | none(_) => 0 + | u-base(_, id) => + if id == target-id: 1 else: 0 end + | u-mul(_, _, lhs, rhs) => unit-power(target-id, lhs) + unit-power(target-id, rhs) + | u-div(_, _, lhs, rhs) => unit-power(target-id, lhs) + (-1 * unit-power(target-id, rhs)) + | u-pow(_, _, shadow u, n) => unit-power(target-id, u) * n + | u-paren(_, shadow u) => unit-power(target-id, u) + end +end + +fun compile-unit(u-maybe :: Option) -> J.JExpr: + cases(Option) u-maybe block: + | none => J.j-obj(CL.concat-empty) + | some(u) => + fields = unit-names(u).fold( + lam(acc, id): + power = unit-power(id, u) + if power == 0: + acc + else: + CL.concat-cons(j-field(tostring(id), j-num(power)), acc) + end + end, + CL.concat-empty) + j-obj(fields) + end +end fun compile-ann(ann :: A.Ann, visitor) -> DAG.CaseResults%(is-c-exp): cases(A.Ann) ann: @@ -396,8 +436,11 @@ fun compile-ann(ann :: A.Ann, visitor) -> DAG.CaseResults%(is-c-exp): cl-append(compiled-base.other-stmts, compiled-exp.other-stmts) ) | a-unit(l, base, u) => - # TODO(benmusch): change this to compile units - compile-ann(base, visitor) + compiled-base = compile-ann(base, visitor) + compiled-unit = compile-unit(some(u)) + c-exp( + rt-method("makeUnitAnn", [clist: compiled-base.exp, compiled-unit]), + cl-empty) | a-dot(l, m, field) => c-exp( rt-method("getDotAnn", [clist: @@ -1328,48 +1371,6 @@ fun is-id-fn-name(flatness-env :: D.MutableStringDict>, name :: S flatness-env.has-key-now(name) end -# return all of the names in a unit -fun unit-names(u :: A.Unit) -> Set: - cases (A.Unit) u: - | none(_) => [list-set: ] - | u-base(_, id) => [list-set: id] - | u-mul(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) - | u-div(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) - | u-pow(_, _, shadow u, n) => unit-names(u) - | u-paren(_, shadow u) => unit-names(u) - end -end - -fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: - cases (A.Unit) u: - | none(_) => 0 - | u-base(_, id) => - if id == target-id: 1 else: 0 end - | u-mul(_, _, lhs, rhs) => unit-power(target-id, lhs) + unit-power(target-id, rhs) - | u-div(_, _, lhs, rhs) => unit-power(target-id, lhs) + (-1 * unit-power(target-id, rhs)) - | u-pow(_, _, shadow u, n) => unit-power(target-id, u) * n - | u-paren(_, shadow u) => unit-power(target-id, u) - end -end - -fun compile-unit(u-maybe :: Option) -> J.JExpr: - cases(Option) u-maybe block: - | none => J.j-obj(CL.concat-empty) - | some(u) => - fields = unit-names(u).fold( - lam(acc, id): - power = unit-power(id, u) - if power == 0: - acc - else: - CL.concat-cons(j-field(tostring(id), j-num(power)), acc) - end - end, - CL.concat-empty) - j-obj(fields) - end -end - fun compile-a-app(l :: N.Loc, f :: N.AVal, args :: List, compiler, b :: Option, diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 0660427896..1babbd5bcd 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -4312,11 +4312,14 @@ define("pyret-base/js/js-numbers", function() { Numbers['fromFixnum'] = fromFixnum; Numbers['fromString'] = fromString; - Numbers['addUnit'] = addUnit; Numbers['fromSchemeString'] = fromSchemeString; Numbers['makeBignum'] = makeBignum; Numbers['makeRational'] = Rational.makeInstance; Numbers['makeRoughnum'] = Roughnum.makeInstance; + Numbers['addUnit'] = addUnit; + Numbers['getUnit'] = _unitOf; + Numbers['checkUnit'] = _unitEquals; + Numbers['unitToString'] = _unitToString; Numbers['isPyretNumber'] = isPyretNumber; Numbers['isRational'] = isRational; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 2eb90468f5..1f4a35af28 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2553,7 +2553,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function checkAnn(compilerLoc, ann, val, after) { if(isCheapAnnotation(ann)) { - return returnOrRaise(ann.check(compilerLoc, val), val, after); + return returnOrRaise(ann.check(compilerLoc, val, {}), val, after); } else { return checkAnnSafe(compilerLoc, ann, val, after); @@ -2561,7 +2561,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkAnnSafe(compilerLoc, ann, val, after) { return safeCall(function() { - return ann.check(compilerLoc, val); + return ann.check(compilerLoc, val, {}); }, function(result) { return returnOrRaise(result, val, after); }, @@ -2581,7 +2581,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom ); } if (isCheapAnnotation(ann)) { - var result = ann.check(compilerLoc, args[index]); + var result = ann.check(compilerLoc, args[index], {}); if(thisRuntime.ffi.isOk(result)) { return args[index]; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(wrapReason(result)); @@ -2589,7 +2589,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throw "Internal error: got invalid result from annotation check"; } else { return safeCall(function() { - return ann.check(compilerLoc, args[index]); + return ann.check(compilerLoc, args[index], {}); }, function(result) { if(thisRuntime.ffi.isOk(result)) { return args[index]; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(wrapReason(result)); } @@ -2600,7 +2600,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal1(moduleName, funName, arg, ann) { - var result = ann.check(moduleName, arg); + var result = ann.check(moduleName, arg, {}); if(thisRuntime.ffi.isFail(result)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result, "loc"), @@ -2615,7 +2615,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal2(moduleName, funName, arg1, ann1, arg2, ann2) { - var result1 = ann1.check(moduleName, arg1); + var result1 = ann1.check(moduleName, arg1, {}); if(thisRuntime.ffi.isFail(result1)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result1, "loc"), @@ -2627,7 +2627,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result1, "reason")) )); } - var result2 = ann2.check(moduleName, arg2); + var result2 = ann2.check(moduleName, arg2, {}); if(thisRuntime.ffi.isFail(result2)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result2, "loc"), @@ -2642,7 +2642,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal3(moduleName, funName, arg1, ann1, arg2, ann2, arg3, ann3) { - var result1 = ann1.check(moduleName, arg1); + var result1 = ann1.check(moduleName, arg1, {}); if(thisRuntime.ffi.isFail(result1)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result1, "loc"), @@ -2654,7 +2654,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result1, "reason")) )); } - var result2 = ann2.check(moduleName, arg2); + var result2 = ann2.check(moduleName, arg2, {}); if(thisRuntime.ffi.isFail(result2)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result2, "loc"), @@ -2666,7 +2666,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result2, "reason")) )); } - var result3 = ann3.check(moduleName, arg3); + var result3 = ann3.check(moduleName, arg3, {}); if(thisRuntime.ffi.isFail(result3)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result3, "loc"), @@ -2686,7 +2686,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if (!isCheapAnnotation(argsAndAnns[index + 1])) { thisRuntime.ffi.throwMessageException("Internal error: non-stacksafe annotation given to checkArgsInternalInline"); } - var result = argsAndAnns[index + 1].check(moduleName, argsAndAnns[index]); + var result = argsAndAnns[index + 1].check(moduleName, argsAndAnns[index], {}); if(thisRuntime.ffi.isFail(result)) { var onlyArgs = []; for (var i = 0; i < argsAndAnns.length; i += 2) { @@ -2720,13 +2720,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function _checkAnn(compilerLoc, ann, val) { if (isCheapAnnotation(ann)) { - var result = ann.check(compilerLoc, val); + var result = ann.check(compilerLoc, val, {}); if(thisRuntime.ffi.isOk(result)) { return val; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(result); } throw "Internal error: got invalid result from annotation check"; } else { return safeCall(function() { - var res = ann.check(compilerLoc, val); + var res = ann.check(compilerLoc, val, {}); //if(thisRuntime.isContinuation(res)) { console.trace(); } return res; }, function(result) { @@ -2740,11 +2740,11 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function safeCheckAnnArg(compilerLoc, ann, val, after) { if(isCheapAnnotation(ann)) { - return returnOrRaise(ann.check(compilerLoc, val), val, after); + return returnOrRaise(ann.check(compilerLoc, val, {}), val, after); } else { return safeCall(function() { - var res = ann.check(compilerLoc, val); + var res = ann.check(compilerLoc, val, {}); //if(thisRuntime.isContinuation(res)) { console.trace(); } return res; }, function(result) { @@ -2863,7 +2863,12 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.ffi.makeTypeMismatch(val, that.name)); } } - PPrimAnn.prototype.check = function(compilerLoc, val) { + PPrimAnn.prototype.check = function(compilerLoc, val, metadata) { + // TODO(benmusch): Is this really how I wanna do this?? + if (!metadata.checkedUnits) { + return makeUnitAnn(this, {}).check(compilerLoc, val, metadata); + } + var that = this; if(isCheapAnnotation(this)) { return this.checkOrFail(this.pred(val), val, compilerLoc); @@ -2908,13 +2913,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom this.anns.push({ ann: ann, loc: loc }); } - PAnnList.prototype.check = function(compilerLoc, val) { + PAnnList.prototype.check = function(compilerLoc, val, metadata) { var that = this; function checkI(i) { if(i >= that.anns.length) { return thisRuntime.ffi.contractOk; } else { return safeCall(function() { - return that.anns[i].ann.check(compilerLoc, val); + return that.anns[i].ann.check(compilerLoc, val, {}); }, function(passed) { if(thisRuntime.ffi.isOk(passed)) { return checkI(i + 1); } else { @@ -2929,6 +2934,25 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return checkI(0); } + function PUnitAnn(ann, u) { + this.ann = ann; + this.u = u; + } + function makeUnitAnn(ann, u) { + return new PUnitAnn(ann, u); + } + PUnitAnn.prototype.check = function(compilerLoc, val, metadata) { + metadata.checkedUnits = true + if (!jsnums.checkUnit(jsnums.getUnit(val), this.u)) { + var name = "<" + jsnums.unitToString(this.u) + ">"; + return thisRuntime.ffi.contractFail( + makeSrcloc(compilerLoc), + thisRuntime.ffi.makePredicateFailure(val, name)); + } else { + return this.ann.check(compilerLoc, val, metadata); + } + } + function PPredAnn(ann, pred, predname) { this.ann = ann; this.pred = pred; @@ -2947,7 +2971,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom newAnn.flat = true; return newAnn; } - PPredAnn.prototype.check = function(compilerLoc, val) { + PPredAnn.prototype.check = function(compilerLoc, val, metadata) { function fail() { return thisRuntime.ffi.contractFail( makeSrcloc(compilerLoc), @@ -2957,7 +2981,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // NOTE(joe): fast, safe path for flat refinement if(that.flat) { - var result = that.ann.check(compilerLoc, val); + var result = that.ann.check(compilerLoc, val, {}); if(thisRuntime.ffi.isOk(result)) { var predPassed = that.pred.app(val); if(predPassed) { return thisRuntime.ffi.contractOk; } @@ -2969,7 +2993,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } return safeCall(function() { - return that.ann.check(compilerLoc, val); + return that.ann.check(compilerLoc, val, {}); }, function(result) { if(thisRuntime.ffi.isOk(result)) { return safeCall(function() { @@ -3011,7 +3035,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function makeTupleAnn(locs, anns) { return new PTupleAnn(locs, anns); } - PTupleAnn.prototype.check = function(compilerLoc, val) { + PTupleAnn.prototype.check = function(compilerLoc, val, metadata) { var that = this; if(!isTuple(val)) { return thisRuntime.ffi.contractFail( @@ -3027,7 +3051,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // Fast path for flat refinements, since arbitrary stack space can't be consumed if(that.flat) { for(var i = 0; i < that.anns.length; i++) { - var result = that.anns[i].check(that.locs[i], val.vals[i]); + var result = that.anns[i].check(that.locs[i], val.vals[i], {}); if(!thisRuntime.ffi.isOk(result)) { return this.createTupleFailureError(compilerLoc, val, this.anns[i], result); //return result; @@ -3042,7 +3066,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return safeCall(function() { var thisChecker = remainingAnns.pop(); thisAnn = thisChecker; - return thisChecker.check(that.locs[that.locs.length - remainingAnns.length], val.vals[remainingAnns.length]); + return thisChecker.check(that.locs[that.locs.length - remainingAnns.length], val.vals[remainingAnns.length], {}); }, function(result) { if(thisRuntime.ffi.isOk(result)) { if(remainingAnns.length === 0) { return thisRuntime.ffi.contractOk; } @@ -3132,7 +3156,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom ])) ); }; - PRecordAnn.prototype.check = function(compilerLoc, val) { + PRecordAnn.prototype.check = function(compilerLoc, val, metadata) { var that = this; if(!isObject(val)) { return thisRuntime.ffi.contractFail( @@ -3150,7 +3174,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if(that.flat) { for(var i = 0; i < that.fields.length; i++) { var thisField = that.fields[i]; - var result = that.anns[thisField].check(that.locs[i], getColonField(val, thisField)); + var result = that.anns[thisField].check(that.locs[i], getColonField(val, thisField), {}); if(!thisRuntime.ffi.isOk(result)) { return result; } @@ -3164,7 +3188,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return safeCall(function() { thisField = remainingFields.pop(); var thisChecker = that.anns[thisField]; - return thisChecker.check(that.locs[that.locs.length - remainingFields.length], getColonField(val, thisField)); + return thisChecker.check(that.locs[that.locs.length - remainingFields.length], getColonField(val, thisField), {}); }, function(result) { if(thisRuntime.ffi.isOk(result)) { if(remainingFields.length === 0) { return thisRuntime.ffi.contractOk; } @@ -5751,6 +5775,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom 'checkConstructorArgs': checkConstructorArgs, 'checkConstructorArgs2': checkConstructorArgs2, 'getDotAnn': getDotAnn, + 'makeUnitAnn': makeUnitAnn, 'makePredAnn': makePredAnn, 'makeFlatPredAnn': makeFlatPredAnn, 'makePrimitiveAnn': makePrimitiveAnn, diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 1ce6575d0d..7c91c9667c 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -1186,7 +1186,10 @@ if (node.kids.length == 1) { // (frac-expr n) return RUNTIME.getField(ast, 's-frac') - .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1], {})); + .app(pos(node.pos), + RUNTIME.makeNumberFromString(numden[0], {}), + RUNTIME.makeNumberFromString(numden[1], {}), + RUNTIME.ffi.makeNone()); } else { // (frac-expr n PERCENT dim-expr) return RUNTIME.getField(ast, 's-frac') @@ -1202,7 +1205,10 @@ if (node.kids.length == 1) { // (rfrac-expr n) return RUNTIME.getField(ast, 's-rfrac') - .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1], {})); + .app(pos(node.pos), + RUNTIME.makeNumberFromString(numden[0], {}), + RUNTIME.makeNumberFromString(numden[1], {}), + RUNTIME.ffi.makeNone()); } else { // (rfrac-expr n PERCENT dim-expr) return RUNTIME.getField(ast, 's-rfrac') diff --git a/t.arr b/t.arr index a1fe8e3f22..012f12e1a6 100644 --- a/t.arr +++ b/t.arr @@ -1,9 +1,5 @@ -print(1% / 1%) -print("\n") -print(1% * 1%) -print("\n") -print(1% + 1%) -print("\n") -print(10 / 0%) -print("\n") -print(1 - 1%) +fun f(n :: Number%(num-is-integer)) block: + print(n) + print("\n") +end +f(2%) From 05fd7927c5d4c1665c7ac373c4bb70fea51a94fd Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 5 Jun 2019 12:28:52 -0400 Subject: [PATCH 31/84] Change from a runtime approach to a desugar approach --- src/arr/compiler/anf-loop-compiler.arr | 4 +- src/arr/compiler/desugar.arr | 13 ++++-- src/js/base/runtime.js | 63 ++++++++++++-------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index 0224d81a43..488b2d92e1 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -333,7 +333,7 @@ end # return all of the names in a unit fun unit-names(u :: A.Unit) -> Set: cases (A.Unit) u: - | none(_) => [list-set: ] + | u-one(_) => [list-set: ] | u-base(_, id) => [list-set: id] | u-mul(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) | u-div(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) @@ -344,7 +344,7 @@ end fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: cases (A.Unit) u: - | none(_) => 0 + | u-one(_) => 0 | u-base(_, id) => if id == target-id: 1 else: 0 end | u-mul(_, _, lhs, rhs) => unit-power(target-id, lhs) + unit-power(target-id, rhs) diff --git a/src/arr/compiler/desugar.arr b/src/arr/compiler/desugar.arr index 6126e11f55..57cbf3b5ff 100644 --- a/src/arr/compiler/desugar.arr +++ b/src/arr/compiler/desugar.arr @@ -89,11 +89,13 @@ end fun desugar-afield(f :: A.AField) -> A.AField: A.a-field(f.l, f.name, desugar-ann(f.ann)) end -fun desugar-ann(a :: A.Ann) -> A.Ann: +fun desugar-ann-helper(a :: A.Ann, seen-unit :: Boolean) -> A.Ann: cases(A.Ann) a: | a-blank => a | a-any(_) => a - | a-name(_, _) => a + | a-name(_, _) => + # TODO(benmusch): should we check if this is a number-related name? + if seen-unit: a else: A.a-unit(A.dummy-loc, a, A.u-one(A.dummy-loc)) end | a-type-var(_, _) => a | a-dot(_, _, _) => a | a-arrow(l, args, ret, use-parens) => @@ -109,11 +111,14 @@ fun desugar-ann(a :: A.Ann) -> A.Ann: | a-tuple(l, fields) => A.a-tuple(l, fields.map(desugar-ann)) | a-unit(l, ann, u) => - A.a-unit(l, desugar-ann(ann), u) + A.a-unit(l, desugar-ann-helper(ann, true), u) | a-pred(l, ann, exp) => - A.a-pred(l, desugar-ann(ann), desugar-expr(exp)) + A.a-pred(l, desugar-ann-helper(ann, seen-unit), desugar-expr(exp)) end end +fun desugar-ann(a :: A.Ann) -> A.Ann: + desugar-ann-helper(a, false) +end fun desugar(program :: A.Program): doc: ``` diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 1f4a35af28..80419afc65 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2553,7 +2553,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function checkAnn(compilerLoc, ann, val, after) { if(isCheapAnnotation(ann)) { - return returnOrRaise(ann.check(compilerLoc, val, {}), val, after); + return returnOrRaise(ann.check(compilerLoc, val), val, after); } else { return checkAnnSafe(compilerLoc, ann, val, after); @@ -2561,7 +2561,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkAnnSafe(compilerLoc, ann, val, after) { return safeCall(function() { - return ann.check(compilerLoc, val, {}); + return ann.check(compilerLoc, val); }, function(result) { return returnOrRaise(result, val, after); }, @@ -2581,7 +2581,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom ); } if (isCheapAnnotation(ann)) { - var result = ann.check(compilerLoc, args[index], {}); + var result = ann.check(compilerLoc, args[index]); if(thisRuntime.ffi.isOk(result)) { return args[index]; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(wrapReason(result)); @@ -2589,7 +2589,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throw "Internal error: got invalid result from annotation check"; } else { return safeCall(function() { - return ann.check(compilerLoc, args[index], {}); + return ann.check(compilerLoc, args[index]); }, function(result) { if(thisRuntime.ffi.isOk(result)) { return args[index]; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(wrapReason(result)); } @@ -2600,7 +2600,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal1(moduleName, funName, arg, ann) { - var result = ann.check(moduleName, arg, {}); + var result = ann.check(moduleName, arg); if(thisRuntime.ffi.isFail(result)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result, "loc"), @@ -2615,7 +2615,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal2(moduleName, funName, arg1, ann1, arg2, ann2) { - var result1 = ann1.check(moduleName, arg1, {}); + var result1 = ann1.check(moduleName, arg1); if(thisRuntime.ffi.isFail(result1)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result1, "loc"), @@ -2627,7 +2627,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result1, "reason")) )); } - var result2 = ann2.check(moduleName, arg2, {}); + var result2 = ann2.check(moduleName, arg2); if(thisRuntime.ffi.isFail(result2)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result2, "loc"), @@ -2642,7 +2642,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal3(moduleName, funName, arg1, ann1, arg2, ann2, arg3, ann3) { - var result1 = ann1.check(moduleName, arg1, {}); + var result1 = ann1.check(moduleName, arg1); if(thisRuntime.ffi.isFail(result1)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result1, "loc"), @@ -2654,7 +2654,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result1, "reason")) )); } - var result2 = ann2.check(moduleName, arg2, {}); + var result2 = ann2.check(moduleName, arg2); if(thisRuntime.ffi.isFail(result2)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result2, "loc"), @@ -2666,7 +2666,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result2, "reason")) )); } - var result3 = ann3.check(moduleName, arg3, {}); + var result3 = ann3.check(moduleName, arg3); if(thisRuntime.ffi.isFail(result3)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result3, "loc"), @@ -2686,7 +2686,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if (!isCheapAnnotation(argsAndAnns[index + 1])) { thisRuntime.ffi.throwMessageException("Internal error: non-stacksafe annotation given to checkArgsInternalInline"); } - var result = argsAndAnns[index + 1].check(moduleName, argsAndAnns[index], {}); + var result = argsAndAnns[index + 1].check(moduleName, argsAndAnns[index]); if(thisRuntime.ffi.isFail(result)) { var onlyArgs = []; for (var i = 0; i < argsAndAnns.length; i += 2) { @@ -2720,13 +2720,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function _checkAnn(compilerLoc, ann, val) { if (isCheapAnnotation(ann)) { - var result = ann.check(compilerLoc, val, {}); + var result = ann.check(compilerLoc, val); if(thisRuntime.ffi.isOk(result)) { return val; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(result); } throw "Internal error: got invalid result from annotation check"; } else { return safeCall(function() { - var res = ann.check(compilerLoc, val, {}); + var res = ann.check(compilerLoc, val); //if(thisRuntime.isContinuation(res)) { console.trace(); } return res; }, function(result) { @@ -2740,11 +2740,11 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function safeCheckAnnArg(compilerLoc, ann, val, after) { if(isCheapAnnotation(ann)) { - return returnOrRaise(ann.check(compilerLoc, val, {}), val, after); + return returnOrRaise(ann.check(compilerLoc, val), val, after); } else { return safeCall(function() { - var res = ann.check(compilerLoc, val, {}); + var res = ann.check(compilerLoc, val); //if(thisRuntime.isContinuation(res)) { console.trace(); } return res; }, function(result) { @@ -2863,12 +2863,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.ffi.makeTypeMismatch(val, that.name)); } } - PPrimAnn.prototype.check = function(compilerLoc, val, metadata) { - // TODO(benmusch): Is this really how I wanna do this?? - if (!metadata.checkedUnits) { - return makeUnitAnn(this, {}).check(compilerLoc, val, metadata); - } - + PPrimAnn.prototype.check = function(compilerLoc, val) { var that = this; if(isCheapAnnotation(this)) { return this.checkOrFail(this.pred(val), val, compilerLoc); @@ -2913,13 +2908,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom this.anns.push({ ann: ann, loc: loc }); } - PAnnList.prototype.check = function(compilerLoc, val, metadata) { + PAnnList.prototype.check = function(compilerLoc, val) { var that = this; function checkI(i) { if(i >= that.anns.length) { return thisRuntime.ffi.contractOk; } else { return safeCall(function() { - return that.anns[i].ann.check(compilerLoc, val, {}); + return that.anns[i].ann.check(compilerLoc, val); }, function(passed) { if(thisRuntime.ffi.isOk(passed)) { return checkI(i + 1); } else { @@ -2941,7 +2936,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function makeUnitAnn(ann, u) { return new PUnitAnn(ann, u); } - PUnitAnn.prototype.check = function(compilerLoc, val, metadata) { + PUnitAnn.prototype.check = function(compilerLoc, val) { metadata.checkedUnits = true if (!jsnums.checkUnit(jsnums.getUnit(val), this.u)) { var name = "<" + jsnums.unitToString(this.u) + ">"; @@ -2949,7 +2944,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom makeSrcloc(compilerLoc), thisRuntime.ffi.makePredicateFailure(val, name)); } else { - return this.ann.check(compilerLoc, val, metadata); + return this.ann.check(compilerLoc, val); } } @@ -2971,7 +2966,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom newAnn.flat = true; return newAnn; } - PPredAnn.prototype.check = function(compilerLoc, val, metadata) { + PPredAnn.prototype.check = function(compilerLoc, val) { function fail() { return thisRuntime.ffi.contractFail( makeSrcloc(compilerLoc), @@ -2981,7 +2976,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // NOTE(joe): fast, safe path for flat refinement if(that.flat) { - var result = that.ann.check(compilerLoc, val, {}); + var result = that.ann.check(compilerLoc, val); if(thisRuntime.ffi.isOk(result)) { var predPassed = that.pred.app(val); if(predPassed) { return thisRuntime.ffi.contractOk; } @@ -2993,7 +2988,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } return safeCall(function() { - return that.ann.check(compilerLoc, val, {}); + return that.ann.check(compilerLoc, val); }, function(result) { if(thisRuntime.ffi.isOk(result)) { return safeCall(function() { @@ -3035,7 +3030,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function makeTupleAnn(locs, anns) { return new PTupleAnn(locs, anns); } - PTupleAnn.prototype.check = function(compilerLoc, val, metadata) { + PTupleAnn.prototype.check = function(compilerLoc, val) { var that = this; if(!isTuple(val)) { return thisRuntime.ffi.contractFail( @@ -3051,7 +3046,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // Fast path for flat refinements, since arbitrary stack space can't be consumed if(that.flat) { for(var i = 0; i < that.anns.length; i++) { - var result = that.anns[i].check(that.locs[i], val.vals[i], {}); + var result = that.anns[i].check(that.locs[i], val.vals[i]); if(!thisRuntime.ffi.isOk(result)) { return this.createTupleFailureError(compilerLoc, val, this.anns[i], result); //return result; @@ -3066,7 +3061,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return safeCall(function() { var thisChecker = remainingAnns.pop(); thisAnn = thisChecker; - return thisChecker.check(that.locs[that.locs.length - remainingAnns.length], val.vals[remainingAnns.length], {}); + return thisChecker.check(that.locs[that.locs.length - remainingAnns.length], val.vals[remainingAnns.length]); }, function(result) { if(thisRuntime.ffi.isOk(result)) { if(remainingAnns.length === 0) { return thisRuntime.ffi.contractOk; } @@ -3156,7 +3151,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom ])) ); }; - PRecordAnn.prototype.check = function(compilerLoc, val, metadata) { + PRecordAnn.prototype.check = function(compilerLoc, val) { var that = this; if(!isObject(val)) { return thisRuntime.ffi.contractFail( @@ -3174,7 +3169,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if(that.flat) { for(var i = 0; i < that.fields.length; i++) { var thisField = that.fields[i]; - var result = that.anns[thisField].check(that.locs[i], getColonField(val, thisField), {}); + var result = that.anns[thisField].check(that.locs[i], getColonField(val, thisField)); if(!thisRuntime.ffi.isOk(result)) { return result; } @@ -3188,7 +3183,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return safeCall(function() { thisField = remainingFields.pop(); var thisChecker = that.anns[thisField]; - return thisChecker.check(that.locs[that.locs.length - remainingFields.length], getColonField(val, thisField), {}); + return thisChecker.check(that.locs[that.locs.length - remainingFields.length], getColonField(val, thisField)); }, function(result) { if(thisRuntime.ffi.isOk(result)) { if(remainingFields.length === 0) { return thisRuntime.ffi.contractOk; } From 927c4c3b580bc2478ee476e08aefe9db61ef52b3 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 5 Jun 2019 13:01:12 -0400 Subject: [PATCH 32/84] Fix JS bug --- src/arr/compiler/flatness.arr | 2 +- src/js/base/runtime.js | 2 +- t.arr | 8 +++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/arr/compiler/flatness.arr b/src/arr/compiler/flatness.arr index 4c097ed411..ac429983f8 100644 --- a/src/arr/compiler/flatness.arr +++ b/src/arr/compiler/flatness.arr @@ -69,7 +69,7 @@ fun ann-flatness(ann :: A.Ann, val-env :: FEnv, ann-env :: FEnv) -> Flatness: ann-flatness(base, val-env, ann-env), val-flatness ) - | a-unit(l, base, u) => some(0) + | a-unit(l, base, u) => ann-flatness(base, val-env, ann-env) | a-dot(l, obj, field) => none # TODO(joe): module-ids should make this doable... | a-checked(checked, residual) => none end diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 80419afc65..d255dfea3e 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2932,12 +2932,12 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function PUnitAnn(ann, u) { this.ann = ann; this.u = u; + this.flat = true; } function makeUnitAnn(ann, u) { return new PUnitAnn(ann, u); } PUnitAnn.prototype.check = function(compilerLoc, val) { - metadata.checkedUnits = true if (!jsnums.checkUnit(jsnums.getUnit(val), this.u)) { var name = "<" + jsnums.unitToString(this.u) + ">"; return thisRuntime.ffi.contractFail( diff --git a/t.arr b/t.arr index 012f12e1a6..34630b3690 100644 --- a/t.arr +++ b/t.arr @@ -1,5 +1,3 @@ -fun f(n :: Number%(num-is-integer)) block: - print(n) - print("\n") -end -f(2%) +fun id(x): x end + fun f(x) -> Number: cases(List) empty: | empty => id(x) | link(_, _) => id(x) end end + f('foo') From 4d478ab33fc0951430e9c10d4f6783d1af6b9606 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 5 Jun 2019 14:38:54 -0400 Subject: [PATCH 33/84] Add support for underscore annotations --- src/arr/compiler/desugar.arr | 4 +-- src/js/base/js-numbers.js | 3 ++ src/js/base/runtime.js | 3 +- t.arr | 5 ++-- tests/pyret/tests/test-contracts.arr | 45 ++++++++++++++++++++++++++++ 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/arr/compiler/desugar.arr b/src/arr/compiler/desugar.arr index 57cbf3b5ff..217aec4413 100644 --- a/src/arr/compiler/desugar.arr +++ b/src/arr/compiler/desugar.arr @@ -93,9 +93,9 @@ fun desugar-ann-helper(a :: A.Ann, seen-unit :: Boolean) -> A.Ann: cases(A.Ann) a: | a-blank => a | a-any(_) => a - | a-name(_, _) => + | a-name(l, _) => # TODO(benmusch): should we check if this is a number-related name? - if seen-unit: a else: A.a-unit(A.dummy-loc, a, A.u-one(A.dummy-loc)) end + if seen-unit: a else: A.a-unit(l, a, A.u-one(A.dummy-loc)) end | a-type-var(_, _) => a | a-dot(_, _, _) => a | a-arrow(l, args, ret, use-parens) => diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 1babbd5bcd..8194970a79 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1060,6 +1060,9 @@ define("pyret-base/js/js-numbers", function() { // Unit operations + // hard-coded constant of what the underscore unit parses to + var UNIT_ANY = { "_": 1 } + var _withUnit = function(n, u) { if (n instanceof Unitnum) { return new Unitnum(n.n, u); diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index d255dfea3e..0921dc4615 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2933,12 +2933,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom this.ann = ann; this.u = u; this.flat = true; + this.isAny = jsnums.checkUnit(this.u, { "_": 1 }) } function makeUnitAnn(ann, u) { return new PUnitAnn(ann, u); } PUnitAnn.prototype.check = function(compilerLoc, val) { - if (!jsnums.checkUnit(jsnums.getUnit(val), this.u)) { + if (!this.isAny && !jsnums.checkUnit(jsnums.getUnit(val), this.u)) { var name = "<" + jsnums.unitToString(this.u) + ">"; return thisRuntime.ffi.contractFail( makeSrcloc(compilerLoc), diff --git a/t.arr b/t.arr index 34630b3690..0b8dc9c2e0 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,2 @@ -fun id(x): x end - fun f(x) -> Number: cases(List) empty: | empty => id(x) | link(_, _) => id(x) end end - f('foo') +fun id(n :: Number): n end +id(1%) diff --git a/tests/pyret/tests/test-contracts.arr b/tests/pyret/tests/test-contracts.arr index 9e5d9b8f95..45a0620f36 100644 --- a/tests/pyret/tests/test-contracts.arr +++ b/tests/pyret/tests/test-contracts.arr @@ -457,3 +457,48 @@ check "standalone contract statements": ```) is%(output) success end +check "Should notice unit mis-matches": + run-str( + ``` + fun id(n :: Number): n end + id(1%) + ```) is%(output) contract-error + run-str( + ``` + fun id(n :: Number%): n end + id(1%) + ```) is%(output) contract-error + run-str( + ``` + fun id(n :: Number%%(num-is-integer)): n end + id(1%) + ```) is%(output) contract-error + run-str( + ``` + type N = Number%(num-is-integer) + fun id(n :: N%): n end + id(1%) + ```) is%(output) contract-error + run-str( + ``` + type N = Number%%(num-is-integer) + fun id(n :: N): n end + id(1%) + ```) is%(output) contract-error + + run-str( + ``` + fun id(n :: Number%%(num-is-integer)): n end + id(1%) + ```) is%(output) success + run-str( + ``` + fun id(n :: Number%<_>%(num-is-integer)): n end + id(1%) + ```) is%(output) success + run-str( + ``` + fun id(n :: Number%<_>%(num-is-integer)): n end + id(1) + ```) is%(output) success +end From ca16198ea713ba3abb592e7d2800080c2b88b590 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 5 Jun 2019 15:16:50 -0400 Subject: [PATCH 34/84] Tests for contracts --- src/js/base/js-numbers.js | 3 -- t.arr | 5 ++-- tests/pyret/tests/test-contracts.arr | 42 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 8194970a79..1babbd5bcd 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1060,9 +1060,6 @@ define("pyret-base/js/js-numbers", function() { // Unit operations - // hard-coded constant of what the underscore unit parses to - var UNIT_ANY = { "_": 1 } - var _withUnit = function(n, u) { if (n instanceof Unitnum) { return new Unitnum(n.n, u); diff --git a/t.arr b/t.arr index 0b8dc9c2e0..b5ad99c9b6 100644 --- a/t.arr +++ b/t.arr @@ -1,2 +1,3 @@ -fun id(n :: Number): n end -id(1%) +fun id(n :: {Number%; Number%}): n end +# TODO(benmusch): Why does this return such a weird contract error? +id({1%; 2%}) diff --git a/tests/pyret/tests/test-contracts.arr b/tests/pyret/tests/test-contracts.arr index 45a0620f36..180110842b 100644 --- a/tests/pyret/tests/test-contracts.arr +++ b/tests/pyret/tests/test-contracts.arr @@ -485,6 +485,27 @@ check "Should notice unit mis-matches": fun id(n :: N): n end id(1%) ```) is%(output) contract-error + run-str( + ``` + type N = Number%%(num-is-integer) + fun id(n :: N%): n end + id(1%) + ```) is%(output) contract-error + run-str( + ``` + fun id(n :: Any%): n end + id(1%) + ```) is%(output) contract-error + run-str( + ``` + fun id(n :: {Number%; Number%}): n end + id({1%; 1%}) + ```) is%(output) contract-error + run-str( + ``` + fun id(n :: { a :: Number%, b :: Number%}): n end + id({ a: 1%, b: 1%}) + ```) is%(output) contract-error run-str( ``` @@ -501,4 +522,25 @@ check "Should notice unit mis-matches": fun id(n :: Number%<_>%(num-is-integer)): n end id(1) ```) is%(output) success + run-str( + ``` + fun id(n :: Any): n end + id(1%) + ```) is%(output) success + run-str( + ``` + fun id(n :: Any%): n end + id(1%) + ```) is%(output) success + run-str( + ``` + fun id(n :: {Number%; Number%}): n end + id({1%; 1%}) + ```) is%(output) success + run-str( + ``` + type N = Number%%(num-is-integer) + fun id(n :: { a :: Number%, b :: Number%}): n end + id({ a: 1%, b: 1%}) + ```) is%(output) success end From 020f04e503f4ed00d2b01ddb0b95500173372001 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 5 Jun 2019 15:55:33 -0400 Subject: [PATCH 35/84] Add wf checks for underscores in units --- src/arr/compiler/compile-structs.arr | 21 ++++++++++++++ src/arr/compiler/well-formed.arr | 40 +++++++++++++++++++------- t.arr | 4 +-- tests/pyret/tests/test-well-formed.arr | 6 ++++ 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/arr/compiler/compile-structs.arr b/src/arr/compiler/compile-structs.arr index d52414a79d..a87353dee5 100644 --- a/src/arr/compiler/compile-structs.arr +++ b/src/arr/compiler/compile-structs.arr @@ -997,6 +997,27 @@ data CompileError: ED.text(self.kind), ED.text(".")]] end + | underscore-as-unit(l :: Loc) with: + method render-fancy-reason(self): + [ED.error: + [ED.para: + ED.text("The underscore "), + ED.code(ED.highlight(ED.text("_"), [ED.locs: self.l], 0)), + ED.text(" is invalid."), + ED.text(" Underscores can only be used in units when"), + ED.text(" they are an annotation and there is nothing else in the unit expression")]] + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.text("The underscore "), + ED.code(ED.text("_")), + ED.text(" at "), + ED.loc(self.l), + ED.text(" is invalid."), + ED.text(" Underscores can only be used in units when"), + ED.text(" they are an annotation and there is nothing else in the unit expression")]] + end | underscore-as-pattern(l :: Loc) with: method render-fancy-reason(self): [ED.error: diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index f77856856b..52c9b1535e 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -294,7 +294,7 @@ end # TODO(benmuch): Consider refactoring to make this a proper visitor # and shadow the reachable-ops pattern -fun wf-unit(u :: A.Unit, parent-maybe :: Option) block: +fun wf-unit-helper(u :: A.Unit, parent-maybe :: Option) block: cases(Option) parent-maybe block: | some(p) => if unit-is-op(u) and unit-is-op(p) and not(u.label() == p.label()): @@ -307,19 +307,37 @@ fun wf-unit(u :: A.Unit, parent-maybe :: Option) block: cases(A.Unit) u block: | u-mul(_, _, lhs, rhs) => - wf-unit(lhs, some(u)) - wf-unit(rhs, some(u)) + wf-unit-helper(lhs, some(u)) + wf-unit-helper(rhs, some(u)) | u-div(_, _, lhs, rhs) => - wf-unit(lhs, some(u)) - wf-unit(rhs, some(u)) - | u-paren(_, paren-u) => wf-unit(paren-u, some(u)) + wf-unit-helper(lhs, some(u)) + wf-unit-helper(rhs, some(u)) + | u-paren(_, paren-u) => wf-unit-helper(paren-u, some(u)) | u-pow(l, _, pow-u, n) => if (n == 0) or not(num-is-integer(n)): add-error(C.invalid-unit-power(l, n)) else: nothing end - wf-unit(pow-u, some(u)) + wf-unit-helper(pow-u, some(u)) + | u-base(l, id) => + if A.is-s-underscore(id) and is-some(parent-maybe): + add-error(C.underscore-as-unit(l)) + else: + nothing + end + end +end +fun wf-unit(u :: A.Unit, allow-underscore :: Boolean) block: + wf-unit-helper(u, none) + + cases(A.Unit) u: + | u-base(l, id) => + if A.is-s-underscore(id) and not(allow-underscore): + add-error(C.underscore-as-unit(l)) + else: + nothing + end | else => nothing end end @@ -917,7 +935,7 @@ well-formed-visitor = A.default-iter-visitor.{ cases(Option) unit-maybe: | none => nothing - | some(u) => wf-unit(u, none) + | some(u) => wf-unit(u, false) end true @@ -929,7 +947,7 @@ well-formed-visitor = A.default-iter-visitor.{ cases(Option) unit-maybe: | none => nothing - | some(u) => wf-unit(u, none) + | some(u) => wf-unit(u, false) end true @@ -1056,7 +1074,7 @@ well-formed-visitor = A.default-iter-visitor.{ true end, method a-unit(self, l, base, u) block: - wf-unit(u, none) + wf-unit(u, true) cases(Option) unit-ann-loc(base): | none => nothing | some(dup-l) => add-error(C.multiple-unit-anns(l, dup-l)) @@ -1066,7 +1084,7 @@ well-formed-visitor = A.default-iter-visitor.{ method s-num(self, l, n, unit-maybe) block: cases(Option) unit-maybe: | none => nothing - | some(u) => wf-unit(u, none) + | some(u) => wf-unit(u, false) end true end diff --git a/t.arr b/t.arr index b5ad99c9b6..adeea41f4a 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,3 @@ -fun id(n :: {Number%; Number%}): n end +fun id(n :: {Number%; Number%<_>}): n end # TODO(benmusch): Why does this return such a weird contract error? -id({1%; 2%}) +id({1%; 2%}) diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index 241b519b75..839c764db6 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -261,6 +261,12 @@ check "underscores": run-str("{a: 1}.{_: 2}") is%(output) compile-error(CS.is-underscore-as) run-str("{a: 1}._") is%(output) compile-error(CS.is-underscore-as) + + run-str("2%<_>") is%(output) compile-error(CS.is-underscore-as-unit) + run-str("n :: Number%<_ * m> = 1") is%(output) compile-error(CS.is-underscore-as-unit) + run-str("n :: Number%<_ / m> = 1") is%(output) compile-error(CS.is-underscore-as-unit) + run-str("n :: Number%<(_)> = 1") is%(output) compile-error(CS.is-underscore-as-unit) + run-str("n :: Number%<_ ^ 2> = 1") is%(output) compile-error(CS.is-underscore-as-unit) end check "unit annotations": From bf0689dfd59c0e8018d3556dfe04a337d6d21207 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 5 Jun 2019 17:33:49 -0400 Subject: [PATCH 36/84] Enforce unit quantity and ordering via grammar, not wf --- src/arr/compiler/compile-structs.arr | 17 ----------------- src/arr/compiler/well-formed.arr | 12 ------------ src/js/base/pyret-grammar.bnf | 2 +- tests/pyret/tests/test-parse.arr | 21 ++++++++++----------- tests/pyret/tests/test-well-formed.arr | 3 --- 5 files changed, 11 insertions(+), 44 deletions(-) diff --git a/src/arr/compiler/compile-structs.arr b/src/arr/compiler/compile-structs.arr index a87353dee5..6a319045d4 100644 --- a/src/arr/compiler/compile-structs.arr +++ b/src/arr/compiler/compile-structs.arr @@ -1076,23 +1076,6 @@ data CompileError: ED.loc(self.l), ED.text(" cannot be used where a type annotation is expected.")]] end - | multiple-unit-anns(l :: Loc, dup-l :: Loc) with: - method render-fancy-reason(self): - [ED.error: - [ED.para: - ED.text("The annotation had multiple "), - ED.code(ED.highlight(ED.text("unit annotations"), [ED.locs: self.l, self.dup-l])), - ED.text(", but only one is allowed")]] - end, - method render-reason(self): - [ED.error: - [ED.para: - ED.text("Tried to add a unit annotation at "), - ED.loc(self.dup-l), - ED.text(", but one already exists at "), - ED.loc(self.l), - ED.text(". Do not add more than one unit annotation.")]] - end | block-needed(expr-loc :: Loc, blocks :: List) with: method render-fancy-reason(self): if self.blocks.length() > 1: diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index 52c9b1535e..c162597674 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -342,14 +342,6 @@ fun wf-unit(u :: A.Unit, allow-underscore :: Boolean) block: end end -fun unit-ann-loc(ann :: A.Ann) -> Option: - cases(A.Ann) ann: - | a-pred(l, base, exp) => unit-ann-loc(base) - | a-unit(l, base, u) => some(l) - | else => none - end -end - fun wf-last-stmt(block-loc, stmt :: A.Expr): cases(A.Expr) stmt: | s-let(l, _, _, _) => add-error(C.block-ending(l, block-loc, "let-binding")) @@ -1075,10 +1067,6 @@ well-formed-visitor = A.default-iter-visitor.{ end, method a-unit(self, l, base, u) block: wf-unit(u, true) - cases(Option) unit-ann-loc(base): - | none => nothing - | some(dup-l) => add-error(C.multiple-unit-anns(l, dup-l)) - end true end, method s-num(self, l, n, unit-maybe) block: diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index 6023ec8ada..418cb99311 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -262,7 +262,7 @@ app-ann: (name-ann|dot-ann) LANGLE comma-anns (RANGLE|GT) comma-anns: ann (COMMA ann)* # TODO: Can we enforce ordering here? -unit-ann: ann PERCENT dim-expr +unit-ann: name-ann PERCENT dim-expr pred-ann: ann PERCENT (PARENSPACE|PARENNOSPACE) id-expr RPAREN diff --git a/tests/pyret/tests/test-parse.arr b/tests/pyret/tests/test-parse.arr index 1fc6ec7e14..d8e3f5824a 100644 --- a/tests/pyret/tests/test-parse.arr +++ b/tests/pyret/tests/test-parse.arr @@ -492,19 +492,18 @@ check "should parse unit-annotated numbers": end check "should parse unit-anns numbers": - does-parse("n :: Number% = 0") is true - does-parse("n :: Number%%(is-even) = 0") is true - does-parse("n :: Number%%(is-two)%(is-even) = 0") is true - does-parse("n :: String% = 'foo'") is true - - # TODO(benmusch): Should these parse? - does-parse("n :: Number%(is-two)% = 0") is true + does-parse("n :: Number%% = 0") is false + does-parse("n :: Number%%(is-even)% = 0") is false + does-parse("n :: Number%(is-two)% = 0") is false + does-parse("n :: Number%<>%(is-even) = 0") is false + does-parse("n :: Number%<> = 0") is false # should be caught by wf: - does-parse("n :: Number%% = 0") is true - does-parse("n :: Number%%(is-even)% = 0") is true does-parse("n :: Number% = 0") is true - does-parse("n :: Number%<>%(is-even) = 0") is false - does-parse("n :: Number%<> = 0") is false + does-parse("n :: Number%%(is-even) = 0") is true + does-parse("n :: Number% = 0") is true + does-parse("n :: Number%%(is-even) = 0") is true + does-parse("n :: Number%%(is-two)%(is-even) = 0") is true + does-parse("n :: String% = 'foo'") is true end diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index 839c764db6..b1959208a4 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -291,9 +291,6 @@ check "unit annotations": run-str("~1/2%") is%(output) compile-error(CS.is-invalid-unit-power) run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-invalid-unit-power) run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-invalid-unit-power) - - run-str("var n :: Number%% = 0") is%(output) compile-error(CS.is-multiple-unit-anns) - run-str("var n :: Number%%(num-is-integer)% = 0") is%(output) compile-error(CS.is-multiple-unit-anns) end #| From 43eada143d0ecb4e2c9acfb30a9eb0f264954149 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 6 Jun 2019 14:47:17 -0400 Subject: [PATCH 37/84] Switch numbers to always have a unit, start adding a visitor for units --- pitometer/programs/ast.arr | 2 +- src/arr/compiler/anf-loop-compiler.arr | 53 +++++++++--------- src/arr/compiler/anf.arr | 14 ++--- src/arr/compiler/ast-anf.arr | 6 +- src/arr/compiler/desugar.arr | 4 +- src/arr/compiler/resolve-scope.arr | 37 +++++++------ src/arr/compiler/type-check.arr | 2 +- src/arr/compiler/well-formed.arr | 36 ++++-------- src/arr/trove/ast.arr | 77 ++++++++++++++++---------- src/js/trove/parse-pyret.js | 15 +++-- t.arr | 6 +- tests/pyret/tests/test-contracts.arr | 8 +++ 12 files changed, 138 insertions(+), 122 deletions(-) diff --git a/pitometer/programs/ast.arr b/pitometer/programs/ast.arr index bab26a52a5..dfaebe5835 100644 --- a/pitometer/programs/ast.arr +++ b/pitometer/programs/ast.arr @@ -3070,7 +3070,7 @@ dummy-loc-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(dummy-loc, loc) end, - method s-num(self, l :: Loc, n :: Number, u :: Option): + method s-num(self, l :: Loc, n :: Number, u :: Unit): s-num(dummy-loc, n, u) end, method s-frac(self, l :: Loc, num :: Number, den :: Number): diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index 488b2d92e1..94b8f066e0 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -354,22 +354,18 @@ fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: end end -fun compile-unit(u-maybe :: Option) -> J.JExpr: - cases(Option) u-maybe block: - | none => J.j-obj(CL.concat-empty) - | some(u) => - fields = unit-names(u).fold( - lam(acc, id): - power = unit-power(id, u) - if power == 0: - acc - else: - CL.concat-cons(j-field(tostring(id), j-num(power)), acc) - end - end, - CL.concat-empty) - j-obj(fields) - end +fun compile-unit(u :: A.Unit) -> J.JExpr: + fields = unit-names(u).fold( + lam(acc, id): + power = unit-power(id, u) + if power == 0: + acc + else: + CL.concat-cons(j-field(tostring(id), j-num(power)), acc) + end + end, + CL.concat-empty) + j-obj(fields) end fun compile-ann(ann :: A.Ann, visitor) -> DAG.CaseResults%(is-c-exp): @@ -437,7 +433,7 @@ fun compile-ann(ann :: A.Ann, visitor) -> DAG.CaseResults%(is-c-exp): ) | a-unit(l, base, u) => compiled-base = compile-ann(base, visitor) - compiled-unit = compile-unit(some(u)) + compiled-unit = compile-unit(u) c-exp( rt-method("makeUnitAnn", [clist: compiled-base.exp, compiled-unit]), cl-empty) @@ -1715,11 +1711,11 @@ compiler-visitor = { method a-srcloc(self, l, loc): c-exp(self.get-loc(loc), cl-empty) end, - method a-num(self, l :: Loc, n :: Number, u-maybe :: Option) block: - if num-is-fixnum(n) and A.is-u-one(u-maybe.or-else(A.u-one(N.dummy-loc))): + method a-num(self, l :: Loc, n :: Number, u :: A.Unit) block: + if num-is-fixnum(n) and A.is-u-one(u): c-exp(j-parens(j-num(n)), cl-empty) else: - args = [clist: j-str(tostring(n)), compile-unit(u-maybe)] + args = [clist: j-str(tostring(n)), compile-unit(u)] c-exp(rt-method("makeNumberFromString", args), cl-empty) end end, @@ -1956,21 +1952,22 @@ remove-useless-if-visitor = N.default-map-visitor.{ check: d = N.dummy-loc + u = A.u-one(d) true1 = N.a-if(d, N.a-bool(d, true), - N.a-lettable(d, N.a-val(d, N.a-num(d, 1, none))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 2, none)))) - true1.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 1, none)) + N.a-lettable(d, N.a-val(d, N.a-num(d, 1, u))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 2, u)))) + true1.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 1, u)) false4 = N.a-if(d, N.a-bool(d, false), - N.a-lettable(d, N.a-val(d, N.a-num(d, 3, none))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 4, none)))) - false4.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 4, none)) + N.a-lettable(d, N.a-val(d, N.a-num(d, 3, u))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 4, u)))) + false4.visit(remove-useless-if-visitor) is N.a-val(d, N.a-num(d, 4, u)) N.a-if(d, N.a-id(d, A.s-name(d, "x")), N.a-lettable(d, true1), N.a-lettable(d, false4) ).visit(remove-useless-if-visitor) is N.a-if(d, N.a-id(d, A.s-name(d, "x")), - N.a-lettable(d, N.a-val(d, N.a-num(d, 1, none))), - N.a-lettable(d, N.a-val(d, N.a-num(d, 4, none)))) + N.a-lettable(d, N.a-val(d, N.a-num(d, 1, u))), + N.a-lettable(d, N.a-val(d, N.a-num(d, 4, u)))) end |# diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index 031d0fe067..36bf691a52 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -167,14 +167,14 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: end) end) - | s-num(l, n, u-maybe) => - k(N.a-val(l, N.a-num(l, n, u-maybe))) + | s-num(l, n, u) => + k(N.a-val(l, N.a-num(l, n, u))) # num, den are exact ints, and s-frac desugars to the exact rational num/den - | s-frac(l, num, den, u-maybe) => - k(N.a-val(l, N.a-num(l, num / den, u-maybe))) # Possibly unneeded if removed by desugar? + | s-frac(l, num, den, u) => + k(N.a-val(l, N.a-num(l, num / den, u))) # Possibly unneeded if removed by desugar? # num, den are exact ints, and s-rfrac desugars to the roughnum fraction corresponding to num/den - | s-rfrac(l, num, den, u-maybe) => - k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den), u-maybe))) # Possibly unneeded if removed by desugar? + | s-rfrac(l, num, den, u) => + k(N.a-val(l, N.a-num(l, num-to-roughnum(num / den), u))) # Possibly unneeded if removed by desugar? | s-str(l, s) => k(N.a-val(l, N.a-str(l, s))) | s-undefined(l) => k(N.a-val(l, N.a-undefined(l))) | s-bool(l, b) => k(N.a-val(l, N.a-bool(l, b))) @@ -346,7 +346,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: N.a-let( l, bind(l, array-id), - N.a-prim-app(l, "makeArrayN", [list: N.a-num(l, values.length(), none)], flat-prim-app), + N.a-prim-app(l, "makeArrayN", [list: N.a-num(l, values.length(), A.u-one(l))], flat-prim-app), anf-name-arr-rec(values, array-id, 0, lam(): k(N.a-val(l, N.a-id(l, array-id))) end)) diff --git a/src/arr/compiler/ast-anf.arr b/src/arr/compiler/ast-anf.arr index 9e58472322..aa67145003 100644 --- a/src/arr/compiler/ast-anf.arr +++ b/src/arr/compiler/ast-anf.arr @@ -464,7 +464,7 @@ data AVal: | a-srcloc(l :: Loc, loc :: Loc) with: method label(self): "a-srcloc" end, method tosource(self): PP.str(torepr(self.loc)) end - | a-num(l :: Loc, n :: Number, u :: Option) with: + | a-num(l :: Loc, n :: Number, u :: A.Unit) with: method label(self): "a-num" end, method tosource(self): cases(Option) self.u: @@ -713,7 +713,7 @@ default-map-visitor = { method a-srcloc(self, l, loc): a-srcloc(l, loc) end, - method a-num(self, l :: Loc, n :: Number, u :: Option): + method a-num(self, l :: Loc, n :: Number, u :: A.Unit): a-num(l, n, u) end, method a-str(self, l :: Loc, s :: String): @@ -820,7 +820,7 @@ where: x = n("x") y = n("y") freevars-e( - a-let(d, a-bind(d, x, A.a-blank), a-val(d, a-num(d, 4, none)), + a-let(d, a-bind(d, x, A.a-blank), a-val(d, a-num(d, 4, A.u-one(d))), a-lettable(d, a-val(d, a-id(d, y))))).keys-list() is [list: y.key()] end diff --git a/src/arr/compiler/desugar.arr b/src/arr/compiler/desugar.arr index 217aec4413..13e9fc5b71 100644 --- a/src/arr/compiler/desugar.arr +++ b/src/arr/compiler/desugar.arr @@ -965,8 +965,8 @@ where: p = lam(str): PP.surface-parse(str, "test").block.visit(A.dummy-loc-visitor) end ds = lam(prog): desugar-expr(prog).visit(unglobal).visit(A.dummy-loc-visitor) end id = lam(s): A.s-id(d, A.s-name(d, s)) end - one = A.s-num(d, 1, none) - two = A.s-num(d, 2, none) + one = A.s-num(d, 1, A.u-one(d)) + two = A.s-num(d, 2, A.u-one(d)) pretty = lam(prog): prog.tosource().pretty(80).join-str("\n") end if-else = "if true: 5 else: 6 end" diff --git a/src/arr/compiler/resolve-scope.arr b/src/arr/compiler/resolve-scope.arr index 13d1b2d760..a19341e711 100644 --- a/src/arr/compiler/resolve-scope.arr +++ b/src/arr/compiler/resolve-scope.arr @@ -470,29 +470,30 @@ where: thunk = lam(e): A.s-lam(d, "", [list: ], [list: ], A.a-blank, "", bk(e), n, n, false) end + u = A.u-one(d) compare1 = - A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 15, none)), - A.s-let-bind(d, b("y"), A.s-num(d, 10, none))], + A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 15, u)), + A.s-let-bind(d, b("y"), A.s-num(d, 10, u))], id("y"), false) dsb(p("x = 15 y = 10 y").stmts).visit(A.dummy-loc-visitor) is compare1 dsb(p("x = 55 var y = 10 y").stmts).visit(A.dummy-loc-visitor) - is A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 55, none)), - A.s-var-bind(d, b("y"), A.s-num(d, 10, none))], id("y"), false) + is A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 55, u)), + A.s-var-bind(d, b("y"), A.s-num(d, 10, u))], id("y"), false) bs("x = 7 print(2) var y = 10 y") is - A.s-let-expr(d, [list:A.s-let-bind(d, b("x"), A.s-num(d, 7, none))], + A.s-let-expr(d, [list:A.s-let-bind(d, b("x"), A.s-num(d, 7, u))], A.s-block(d, [list: - A.s-app(d, id("print"), [list:A.s-num(d, 2, none)]), - A.s-let-expr(d, [list:A.s-var-bind(d, b("y"), A.s-num(d, 10, none))], + A.s-app(d, id("print"), [list:A.s-num(d, 2, u)]), + A.s-let-expr(d, [list:A.s-var-bind(d, b("y"), A.s-num(d, 10, u))], id("y"), false) ]), false) prog = bs("fun f(): 4 end fun g(): 5 end f()") prog is A.s-letrec(d, [list: - A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, none))), - A.s-letrec-bind(d, b("g"), thunk(A.s-num(d, 5, none))) + A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, u))), + A.s-letrec-bind(d, b("g"), thunk(A.s-num(d, 5, u))) ], A.s-app(d, id("f"), [list: ]), false) @@ -501,13 +502,13 @@ where: prog2 = bs("print(1) fun f(): 4 end fun g(): 5 end fun h(): 6 end x = 3 print(x)") prog2 is A.s-block(d, - [list: p-s(A.s-num(d, 1, none)), + [list: p-s(A.s-num(d, 1, u)), A.s-letrec(d, [list: - A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, none))), - A.s-letrec-bind(d, b("g"), thunk(A.s-num(d, 5, none))), - A.s-letrec-bind(d, b("h"), thunk(A.s-num(d, 6, none))) + A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, u))), + A.s-letrec-bind(d, b("g"), thunk(A.s-num(d, 5, u))), + A.s-letrec-bind(d, b("h"), thunk(A.s-num(d, 6, u))) ], - A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 3, none))], p-s(id("x")), false), + A.s-let-expr(d, [list: A.s-let-bind(d, b("x"), A.s-num(d, 3, u))], p-s(id("x")), false), false)]) dsb([list: prog2]) is prog2 @@ -519,17 +520,17 @@ where: prog3 is A.s-block(d, [list: p-s(id("x")), - A.s-assign(d, A.s-name(d, "x"), A.s-num(d, 3, none)), + A.s-assign(d, A.s-name(d, "x"), A.s-num(d, 3, u)), p-s(id("x")) ]) prog4 = bs("var x = 10 fun f(): 4 end f()") prog4 is A.s-let-expr(d, [list: - A.s-var-bind(d, b("x"), A.s-num(d, 10, none)) + A.s-var-bind(d, b("x"), A.s-num(d, 10, u)) ], A.s-letrec(d, [list: - A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, none))) + A.s-letrec-bind(d, b("f"), thunk(A.s-num(d, 4, u))) ], A.s-app(d, id("f"), [list: ]), false), false @@ -707,7 +708,7 @@ where: ds = lam(prog): desugar-scope(prog, C.no-builtins).ast.visit(A.dummy-loc-visitor) end compare1 = A.s-program(d, A.s-provide-none(d), A.s-provide-types-none(d), [list: ], A.s-let-expr(d, [list: - A.s-let-bind(d, b("x"), A.s-num(d, 10, none)) + A.s-let-bind(d, b("x"), A.s-num(d, 10, A.u-one(d))) ], A.s-module(d, id("nothing"), empty, empty, id("x"), [list:], checks), false) ) diff --git a/src/arr/compiler/type-check.arr b/src/arr/compiler/type-check.arr index a47e7b2161..d091f10e37 100644 --- a/src/arr/compiler/type-check.arr +++ b/src/arr/compiler/type-check.arr @@ -828,7 +828,7 @@ fun _synthesis(e :: Expr, top-level :: Boolean, context :: Context) -> TypingRes raise("synthesis for s-undefined not implemented") | s-srcloc(l, loc) => typing-result(e, t-srcloc(l), context) - | s-num(l, n, _) => + | s-num(l, n, u) => typing-result(e, t-number(l), context) | s-frac(l, num, den, u) => typing-result(e, t-number(l), context) diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index c162597674..227fc545cb 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -314,18 +314,15 @@ fun wf-unit-helper(u :: A.Unit, parent-maybe :: Option) block: wf-unit-helper(rhs, some(u)) | u-paren(_, paren-u) => wf-unit-helper(paren-u, some(u)) | u-pow(l, _, pow-u, n) => - if (n == 0) or not(num-is-integer(n)): + when (n == 0) or not(num-is-integer(n)): add-error(C.invalid-unit-power(l, n)) - else: - nothing end wf-unit-helper(pow-u, some(u)) | u-base(l, id) => - if A.is-s-underscore(id) and is-some(parent-maybe): + when A.is-s-underscore(id) and is-some(parent-maybe): add-error(C.underscore-as-unit(l)) - else: - nothing end + | u-one(l) => nothing end end fun wf-unit(u :: A.Unit, allow-underscore :: Boolean) block: @@ -920,28 +917,20 @@ well-formed-visitor = A.default-iter-visitor.{ end iterator.visit(self) and lists.all(_.visit(self), bindings) and ann.visit(self) and body.visit(self) end, - method s-frac(self, l, num, den, unit-maybe) block: + method s-frac(self, l, num, den, u) block: when den == 0: add-error(C.zero-fraction(l, num)) end - cases(Option) unit-maybe: - | none => nothing - | some(u) => wf-unit(u, false) - end - + wf-unit(u, false) true end, - method s-rfrac(self, l, num, den, unit-maybe) block: + method s-rfrac(self, l, num, den, u) block: when den == 0: add-error(C.zero-fraction(l, num)) end - cases(Option) unit-maybe: - | none => nothing - | some(u) => wf-unit(u, false) - end - + wf-unit(u, false) true end, method s-id(self, l, id) block: @@ -1069,11 +1058,8 @@ well-formed-visitor = A.default-iter-visitor.{ wf-unit(u, true) true end, - method s-num(self, l, n, unit-maybe) block: - cases(Option) unit-maybe: - | none => nothing - | some(u) => wf-unit(u, false) - end + method s-num(self, l, n, u) block: + wf-unit(u, false) true end } @@ -1393,8 +1379,8 @@ top-level-visitor = A.default-iter-visitor.{ method s-table-extend(_, l :: Loc, column-binds :: A.ColumnBinds, extensions :: List): well-formed-visitor.s-table-extend(l, column-binds, extensions) end, - method s-num(self, l, n, unit-maybe): - well-formed-visitor.s-num(l, n, unit-maybe) + method s-num(self, l, n, u): + well-formed-visitor.s-num(l, n, u) end, method a-arrow(_, l, args, ret, use-parens): well-formed-visitor.a-arrow(l, args, ret, use-parens) diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index dddf5aec0e..c11eae975f 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -140,7 +140,7 @@ data Name: method tosourcestring(self): "$type$" + self.s end, method toname(self): self.s end, method key(self): "tglobal#" + self.s end - + | s-atom(base :: String, serial :: Number) with: method to-compiled-source(self): PP.str(self.to-compiled()) end, method to-compiled(self): self.base + tostring(self.serial) end, @@ -931,33 +931,36 @@ data Expr: | s-srcloc(l :: Loc, loc :: Loc) with: method label(self): "s-srcloc" end, method tosource(self): PP.str(torepr(self.loc)) end - | s-num(l :: Loc, n :: Number, u :: Option) with: + | s-num(l :: Loc, n :: Number, u :: Unit) with: method label(self): "s-num" end, method tosource(self): - cases(Option) self.u: - | none => PP.number(self.n) - | some(u) => PP.separate(str-percent, - [list: PP.number(self.n), PP.surround(INDENT, 0, PP.langle, u.tosource(), PP.rangle)]) + if is-u-one(self.u): + PP.number(self.n) + else: + PP.separate(str-percent, + [list: PP.number(self.n), PP.surround(INDENT, 0, PP.langle, self.u.tosource(), PP.rangle)]) end end - | s-frac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option) with: + | s-frac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit) with: method label(self): "s-frac" end, method tosource(self) block: base-str = PP.number(self.num) + PP.str("/") + PP.number(self.den) - cases(Option) self.u: - | none => base-str - | some(u) => PP.separate(str-percent, - [list: base-str, PP.surround(INDENT, 0, PP.langle, u.tosource(), PP.rangle)]) + if is-u-one(self.u): + base-str + else: + PP.separate(str-percent, + [list: base-str, PP.surround(INDENT, 0, PP.langle, self.u.tosource(), PP.rangle)]) end end - | s-rfrac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option) with: + | s-rfrac(l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit) with: method label(self): "s-rfrac" end, method tosource(self): base-str = PP.str("~") + PP.number(self.num) + PP.str("/") + PP.number(self.den) - cases(Option) self.u: - | none => base-str - | some(u) => PP.separate(str-percent, - [list: base-str, PP.surround(INDENT, 0, PP.langle, u.tosource(), PP.rangle)]) + if is-u-one(self.u): + base-str + else: + PP.separate(str-percent, + [list: base-str, PP.surround(INDENT, 0, PP.langle, self.u.tosource(), PP.rangle)]) end end | s-bool(l :: Loc, b :: Boolean) with: @@ -1673,6 +1676,10 @@ data Unit: | u-paren(l :: Loc, u :: Unit) with: method label(self): "u-paren" end, method tosource(self): PP.paren(self.u.tosource()) end +sharing: + method visit(self, visitor): + self._match(visitor, lam(val): raise("No visitor field for " + self.label()) end) + end end @@ -2187,13 +2194,13 @@ default-map-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(l, loc) end, - method s-num(self, l :: Loc, n :: Number, u :: Option): + method s-num(self, l :: Loc, n :: Number, u :: Unit): s-num(l, n, u) end, - method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): + method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): s-frac(l, num, den, u) end, - method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): + method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): s-rfrac(l, num, den, u) end, method s-bool(self, l :: Loc, b :: Boolean): @@ -2751,13 +2758,13 @@ default-iter-visitor = { method s-srcloc(self, l, shadow loc): true end, - method s-num(self, l :: Loc, n :: Number, u :: Option): + method s-num(self, l :: Loc, n :: Number, u :: Unit): true end, - method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): + method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): true end, - method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): + method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): true end, method s-bool(self, l :: Loc, b :: Boolean): @@ -3291,14 +3298,14 @@ dummy-loc-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(dummy-loc, loc) end, - method s-num(self, l :: Loc, n :: Number, u :: Option): - s-num(dummy-loc, n, u) + method s-num(self, l :: Loc, n :: Number, u :: Unit): + s-num(dummy-loc, n, u.visit(self)) end, - method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): - s-frac(dummy-loc, num, den, u) + method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): + s-frac(dummy-loc, num, den, u.visit(self)) end, - method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Option): - s-rfrac(dummy-loc, num, den, u) + method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): + s-rfrac(dummy-loc, num, den, u.visit(self)) end, method s-bool(self, l :: Loc, b :: Boolean): s-bool(dummy-loc, b) @@ -3511,5 +3518,19 @@ dummy-loc-visitor = { end, method a-field(self, l, name, ann): a-field(dummy-loc, name, ann.visit(self)) + end, + method u-one(self, l): u-one(dummy-loc) end, + method u-base(self, l, id): u-base(dummy-loc, id) end, + method u-mul(self, l :: Loc, op-l :: Loc, lhs :: Unit, rhs :: Unit): + u-mul(dummy-loc, dummy-loc, lhs.visit(self), rhs.visit(self)) + end, + method u-div(self, l :: Loc, op-l :: Loc, lhs :: Unit, rhs :: Unit): + u-div(dummy-loc, dummy-loc, lhs.visit(self), rhs.visit(self)) + end, + method u-pow(self, l :: Loc, op-l :: Loc, u :: Unit, n :: Number): + u-pow(dummy-loc, dummy-loc, u.visit(self), n) + end, + method u-paren(self, l :: Loc, u :: Unit): + u-paren(dummy-loc, u.visit(self)) end } diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 7c91c9667c..4d7b958f57 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -1173,30 +1173,32 @@ 'num-expr': function(node) { if (node.kids.length == 1) { // (num-expr n) + var u = RUNTIME.getField(ast, 'u-one').app(pos(node.pos)) return RUNTIME.getField(ast, 's-num') - .app(pos(node.pos), number(node.kids[0]), RUNTIME.ffi.makeNone()); + .app(pos(node.pos), number(node.kids[0]), u); } else { // (num-expr n PERCENT dim-expr) return RUNTIME.getField(ast, 's-num') - .app(pos(node.pos), number(node.kids[0]), RUNTIME.ffi.makeSome(tr(node.kids[2]))); + .app(pos(node.pos), number(node.kids[0]), tr(node.kids[2])); } }, 'frac-expr': function(node) { var numden = node.kids[0].value.split("/"); if (node.kids.length == 1) { // (frac-expr n) + var u = RUNTIME.getField(ast, 'u-one').app(pos(node.pos)) return RUNTIME.getField(ast, 's-frac') .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1], {}), - RUNTIME.ffi.makeNone()); + u); } else { // (frac-expr n PERCENT dim-expr) return RUNTIME.getField(ast, 's-frac') .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1], {}), - RUNTIME.ffi.makeSome(tr(node.kids[2]))); + tr(node.kids[2])); } }, 'rfrac-expr': function(node) { @@ -1204,18 +1206,19 @@ var numden = node.kids[0].value.substring(1).split("/"); if (node.kids.length == 1) { // (rfrac-expr n) + var u = RUNTIME.getField(ast, 'u-one').app(pos(node.pos)) return RUNTIME.getField(ast, 's-rfrac') .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1], {}), - RUNTIME.ffi.makeNone()); + u); } else { // (rfrac-expr n PERCENT dim-expr) return RUNTIME.getField(ast, 's-rfrac') .app(pos(node.pos), RUNTIME.makeNumberFromString(numden[0], {}), RUNTIME.makeNumberFromString(numden[1], {}), - RUNTIME.ffi.makeSome(tr(node.kids[2]))); + tr(node.kids[2])); } }, 'string-expr': function(node) { diff --git a/t.arr b/t.arr index adeea41f4a..db11f9c68c 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,3 @@ -fun id(n :: {Number%; Number%<_>}): n end -# TODO(benmusch): Why does this return such a weird contract error? -id({1%; 2%}) +type MyAny = Any +fun id(n :: MyAny): n end +id(1%) diff --git a/tests/pyret/tests/test-contracts.arr b/tests/pyret/tests/test-contracts.arr index 180110842b..20f9d6287f 100644 --- a/tests/pyret/tests/test-contracts.arr +++ b/tests/pyret/tests/test-contracts.arr @@ -543,4 +543,12 @@ check "Should notice unit mis-matches": fun id(n :: { a :: Number%, b :: Number%}): n end id({ a: 1%, b: 1%}) ```) is%(output) success + #| TODO: re-comment when working on unit annotations + run-str( + ``` + type MyAny = Any + fun id(n :: MyAny): n end + id(1%) + ```) is%(output) success + |# end From ff2434cdb263b6d63a6ac6934f695256ea2f0da4 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 6 Jun 2019 15:27:03 -0400 Subject: [PATCH 38/84] Refactor compile-unit to be faster --- src/arr/compiler/anf-loop-compiler.arr | 41 +++++++++++--------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index 94b8f066e0..edb0bea0c3 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -330,38 +330,31 @@ fun is-function-flat(flatness-env :: FL.FEnv, fun-name :: String) -> Boolean: is-flat-enough(flatness-opt) end -# return all of the names in a unit -fun unit-names(u :: A.Unit) -> Set: - cases (A.Unit) u: - | u-one(_) => [list-set: ] - | u-base(_, id) => [list-set: id] - | u-mul(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) - | u-div(_, _, lhs, rhs) => unit-names(lhs).union(unit-names(rhs)) - | u-pow(_, _, shadow u, n) => unit-names(u) - | u-paren(_, shadow u) => unit-names(u) - end -end - -fun unit-power(target-id :: A.Name, u :: A.Unit) -> NumInteger: - cases (A.Unit) u: - | u-one(_) => 0 +fun normalize-unit-help(u :: A.Unit, factor :: NumInteger, acc :: D.MutableStringDict) -> D.MutableStringDict: + cases (A.Unit) u block: + | u-one(_) => acc | u-base(_, id) => - if id == target-id: 1 else: 0 end - | u-mul(_, _, lhs, rhs) => unit-power(target-id, lhs) + unit-power(target-id, rhs) - | u-div(_, _, lhs, rhs) => unit-power(target-id, lhs) + (-1 * unit-power(target-id, rhs)) - | u-pow(_, _, shadow u, n) => unit-power(target-id, u) * n - | u-paren(_, shadow u) => unit-power(target-id, u) + acc.set-now(tostring(id), acc.get-now(tostring(id)).or-else(0) + factor) + acc + | u-mul(_, _, lhs, rhs) => normalize-unit-help(lhs, factor, normalize-unit-help(rhs, factor, acc)) + | u-div(_, _, lhs, rhs) => normalize-unit-help(lhs, factor, normalize-unit-help(rhs, factor * -1, acc)) + | u-pow(_, _, shadow u, n) => normalize-unit-help(u, n * factor, acc) + | u-paren(_, shadow u) => normalize-unit-help(u, factor, acc) end end +fun normalize-unit(u :: A.Unit) -> D.StringDict: + normalize-unit-help(u, 1, [mutable-string-dict: ]).freeze() +end fun compile-unit(u :: A.Unit) -> J.JExpr: - fields = unit-names(u).fold( - lam(acc, id): - power = unit-power(id, u) + normalized = normalize-unit(u) + fields = normalized.keys().fold( + lam(acc, key): + power = normalized.get-value(key) if power == 0: acc else: - CL.concat-cons(j-field(tostring(id), j-num(power)), acc) + CL.concat-cons(j-field(key, j-num(power)), acc) end end, CL.concat-empty) From 05384be3083ff6d65d77d4b59708eeead989a1b3 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 6 Jun 2019 17:25:31 -0400 Subject: [PATCH 39/84] Refactor units to be visited --- src/arr/compiler/anf.arr | 4 +- src/arr/compiler/ast-anf.arr | 8 +- src/arr/compiler/well-formed.arr | 122 +++++++++++++++---------------- src/arr/trove/ast.arr | 55 +++++++++++--- t.arr | 4 +- 5 files changed, 114 insertions(+), 79 deletions(-) diff --git a/src/arr/compiler/anf.arr b/src/arr/compiler/anf.arr index 36bf691a52..be7cf8374a 100644 --- a/src/arr/compiler/anf.arr +++ b/src/arr/compiler/anf.arr @@ -23,7 +23,7 @@ fun mk-id(loc, base): { id: t, id-b: bind(loc, t), id-e: N.a-id(loc, t) } end -fun anf-term(e :: A.Expr) -> N.AExpr block: +fun anf-term(e :: A.Expr) -> N.AExpr: anf(e, lam(x): N.a-lettable(x.l, x) end) end @@ -143,7 +143,7 @@ fun anf-block(es-init :: List, k :: ANFCont): anf-block-help(es-init) end -fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr block: +fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: cases(A.Expr) e: | s-module(l, answer, dvs, dts, provides, types, checks) => adts = for map(dt from dts): diff --git a/src/arr/compiler/ast-anf.arr b/src/arr/compiler/ast-anf.arr index aa67145003..05de399ccb 100644 --- a/src/arr/compiler/ast-anf.arr +++ b/src/arr/compiler/ast-anf.arr @@ -467,9 +467,11 @@ data AVal: | a-num(l :: Loc, n :: Number, u :: A.Unit) with: method label(self): "a-num" end, method tosource(self): - cases(Option) self.u: - | none => PP.number(self.n) - | some(u) => PP.separate(str-percent, [list: PP.number(self.n), u.tosource()]) + if A.is-u-one(self.u): + PP.number(self.n) + else: + PP.separate(str-percent, + [list: PP.number(self.n), PP.surround(INDENT, 0, PP.langle, self.u.tosource(), PP.rangle)]) end end | a-str(l :: Loc, s :: String) with: diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index 227fc545cb..56423473e7 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -279,77 +279,53 @@ fun ensure-unique-variant-ids(variants :: List, name :: String, data- end end -fun unit-is-op(u :: A.Unit): - # NOTE(benmusch): Should we include powers here? - A.is-u-mul(u) or A.is-u-div(u) or A.is-u-pow(u) + +fun wf-last-stmt(block-loc, stmt :: A.Expr): + cases(A.Expr) stmt: + | s-let(l, _, _, _) => add-error(C.block-ending(l, block-loc, "let-binding")) + | s-var(l, _, _) => add-error(C.block-ending(l, block-loc, "var-binding")) + | s-rec(l, _, _) => add-error(C.block-ending(l, block-loc, "rec-binding")) + | s-fun(l, _, _, _, _, _, _, _, _, _) => add-error(C.block-ending(l, block-loc, "fun-binding")) + | s-data(l, _, _, _, _, _, _, _) => add-error(C.block-ending(l, block-loc, "data definition")) + | s-contract(l, _, _, _) => add-error(C.block-ending(l, block-loc, "contract")) + | else => nothing + end end -fun unit-opname(u :: A.Unit%(unit-is-op)): + + +fun unit-opname(u :: A.Unit): cases(A.Unit) u: | u-mul(_, _, _, _) => "*" | u-div(_, _, _, _) => "/" | u-pow(_, _, _, _) => "^" end end - - -# TODO(benmuch): Consider refactoring to make this a proper visitor -# and shadow the reachable-ops pattern -fun wf-unit-helper(u :: A.Unit, parent-maybe :: Option) block: - cases(Option) parent-maybe block: - | some(p) => - if unit-is-op(u) and unit-is-op(p) and not(u.label() == p.label()): - add-error(C.mixed-binops(p.l, unit-opname(u), u.op-l, unit-opname(p), p.op-l)) - else: - nothing - end - | none => nothing - end - +fun reachable-ops-unit(self, l, op-l, parent, u): cases(A.Unit) u block: - | u-mul(_, _, lhs, rhs) => - wf-unit-helper(lhs, some(u)) - wf-unit-helper(rhs, some(u)) - | u-div(_, _, lhs, rhs) => - wf-unit-helper(lhs, some(u)) - wf-unit-helper(rhs, some(u)) - | u-paren(_, paren-u) => wf-unit-helper(paren-u, some(u)) - | u-pow(l, _, pow-u, n) => - when (n == 0) or not(num-is-integer(n)): - add-error(C.invalid-unit-power(l, n)) + | u-mul(l2, op-l2, lhs, rhs) => + if A.is-u-mul(parent) block: + reachable-ops-unit(self, l2, op-l2, u, lhs) + reachable-ops-unit(self, l2, op-l2, u, lhs) + else: + add-error(C.mixed-binops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) end - wf-unit-helper(pow-u, some(u)) - | u-base(l, id) => - when A.is-s-underscore(id) and is-some(parent-maybe): - add-error(C.underscore-as-unit(l)) + | u-div(l2, op-l2, lhs, rhs) => + if A.is-u-div(parent) block: + reachable-ops-unit(self, l2, op-l2, u, lhs) + reachable-ops-unit(self, l2, op-l2, u, lhs) + else: + add-error(C.mixed-binops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) end - | u-one(l) => nothing - end -end -fun wf-unit(u :: A.Unit, allow-underscore :: Boolean) block: - wf-unit-helper(u, none) - - cases(A.Unit) u: - | u-base(l, id) => - if A.is-s-underscore(id) and not(allow-underscore): - add-error(C.underscore-as-unit(l)) + | u-pow(l2, op-l2, pow-u, n) => + if A.is-u-pow(self): + reachable-ops-unit(self, l2, op-l2, u, pow-u) else: - nothing + add-error(C.mixed-binops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) end - | else => nothing + | else => u.visit(self) end end -fun wf-last-stmt(block-loc, stmt :: A.Expr): - cases(A.Expr) stmt: - | s-let(l, _, _, _) => add-error(C.block-ending(l, block-loc, "let-binding")) - | s-var(l, _, _) => add-error(C.block-ending(l, block-loc, "var-binding")) - | s-rec(l, _, _) => add-error(C.block-ending(l, block-loc, "rec-binding")) - | s-fun(l, _, _, _, _, _, _, _, _, _) => add-error(C.block-ending(l, block-loc, "fun-binding")) - | s-data(l, _, _, _, _, _, _, _) => add-error(C.block-ending(l, block-loc, "data definition")) - | s-contract(l, _, _, _) => add-error(C.block-ending(l, block-loc, "contract")) - | else => nothing - end -end fun fields-to-binds(members :: List) -> List: for map(mem from members): @@ -366,7 +342,6 @@ fun reachable-ops(self, l, op-l, op, ast): reachable-ops(self, l, op-l, op, right2) else: add-error(C.mixed-binops(l, opname(op), op-l, opname(op2), op-l2)) - nothing end true | else => ast.visit(self) @@ -922,7 +897,7 @@ well-formed-visitor = A.default-iter-visitor.{ add-error(C.zero-fraction(l, num)) end - wf-unit(u, false) + u.visit(self) true end, method s-rfrac(self, l, num, den, u) block: @@ -930,7 +905,7 @@ well-formed-visitor = A.default-iter-visitor.{ add-error(C.zero-fraction(l, num)) end - wf-unit(u, false) + u.visit(self) true end, method s-id(self, l, id) block: @@ -1055,11 +1030,34 @@ well-formed-visitor = A.default-iter-visitor.{ true end, method a-unit(self, l, base, u) block: - wf-unit(u, true) + cases(A.Unit) u: + | u-base(_, id) => when not(A.is-s-underscore(id)): u.visit(self) end + | else => u.visit(self) + end + base.visit(self) + true + end, + method u-mul(self, l :: Loc, op-l :: Loc, lhs :: A.Unit, rhs :: A.Unit) block: + reachable-ops-unit(self, l, op-l, A.u-mul(l, op-l, lhs, rhs), lhs) + reachable-ops-unit(self, l, op-l, A.u-mul(l, op-l, lhs, rhs), rhs) + true + end, + method u-div(self, l :: Loc, op-l :: Loc, lhs :: A.Unit, rhs :: A.Unit) block: + reachable-ops-unit(self, l, op-l, A.u-div(l, op-l, lhs, rhs), lhs) + reachable-ops-unit(self, l, op-l, A.u-div(l, op-l, lhs, rhs), rhs) + true + end, + method u-pow(self, l :: Loc, op-l :: Loc, u :: A.Unit, n :: Number) block: + when (n == 0) or not(num-is-integer(n)): + add-error(C.invalid-unit-power(l, n)) + end + reachable-ops-unit(self, l, op-l, A.u-pow(l, op-l, u, n), u) true end, - method s-num(self, l, n, u) block: - wf-unit(u, false) + method u-base(self, l :: Loc, id :: A.Name) block: + when A.is-s-underscore(id): + add-error(C.underscore-as-unit(l)) + end true end } diff --git a/src/arr/trove/ast.arr b/src/arr/trove/ast.arr index c11eae975f..9f7c7b6fa7 100644 --- a/src/arr/trove/ast.arr +++ b/src/arr/trove/ast.arr @@ -2195,13 +2195,13 @@ default-map-visitor = { s-srcloc(l, loc) end, method s-num(self, l :: Loc, n :: Number, u :: Unit): - s-num(l, n, u) + s-num(l, n, u.visit(self)) end, method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): - s-frac(l, num, den, u) + s-frac(l, num, den, u.visit(self)) end, method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): - s-rfrac(l, num, den, u) + s-rfrac(l, num, den, u.visit(self)) end, method s-bool(self, l :: Loc, b :: Boolean): s-bool(l, b) @@ -2407,14 +2407,31 @@ default-map-visitor = { a-pred(l, ann.visit(self), exp.visit(self)) end, method a-unit(self, l, ann, u): - # TODO(benmusch): Add units to the visitor? - a-unit(l, ann.visit(self), u) + a-unit(l, ann.visit(self), u.visit(self)) end, method a-dot(self, l, obj, field): a-dot(l, obj.visit(self), field) end, method a-field(self, l, name, ann): a-field(l, name, ann.visit(self)) + end, + method u-one(self, l): + u-one(l) + end, + method u-base(self, l, id): + u-base(l, id) + end, + method u-mul(self, l :: Loc, op-l :: Loc, lhs :: Unit, rhs :: Unit): + u-mul(l, op-l, lhs.visit(self), rhs.visit(self)) + end, + method u-div(self, l :: Loc, op-l :: Loc, lhs :: Unit, rhs :: Unit): + u-div(l, op-l, lhs.visit(self), rhs.visit(self)) + end, + method u-pow(self, l :: Loc, op-l :: Loc, u :: Unit, n :: Number): + u-pow(l, op-l, u.visit(self), n) + end, + method u-paren(self, l :: Loc, u :: Unit): + u-paren(l, u.visit(self)) end } @@ -2759,13 +2776,13 @@ default-iter-visitor = { true end, method s-num(self, l :: Loc, n :: Number, u :: Unit): - true + u.visit(self) end, method s-frac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): - true + u.visit(self) end, method s-rfrac(self, l :: Loc, num :: NumInteger, den :: NumInteger, u :: Unit): - true + u.visit(self) end, method s-bool(self, l :: Loc, b :: Boolean): true @@ -2961,13 +2978,31 @@ default-iter-visitor = { ann.visit(self) and exp.visit(self) end, method a-unit(self, l, ann, u): - ann.visit(self) + ann.visit(self) and u.visit(self) end, method a-dot(self, l, obj, field): obj.visit(self) end, method a-field(self, l, name, ann): ann.visit(self) + end, + method u-one(self, l): + true + end, + method u-base(self, l, id): + true + end, + method u-mul(self, l :: Loc, op-l :: Loc, lhs :: Unit, rhs :: Unit): + lhs.visit(self) and rhs.visit(self) + end, + method u-div(self, l :: Loc, op-l :: Loc, lhs :: Unit, rhs :: Unit): + lhs.visit(self) and rhs.visit(self) + end, + method u-pow(self, l :: Loc, op-l :: Loc, u :: Unit, n :: Number): + u.visit(self) + end, + method u-paren(self, l :: Loc, u :: Unit): + u.visit(self) end } @@ -3511,7 +3546,7 @@ dummy-loc-visitor = { a-pred(dummy-loc, ann.visit(self), exp.visit(self)) end, method a-unit(self, l, ann, u): - a-unit(dummy-loc, ann.visit(self), u) + a-unit(dummy-loc, ann.visit(self), u.visit(self)) end, method a-dot(self, l, obj, field): a-dot(dummy-loc, obj, field) diff --git a/t.arr b/t.arr index db11f9c68c..8714a4c648 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1,3 @@ -type MyAny = Any +type MyAny = Number%<_> fun id(n :: MyAny): n end -id(1%) +id(1) From 7adc2f59407465e36b0ff07c1aa069f111810388 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 6 Jun 2019 17:44:57 -0400 Subject: [PATCH 40/84] Use fold-keys --- src/arr/compiler/anf-loop-compiler.arr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index edb0bea0c3..e167e4b040 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -348,8 +348,8 @@ end fun compile-unit(u :: A.Unit) -> J.JExpr: normalized = normalize-unit(u) - fields = normalized.keys().fold( - lam(acc, key): + fields = normalized.fold-keys( + lam(key, acc): power = normalized.get-value(key) if power == 0: acc From 52445101062affc6fe02ff16287d7e250d389157 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 6 Jun 2019 17:47:08 -0400 Subject: [PATCH 41/84] Sort units when making strings --- src/js/base/js-numbers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 1babbd5bcd..fd3c69a9ad 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1092,7 +1092,7 @@ define("pyret-base/js/js-numbers", function() { if (unitStrs.length === 0) { return "1"; } else { - return unitStrs.join(" * "); + return unitStrs.sort().join(" * "); } }; From b641650a1ec17a7e5fbaf18daf343348941d9b4b Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 6 Jun 2019 18:14:47 -0400 Subject: [PATCH 42/84] Refactor the grammar to resemble binops --- src/js/base/pyret-grammar.bnf | 8 +++----- src/js/trove/parse-pyret.js | 26 ++++++++++++++++---------- t.arr | 4 +--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index 418cb99311..cf91d98ea1 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -132,12 +132,10 @@ prim-expr: num-expr | frac-expr | rfrac-expr | bool-expr | string-expr dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) # TODO(benmusch): Consider adding | (LANGLE|LT) NUMBER (RANGLE|GT) -unit-lhs: (PARENNOSPACE|PARENSPACE) unit-expr RPAREN +unit-atom: (PARENNOSPACE|PARENSPACE) unit-expr RPAREN | NAME - | unit-lhs CARET NUMBER -unit-expr: unit-lhs - | unit-lhs STAR unit-expr - | unit-lhs SLASH unit-expr + | unit-atom CARET NUMBER +unit-expr: unit-atom ((STAR|SLASH) unit-atom)* num-expr: NUMBER [PERCENT dim-expr] diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 4d7b958f57..228745666a 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -973,7 +973,7 @@ // TODO: Handle numbers here somehow return tr(node.kids[1]) }, - 'unit-lhs': function(node) { + 'unit-atom': function(node) { if (node.kids.length === 1) { return RUNTIME.getField(ast, 'u-base') .app(pos(node.pos), name(node.kids[0])) @@ -988,16 +988,22 @@ }, 'unit-expr': function(node) { if (node.kids.length === 1) { - // (unit-expr unit-lhs) + // (unit-expr unit-atom) return tr(node.kids[0]) - } else if (node.kids[1].name === 'STAR') { - // (unit-expr unit-lhs TIMES unit-expr) - return RUNTIME.getField(ast, 'u-mul') - .app(pos(node.pos), pos(node.kids[1].pos), tr(node.kids[0]), tr(node.kids[2])) - } else if (node.kids[1].name === 'SLASH') { - // (unit-expr unit-lhs DIVIDE unit-expr) - return RUNTIME.getField(ast, 'u-div') - .app(pos(node.pos), pos(node.kids[1].pos), tr(node.kids[0]), tr(node.kids[2])) + } else { + function mkUnit(op, lhs, rhs) { + var unitType = op.name === 'STAR' ? 'u-mul' : 'u-div'; + return RUNTIME.getField(ast, unitType) + .app(pos2(node.kids[0].pos, rhs.pos), + pos(op.pos), + lhs, + tr(rhs)); + } + var unit = mkUnit(node.kids[1], tr(node.kids[0]), node.kids[2]); + for (var i = 4; i < node.kids.length; i += 2) { + unit = mkUnit(node.kids[i - 1], unit, node.kids[i]); + } + return unit; } }, 'tuple-expr': function(node) { diff --git a/t.arr b/t.arr index 8714a4c648..a5f4834b63 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1 @@ -type MyAny = Number%<_> -fun id(n :: MyAny): n end -id(1) +1% From 6f14aaa902f11d9312b1527cdd0b780e39d0ea3f Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 6 Jun 2019 19:09:44 -0400 Subject: [PATCH 43/84] Refactor unitnum handling in makeInteger*op() and throw errors on fractional operations --- src/js/base/js-numbers.js | 43 ++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index fd3c69a9ad..fac71d79ae 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1190,12 +1190,12 @@ define("pyret-base/js/js-numbers", function() { var makeIntegerBinop = function(onFixnums, onBignums, options) { options = options || {}; return (function(m, n) { - if (m instanceof Rational) { - m = numerator(m); + if (m instanceof Rational || m instanceof Unitnum) { + m = m.toFixnum(); } - if (n instanceof Rational) { - n = numerator(n); + if (n instanceof Rational || n instanceof Unitnum) { + n = n.toFixnum(); } if (typeof(m) === 'number' && typeof(n) === 'number') { @@ -1222,9 +1222,8 @@ define("pyret-base/js/js-numbers", function() { var makeIntegerUnOp = function(onFixnums, onBignums, options, errbacks) { options = options || {}; return (function(m) { - m = _withoutUnit(m) - if (m instanceof Rational) { - m = numerator(m); + if (m instanceof Rational || m instanceof Unitnum) { + m = m.toFixnum(); } if (typeof(m) === 'number') { @@ -1275,7 +1274,7 @@ define("pyret-base/js/js-numbers", function() { return n === 0; }, function(n) { - return bnEquals.call(_withoutUnit(n), BigInteger.ZERO); + return bnEquals.call(n, BigInteger.ZERO); } ); @@ -1285,7 +1284,7 @@ define("pyret-base/js/js-numbers", function() { return n === 1; }, function(n) { - return bnEquals.call(_withoutUnit(n), BigInteger.ONE); + return bnEquals.call(n, BigInteger.ONE); }); // _integerIsNegativeOne: integer-pyretnum -> boolean @@ -1294,7 +1293,7 @@ define("pyret-base/js/js-numbers", function() { return n === -1; }, function(n) { - return bnEquals.call(_withoutUnit(n), BigInteger.NEGATIVE_ONE); + return bnEquals.call(n, BigInteger.NEGATIVE_ONE); }); // _integerAdd: integer-pyretnum integer-pyretnum -> integer-pyretnum @@ -1303,7 +1302,7 @@ define("pyret-base/js/js-numbers", function() { return m + n; }, function(m, n) { - return bnAdd.call(_withoutUnit(m), _withoutUnit(n)); + return bnAdd.call(m, n); }); // _integerSubtract: integer-pyretnum integer-pyretnum -> integer-pyretnum @@ -1312,7 +1311,7 @@ define("pyret-base/js/js-numbers", function() { return m - n; }, function(m, n) { - return bnSubtract.call(_withoutUnit(m), _withoutUnit(n)); + return bnSubtract.call(m, n); }); // _integerMultiply: integer-pyretnum integer-pyretnum -> integer-pyretnum @@ -1321,7 +1320,7 @@ define("pyret-base/js/js-numbers", function() { return m * n; }, function(m, n) { - return bnMultiply.call(_withoutUnit(m), _withoutUnit(n)); + return bnMultiply.call(m, n); }); //_integerQuotient: integer-pyretnum integer-pyretnum -> integer-pyretnum @@ -1330,7 +1329,7 @@ define("pyret-base/js/js-numbers", function() { return ((m - (m % n))/ n); }, function(m, n) { - return bnDivide.call(_withoutUnit(m), _withoutUnit(n)); + return bnDivide.call(m, n); }); var _integerRemainder = makeIntegerBinop( @@ -1338,7 +1337,7 @@ define("pyret-base/js/js-numbers", function() { return m % n; }, function(m, n) { - return bnRemainder.call(_withoutUnit(m), _withoutUnit(n)); + return bnRemainder.call(m, n); }); // splitIntIntoMantissaExpt: integer-pyretnum -> [JS-double, JS-int] @@ -1411,7 +1410,7 @@ define("pyret-base/js/js-numbers", function() { return m === n; }, function(m, n) { - return bnEquals.call(_withoutUnit(n), _withoutUnit(n)); + return bnEquals.call(m, n); }, {doNotCoerceToFloating: true}); @@ -1431,7 +1430,7 @@ define("pyret-base/js/js-numbers", function() { return m < n; }, function(m, n) { - return bnCompareTo.call(_withoutUnit(m), _withoutUnit(n)) < 0; + return bnCompareTo.call(m, n) < 0; }, {doNotCoerceToFloating: true}); @@ -1441,7 +1440,7 @@ define("pyret-base/js/js-numbers", function() { return m >= n; }, function(m, n) { - return bnCompareTo.call(_withoutUnit(m), _withoutUnit(n)) >= 0; + return bnCompareTo.call(m, n) >= 0; }, {doNotCoerceToFloating: true}); @@ -1451,7 +1450,7 @@ define("pyret-base/js/js-numbers", function() { return m <= n; }, function(m, n) { - return bnCompareTo.call(_withoutUnit(m), _withoutUnit(n)) <= 0; + return bnCompareTo.call(m, n) <= 0; }, {doNotCoerceToFloating: true}); @@ -1685,13 +1684,11 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.numerator = function(errbacks) { - // TODO(benmusch): How should this behave? Maybe remove the unit? - return new Unitnum(numerator(this.n), this.u); + _throwUnitsUnsupported(this.u, errbacks, "numerator"); }; Unitnum.prototype.denominator = function(errbacks) { - // TODO(benmusch): How should this behave? Maybe remove the unit? - return new Unitnum(denominator(this.n), this.u); + _throwUnitsUnsupported(this.u, errbacks, "denominator"); }; Unitnum.prototype.integerSqrt = function(errbacks) { From e86120551cac4d20a3f0d980483277a6cb8718ab Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Thu, 6 Jun 2019 19:14:29 -0400 Subject: [PATCH 44/84] Some housekeeping --- src/js/base/js-numbers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index fac71d79ae..7c2d288c38 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1191,6 +1191,7 @@ define("pyret-base/js/js-numbers", function() { options = options || {}; return (function(m, n) { if (m instanceof Rational || m instanceof Unitnum) { + // TODO(benmusch): Is this okay? Can we always use fixnums? m = m.toFixnum(); } @@ -1420,7 +1421,7 @@ define("pyret-base/js/js-numbers", function() { return m > n; }, function(m, n) { - return bnCompareTo.call(_withoutUnit(m), _withoutUnit(n)) > 0; + return bnCompareTo.call(m, n) > 0; }, {doNotCoerceToFloating: true}); From 22e808836ed70b4d608dcbf0738bb507cd65c73a Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 7 Jun 2019 09:45:53 -0400 Subject: [PATCH 45/84] Don't re-use mixed-binops --- src/arr/compiler/compile-structs.arr | 30 ++++++++++++++++++++++++++ src/arr/compiler/well-formed.arr | 6 +++--- tests/pyret/tests/test-well-formed.arr | 24 ++++++++++----------- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/arr/compiler/compile-structs.arr b/src/arr/compiler/compile-structs.arr index 6a319045d4..cdf133e351 100644 --- a/src/arr/compiler/compile-structs.arr +++ b/src/arr/compiler/compile-structs.arr @@ -725,6 +725,36 @@ data CompileError: ED.loc(self.op-b-loc), ED.text(". Use parentheses to group the operations and to make the order of operations clear.")]] end + | mixed-unit-ops(exp-loc, op-a-name, op-a-loc, op-b-name, op-b-loc) with: + method render-fancy-reason(self): + [ED.error: + [ED.para: + ED.text("Reading this "), + ED.highlight(ED.text("unit"), [ED.locs: self.exp-loc], -1), + ED.text(" errored:")], + ED.cmcode(self.exp-loc), + [ED.para: + ED.text("The "), + ED.code(ED.highlight(ED.text(self.op-a-name),[list: self.op-a-loc], 0)), + ED.text(" operation is at the same level as the "), + ED.code(ED.highlight(ED.text(self.op-b-name),[list: self.op-b-loc], 1)), + ED.text(" operation.")], + [ED.para: + ED.text("Use parentheses to group the operations and to make the order of operations clear.")]] + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.text("Unit operators of different kinds cannot be mixed at the same level, but "), + ED.code(ED.text(self.op-a-name)), + ED.text(" is at "), + ED.loc(self.op-a-loc), + ED.text(" at the same level as "), + ED.code(ED.text(self.op-b-name)), + ED.text(" at "), + ED.loc(self.op-b-loc), + ED.text(". Use parentheses to group the operations and to make the order of operations clear.")]] + end | block-ending(l :: Loc, block-loc :: Loc, kind) with: method render-fancy-reason(self): [ED.error: diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index 56423473e7..e2a9bfe9ad 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -307,20 +307,20 @@ fun reachable-ops-unit(self, l, op-l, parent, u): reachable-ops-unit(self, l2, op-l2, u, lhs) reachable-ops-unit(self, l2, op-l2, u, lhs) else: - add-error(C.mixed-binops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) + add-error(C.mixed-unit-ops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) end | u-div(l2, op-l2, lhs, rhs) => if A.is-u-div(parent) block: reachable-ops-unit(self, l2, op-l2, u, lhs) reachable-ops-unit(self, l2, op-l2, u, lhs) else: - add-error(C.mixed-binops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) + add-error(C.mixed-unit-ops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) end | u-pow(l2, op-l2, pow-u, n) => if A.is-u-pow(self): reachable-ops-unit(self, l2, op-l2, u, pow-u) else: - add-error(C.mixed-binops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) + add-error(C.mixed-unit-ops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) end | else => u.visit(self) end diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index b1959208a4..3e53d6db07 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -270,18 +270,18 @@ check "underscores": end check "unit annotations": - run-str("2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("1/2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("~1/2%") is%(output) compile-error(CS.is-mixed-binops) - run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-binops) - run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-binops) - run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-binops) + run-str("2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("~1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("~1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("~1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-unit-ops) + run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) From 880c7abef16d1aa3514f00cb038c6e377d622008 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 7 Jun 2019 10:59:15 -0400 Subject: [PATCH 46/84] Update annotations on num-* builtins --- src/arr/compiler/desugar.arr | 13 +-- src/js/base/runtime.js | 160 ++++++++++++++++----------- t.arr | 3 +- tests/pyret/tests/test-contracts.arr | 2 - 4 files changed, 101 insertions(+), 77 deletions(-) diff --git a/src/arr/compiler/desugar.arr b/src/arr/compiler/desugar.arr index 13e9fc5b71..0320ccf9a9 100644 --- a/src/arr/compiler/desugar.arr +++ b/src/arr/compiler/desugar.arr @@ -89,13 +89,11 @@ end fun desugar-afield(f :: A.AField) -> A.AField: A.a-field(f.l, f.name, desugar-ann(f.ann)) end -fun desugar-ann-helper(a :: A.Ann, seen-unit :: Boolean) -> A.Ann: +fun desugar-ann(a :: A.Ann) -> A.Ann: cases(A.Ann) a: | a-blank => a | a-any(_) => a - | a-name(l, _) => - # TODO(benmusch): should we check if this is a number-related name? - if seen-unit: a else: A.a-unit(l, a, A.u-one(A.dummy-loc)) end + | a-name(l, _) => a | a-type-var(_, _) => a | a-dot(_, _, _) => a | a-arrow(l, args, ret, use-parens) => @@ -111,14 +109,11 @@ fun desugar-ann-helper(a :: A.Ann, seen-unit :: Boolean) -> A.Ann: | a-tuple(l, fields) => A.a-tuple(l, fields.map(desugar-ann)) | a-unit(l, ann, u) => - A.a-unit(l, desugar-ann-helper(ann, true), u) + A.a-unit(l, desugar-ann(ann), u) | a-pred(l, ann, exp) => - A.a-pred(l, desugar-ann-helper(ann, seen-unit), desugar-expr(exp)) + A.a-pred(l, desugar-ann(ann), desugar-expr(exp)) end end -fun desugar-ann(a :: A.Ann) -> A.Ann: - desugar-ann-helper(a, false) -end fun desugar(program :: A.Program): doc: ``` diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 0921dc4615..1e7c6105a9 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -1356,8 +1356,9 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } } - var makeCheckType = function(test, typeName) { - if (arguments.length !== 2) { + var makeCheckType = function(test, typeName, opts) { + opts = opts || {} + if (arguments.length > 3) { // can't use checkArity yet because thisRuntime.ffi isn't initialized throw("MakeCheckType was called with the wrong number of arguments: expected 2, got " + arguments.length); } @@ -2553,7 +2554,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function checkAnn(compilerLoc, ann, val, after) { if(isCheapAnnotation(ann)) { - return returnOrRaise(ann.check(compilerLoc, val), val, after); + return returnOrRaise(ann.check(compilerLoc, val, {}), val, after); } else { return checkAnnSafe(compilerLoc, ann, val, after); @@ -2561,7 +2562,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkAnnSafe(compilerLoc, ann, val, after) { return safeCall(function() { - return ann.check(compilerLoc, val); + return ann.check(compilerLoc, val, {}); }, function(result) { return returnOrRaise(result, val, after); }, @@ -2581,7 +2582,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom ); } if (isCheapAnnotation(ann)) { - var result = ann.check(compilerLoc, args[index]); + var result = ann.check(compilerLoc, args[index], {}); if(thisRuntime.ffi.isOk(result)) { return args[index]; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(wrapReason(result)); @@ -2589,7 +2590,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throw "Internal error: got invalid result from annotation check"; } else { return safeCall(function() { - return ann.check(compilerLoc, args[index]); + return ann.check(compilerLoc, args[index], {}); }, function(result) { if(thisRuntime.ffi.isOk(result)) { return args[index]; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(wrapReason(result)); } @@ -2600,7 +2601,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal1(moduleName, funName, arg, ann) { - var result = ann.check(moduleName, arg); + var result = ann.check(moduleName, arg, {}); if(thisRuntime.ffi.isFail(result)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result, "loc"), @@ -2615,7 +2616,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal2(moduleName, funName, arg1, ann1, arg2, ann2) { - var result1 = ann1.check(moduleName, arg1); + var result1 = ann1.check(moduleName, arg1, {}); if(thisRuntime.ffi.isFail(result1)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result1, "loc"), @@ -2627,7 +2628,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result1, "reason")) )); } - var result2 = ann2.check(moduleName, arg2); + var result2 = ann2.check(moduleName, arg2, {}); if(thisRuntime.ffi.isFail(result2)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result2, "loc"), @@ -2642,7 +2643,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal3(moduleName, funName, arg1, ann1, arg2, ann2, arg3, ann3) { - var result1 = ann1.check(moduleName, arg1); + var result1 = ann1.check(moduleName, arg1, {}); if(thisRuntime.ffi.isFail(result1)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result1, "loc"), @@ -2654,7 +2655,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result1, "reason")) )); } - var result2 = ann2.check(moduleName, arg2); + var result2 = ann2.check(moduleName, arg2, {}); if(thisRuntime.ffi.isFail(result2)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result2, "loc"), @@ -2666,7 +2667,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result2, "reason")) )); } - var result3 = ann3.check(moduleName, arg3); + var result3 = ann3.check(moduleName, arg3, {}); if(thisRuntime.ffi.isFail(result3)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result3, "loc"), @@ -2686,7 +2687,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if (!isCheapAnnotation(argsAndAnns[index + 1])) { thisRuntime.ffi.throwMessageException("Internal error: non-stacksafe annotation given to checkArgsInternalInline"); } - var result = argsAndAnns[index + 1].check(moduleName, argsAndAnns[index]); + var result = argsAndAnns[index + 1].check(moduleName, argsAndAnns[index], {}); if(thisRuntime.ffi.isFail(result)) { var onlyArgs = []; for (var i = 0; i < argsAndAnns.length; i += 2) { @@ -2720,13 +2721,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function _checkAnn(compilerLoc, ann, val) { if (isCheapAnnotation(ann)) { - var result = ann.check(compilerLoc, val); + var result = ann.check(compilerLoc, val, {}); if(thisRuntime.ffi.isOk(result)) { return val; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(result); } throw "Internal error: got invalid result from annotation check"; } else { return safeCall(function() { - var res = ann.check(compilerLoc, val); + var res = ann.check(compilerLoc, val, {}); //if(thisRuntime.isContinuation(res)) { console.trace(); } return res; }, function(result) { @@ -2740,11 +2741,11 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function safeCheckAnnArg(compilerLoc, ann, val, after) { if(isCheapAnnotation(ann)) { - return returnOrRaise(ann.check(compilerLoc, val), val, after); + return returnOrRaise(ann.check(compilerLoc, val, {}), val, after); } else { return safeCall(function() { - var res = ann.check(compilerLoc, val); + var res = ann.check(compilerLoc, val, {}); //if(thisRuntime.isContinuation(res)) { console.trace(); } return res; }, function(result) { @@ -2863,14 +2864,14 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.ffi.makeTypeMismatch(val, that.name)); } } - PPrimAnn.prototype.check = function(compilerLoc, val) { + PPrimAnn.prototype.check = function(compilerLoc, val, metadata) { var that = this; if(isCheapAnnotation(this)) { - return this.checkOrFail(this.pred(val), val, compilerLoc); + return this.checkOrFail(this.pred(val, metadata), val, compilerLoc); } else { return safeCall(function() { - return that.pred(val); + return that.pred(val, metadata); }, function(passed) { return that.checkOrFail(passed, val, compilerLoc); }, @@ -2908,13 +2909,15 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom this.anns.push({ ann: ann, loc: loc }); } - PAnnList.prototype.check = function(compilerLoc, val) { + PAnnList.prototype.check = function(compilerLoc, val, metadata) { var that = this; function checkI(i) { if(i >= that.anns.length) { return thisRuntime.ffi.contractOk; } else { return safeCall(function() { - return that.anns[i].ann.check(compilerLoc, val); + // TODO(benmusch): should metadata reset here, or just clear out the + // hasCheckedUnits field? + return that.anns[i].ann.check(compilerLoc, val, {}); }, function(passed) { if(thisRuntime.ffi.isOk(passed)) { return checkI(i + 1); } else { @@ -2938,14 +2941,15 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function makeUnitAnn(ann, u) { return new PUnitAnn(ann, u); } - PUnitAnn.prototype.check = function(compilerLoc, val) { + PUnitAnn.prototype.check = function(compilerLoc, val, metadata) { + metadata.hasCheckedUnits = true if (!this.isAny && !jsnums.checkUnit(jsnums.getUnit(val), this.u)) { var name = "<" + jsnums.unitToString(this.u) + ">"; return thisRuntime.ffi.contractFail( makeSrcloc(compilerLoc), thisRuntime.ffi.makePredicateFailure(val, name)); } else { - return this.ann.check(compilerLoc, val); + return this.ann.check(compilerLoc, val, metadata); } } @@ -2967,7 +2971,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom newAnn.flat = true; return newAnn; } - PPredAnn.prototype.check = function(compilerLoc, val) { + PPredAnn.prototype.check = function(compilerLoc, val, metadata) { function fail() { return thisRuntime.ffi.contractFail( makeSrcloc(compilerLoc), @@ -2977,7 +2981,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // NOTE(joe): fast, safe path for flat refinement if(that.flat) { - var result = that.ann.check(compilerLoc, val); + var result = that.ann.check(compilerLoc, val, metadata); if(thisRuntime.ffi.isOk(result)) { var predPassed = that.pred.app(val); if(predPassed) { return thisRuntime.ffi.contractOk; } @@ -2989,7 +2993,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } return safeCall(function() { - return that.ann.check(compilerLoc, val); + return that.ann.check(compilerLoc, val, metadata); }, function(result) { if(thisRuntime.ffi.isOk(result)) { return safeCall(function() { @@ -3031,7 +3035,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function makeTupleAnn(locs, anns) { return new PTupleAnn(locs, anns); } - PTupleAnn.prototype.check = function(compilerLoc, val) { + PTupleAnn.prototype.check = function(compilerLoc, val, metadata) { var that = this; if(!isTuple(val)) { return thisRuntime.ffi.contractFail( @@ -3047,7 +3051,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // Fast path for flat refinements, since arbitrary stack space can't be consumed if(that.flat) { for(var i = 0; i < that.anns.length; i++) { - var result = that.anns[i].check(that.locs[i], val.vals[i]); + var result = that.anns[i].check(that.locs[i], val.vals[i], {}); if(!thisRuntime.ffi.isOk(result)) { return this.createTupleFailureError(compilerLoc, val, this.anns[i], result); //return result; @@ -3062,7 +3066,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return safeCall(function() { var thisChecker = remainingAnns.pop(); thisAnn = thisChecker; - return thisChecker.check(that.locs[that.locs.length - remainingAnns.length], val.vals[remainingAnns.length]); + return thisChecker.check(that.locs[that.locs.length - remainingAnns.length], val.vals[remainingAnns.length], {}); }, function(result) { if(thisRuntime.ffi.isOk(result)) { if(remainingAnns.length === 0) { return thisRuntime.ffi.contractOk; } @@ -3152,7 +3156,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom ])) ); }; - PRecordAnn.prototype.check = function(compilerLoc, val) { + PRecordAnn.prototype.check = function(compilerLoc, val, metadata) { var that = this; if(!isObject(val)) { return thisRuntime.ffi.contractFail( @@ -3170,7 +3174,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if(that.flat) { for(var i = 0; i < that.fields.length; i++) { var thisField = that.fields[i]; - var result = that.anns[thisField].check(that.locs[i], getColonField(val, thisField)); + var result = that.anns[thisField].check(that.locs[i], getColonField(val, thisField), {}); if(!thisRuntime.ffi.isOk(result)) { return result; } @@ -3184,7 +3188,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return safeCall(function() { thisField = remainingFields.pop(); var thisChecker = that.anns[thisField]; - return thisChecker.check(that.locs[that.locs.length - remainingFields.length], getColonField(val, thisField)); + return thisChecker.check(that.locs[that.locs.length - remainingFields.length], getColonField(val, thisField), {}); }, function(result) { if(thisRuntime.ffi.isOk(result)) { if(remainingFields.length === 0) { return thisRuntime.ffi.contractOk; } @@ -4886,10 +4890,11 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom var num_equal = function(l, r) { if (arguments.length !== 2) { var $a=new Array(arguments.length); for (var $i=0;$i +fun id(n :: Number%%(num-is-integer)): n end +id(1%) diff --git a/tests/pyret/tests/test-contracts.arr b/tests/pyret/tests/test-contracts.arr index 20f9d6287f..95b91fa8ea 100644 --- a/tests/pyret/tests/test-contracts.arr +++ b/tests/pyret/tests/test-contracts.arr @@ -543,12 +543,10 @@ check "Should notice unit mis-matches": fun id(n :: { a :: Number%, b :: Number%}): n end id({ a: 1%, b: 1%}) ```) is%(output) success - #| TODO: re-comment when working on unit annotations run-str( ``` type MyAny = Any fun id(n :: MyAny): n end id(1%) ```) is%(output) success - |# end From 9f1561f972e2ad94d0275be4e6cd516e473f7a76 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 7 Jun 2019 11:46:36 -0400 Subject: [PATCH 47/84] Fix-up tests, now failing due to error messages. Need to test every num-* function, probably --- tests/pyret/tests/test-units.arr | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 915e662e20..3173427381 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -59,10 +59,7 @@ check "Other supported operations": |# num-abs(-2%) is 2% num-floor(3/2%) is 1% - num-expt(2%, 3) is 8% num-round(3/2%) is 2% - - num-expt(2%, 3%) raises "expt: power cannot have a unit" end check "Unsupported unops": @@ -79,4 +76,6 @@ check "Unsupported unops": num-tan(2%) raises "The tan operation does not support units but was given an argument with the unit m" num-cos(2%) raises "The cos operation does not support units but was given an argument with the unit m" num-exp(2%) raises "The exp operation does not support units but was given an argument with the unit m" + num-expt(2%, 3) raises "Blah" + num-expt(2%, 3%) raises "Blah" end From ca860327026d92240a00f3176aa8c3f20d3f86b9 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 7 Jun 2019 16:12:52 -0400 Subject: [PATCH 48/84] Test all num functions, fix some edge cases --- src/js/base/js-numbers.js | 18 +++++- src/js/base/runtime.js | 8 +-- tests/pyret/tests/test-units.arr | 94 +++++++++++++++++++++++++------- 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 7c2d288c38..89779f4596 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -748,14 +748,14 @@ define("pyret-base/js/js-numbers", function() { var numerator = function(n, errbacks) { if (typeof(n) === 'number') return n; - return n.numerator(); + return n.numerator(errbacks); }; // denominator: pyretnum -> pyretnum var denominator = function(n, errbacks) { if (typeof(n) === 'number') return 1; - return n.denominator(); + return n.denominator(errbacks); }; // sqrt: pyretnum -> pyretnum @@ -1575,6 +1575,10 @@ define("pyret-base/js/js-numbers", function() { // round: -> pyretnum // Round to the nearest integer. + // roundEven: -> pyretnum + // Round to the nearest integer, returning an exactnum if the number is + // between integers + // equals: pyretnum -> boolean // Produce true if the given number of the same type is equal. @@ -1761,6 +1765,10 @@ define("pyret-base/js/js-numbers", function() { return new Unitnum(round(this.n), this.u); }; + Unitnum.prototype.roundEven = function() { + return new Unitnum(roundEven(this.n), this.u); + }; + Unitnum.prototype.equals = function(other) { // TODO(benmusch): should this support a polymorphic zero which ignores // units? @@ -4283,6 +4291,10 @@ define("pyret-base/js/js-numbers", function() { if (!isInteger(digits)) { errbacks.throwDomainError('num-to-string-digits: digits should be an integer'); } + + if (n instanceof Unitnum) { + return toStringDigits(n.n, digits, errbacks) + "<" + _unitToString(n.u) + ">"; + } var tenDigits = expt(10, digits, errbacks); var d = toFixnum(digits); n = divide(round(multiply(n, tenDigits, errbacks), errbacks), tenDigits, errbacks); @@ -4297,7 +4309,7 @@ define("pyret-base/js/js-numbers", function() { return ans; } // n is not an integer implies that d >= 1 - var decimal = toRepeatingDecimal(n.numerator(), n.denominator(), undefined, errbacks); + var decimal = toRepeatingDecimal(numerator(n), denominator(n), undefined, errbacks); var ans = decimal[1].toString(); while (ans.length < d) { ans += decimal[2]; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 1e7c6105a9..61538b4f49 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -4890,7 +4890,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom var num_equal = function(l, r) { if (arguments.length !== 2) { var $a=new Array(arguments.length); for (var $i=0;$i, 2%) is true + num-equal(4/2%, 2%) is true + num-equal(4/2, 2%) is false + + num-to-roughnum(2%) is-roughly ~2.0% + + num-is-integer(2%) is true + num-is-integer(2.5%) is false + + num-is-rational(2%) is true + num-is-rational(2.5%) is true + num-is-rational(1/2%) is true + num-is-rational(~2%) is false + + num-is-roughnum(2%) is false + num-is-roughnum(1/2%) is false + num-is-roughnum(1.609%) is false + num-is-roughnum(~2%) is true + + num-is-positive(2%) is true + num-is-positive(0%) is false + num-is-positive(-2%) is false + num-is-non-positive(2%) is false + num-is-non-positive(0%) is true + num-is-non-positive(-2%) is true + + num-is-negative(2%) is false + num-is-negative(0%) is false + num-is-negative(-2%) is true + num-is-non-negative(2%) is true + num-is-non-negative(0%) is true + num-is-non-negative(-2%) is false + + num-to-string(2.5%) is "5/2" + num-to-string(2%) is "2" + num-to-string(2/3%) is "2/3" + num-to-string(~2.718%) is "~2.718" + num-to-string(~6.022e23%) is "~6.022e+23" + + num-to-string-digits(2/3%, 3) is "0.667" + num-to-string-digits(-2/3%, 3) is "-0.667" + + # TODO: Failing... hmmm.... + num-to-string-digits(5%, 2) is "5.00" + + num-to-string-digits(5%, 0) is "5" + num-to-string-digits(555%, -2) is "600" + + num-max(2%, 1%) is 2% + num-max(2%, 2%) raises "" + num-min(2%, 1%) is 1% + num-min(2%, 2%) raises "" + num-abs(-2%) is 2% num-floor(3/2%) is 1% + num-ceiling(3/2%) is 2% num-round(3/2%) is 2% + num-round-even(3.5%) is 4% + num-truncate(3.5%) is 3% + num-sqr(3%) is 9% end check "Unsupported unops": - #| - TODO: - integerSqrt() - is there any way to trigger this or is this unused? - |# - num-sqrt(2%) raises "The square root operation does not support units but was given an argument with the unit m" - num-log(2%) raises "The log operation does not support units but was given an argument with the unit m" - num-atan(2%) raises "The atan operation does not support units but was given an argument with the unit m" - num-asin(0%) raises "The asin operation does not support units but was given an argument with the unit m" - num-acos(0%) raises "The acos operation does not support units but was given an argument with the unit m" - num-sin(2%) raises "The sin operation does not support units but was given an argument with the unit m" - num-tan(2%) raises "The tan operation does not support units but was given an argument with the unit m" - num-cos(2%) raises "The cos operation does not support units but was given an argument with the unit m" - num-exp(2%) raises "The exp operation does not support units but was given an argument with the unit m" - num-expt(2%, 3) raises "Blah" - num-expt(2%, 3%) raises "Blah" + num-sqrt(2%) raises "" + num-log(2%) raises "" + num-atan(2%) raises "" + num-atan2(2%) raises "" + num-asin(0%) raises "" + num-acos(0%) raises "" + num-sin(2%) raises "" + num-tan(2%) raises "" + num-cos(2%) raises "" + num-exp(2%) raises "" + num-expt(2%, 3) raises "" + num-expt(2%, 3%) raises "" + num-modulo(2%, 2%) raises "" + num-to-string-digits(2/3%, 3%) raises "" + num-within-abs(2%) raises "" + num-within-rel(2%) raises "" end From c9f8ea32766dc44ed5163c6fd679706bd05e8060 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 12 Jun 2019 23:45:56 -0400 Subject: [PATCH 49/84] Add withUnits pattern with some intermediate hacks and test failures --- src/arr/trove/error.arr | 1 + src/js/base/js-numbers.js | 49 ++++++----- src/js/base/runtime.js | 140 +++++++++++++++++-------------- t.arr | 6 +- tests/pyret/tests/test-units.arr | 2 + 5 files changed, 112 insertions(+), 86 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 6db43a79fd..80a1198002 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -85,6 +85,7 @@ fun destructure-method-application(l, src-available, maybe-ast): end data RuntimeError: + # note to self: ED.text(to-repr(self)) for simple string-ification | multi-error(errors #|:: List|#) with: method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): # The concatenation of renderings is _not_ a valid rendering; diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 89779f4596..2a2a1add72 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -135,8 +135,8 @@ define("pyret-base/js/js-numbers", function() { if (x instanceof Unitnum || y instanceof Unitnum) { // if x or y have units, ensure they are both wrapped in unitnums - x = _withUnit(x, _unitOf(x)); - y = _withUnit(y, _unitOf(y)); + x = _withUnit(x, _unitOf(x), true); + y = _withUnit(y, _unitOf(y), true); } else if (x instanceof Roughnum) { // y is rough, rat or bigint if (!(y instanceof Roughnum)) { @@ -1060,9 +1060,12 @@ define("pyret-base/js/js-numbers", function() { // Unit operations - var _withUnit = function(n, u) { - if (n instanceof Unitnum) { - return new Unitnum(n.n, u); + var _withUnit = function(n, u, forceUnitnum) { + if (_unitEquals(u, {}) && !forceUnitnum) { + return _withoutUnit(n); + } else if (n instanceof Unitnum) { + // TODO: make sure this is okay in all instances + return _withUnit(n.n, u, forceUnitnum); } else { return new Unitnum(n, u); } @@ -1634,13 +1637,13 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.toRational = function() { - return new Unitnum(toRational(this.n), this.u); + return _withUnit(toRational(this.n), this.u, false); } Unitnum.prototype.toExact = Unitnum.prototype.toRational; Unitnum.prototype.toRoughnum = function() { - return new Unitnum(toRoughnum(this.n), this.u); + return _withUnit(toRoughnum(this.n), this.u, false); }; Unitnum.prototype.toFixnum = function() { @@ -1669,23 +1672,22 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.add = function(n, errbacks) { _ensureSameUnits(this.u, _unitOf(n), errbacks, "+"); - return _withUnit(add(_withoutUnit(this), _withoutUnit(n), errbacks), this.u); + return _withUnit(add(_withoutUnit(this), _withoutUnit(n), errbacks), this.u, false); }; Unitnum.prototype.subtract = function(n, errbacks) { _ensureSameUnits(this.u, _unitOf(n), errbacks, "-"); - return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n), errbacks), this.u); + return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n), errbacks), this.u, false); }; Unitnum.prototype.multiply = function(n, errbacks) { var newUnit = _unitMerge(this.u, n.u); - // TODO(benmusch): This & divide should un-box the numbers if the unit is 1 - return _withUnit(multiply(_withoutUnit(this), _withoutUnit(n), errbacks), newUnit); + return _withUnit(multiply(_withoutUnit(this), _withoutUnit(n), errbacks), newUnit, false); }; Unitnum.prototype.divide = function(n, errbacks) { var newUnit = _unitMerge(this.u, _unitInvert(n.u)); - return _withUnit(divide(_withoutUnit(this), _withoutUnit(n), errbacks), newUnit); + return _withUnit(divide(_withoutUnit(this), _withoutUnit(n), errbacks), newUnit, false); }; Unitnum.prototype.numerator = function(errbacks) { @@ -1707,15 +1709,15 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.abs = function() { - return new Unitnum(abs(this.n), this.u); + return _withUnit(abs(this.n), this.u, false); }; Unitnum.prototype.floor = function() { - return new Unitnum(floor(this.n), this.u); + return _withUnit(floor(this.n), this.u, false); }; Unitnum.prototype.ceiling = function() { - return new Unitnum(ceiling(this.n), this.u); + return _withUnit(ceiling(this.n), this.u, false); }; Unitnum.prototype.log = function(errbacks) { @@ -1744,29 +1746,29 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.expt = function(n) { var newUnit = _unitMap(this.u, function(pow) { return n * pow }); - return new Unitnum(expt(this.n, n), newUnit); + return _withUnit(expt(this.n, n), newUnit, false); }; Unitnum.prototype.exp = function(errbacks) { - _throwUnitsUnsupported(this.u, errbacks, "exp"); + _throwUnitsUnsupported(this.u, errbacks, "exp", false); }; Unitnum.prototype.acos = function(errbacks) { // TODO(benmusch): potentially support units here - _throwUnitsUnsupported(this.u, errbacks, "acos"); + _throwUnitsUnsupported(this.u, errbacks, "acos", false); }; Unitnum.prototype.asin = function(errbacks) { // TODO(benmusch): potentially support units here - _throwUnitsUnsupported(this.u, errbacks, "asin"); + _throwUnitsUnsupported(this.u, errbacks, "asin", false); }; Unitnum.prototype.round = function() { - return new Unitnum(round(this.n), this.u); + return _withUnit(round(this.n), this.u, false); }; Unitnum.prototype.roundEven = function() { - return new Unitnum(roundEven(this.n), this.u); + return _withUnit(roundEven(this.n), this.u, false); }; Unitnum.prototype.equals = function(other) { @@ -2456,7 +2458,7 @@ define("pyret-base/js/js-numbers", function() { }; var addUnit = function(n, u) { - return new Unitnum(n, u); + return _withUnit(n, u, false); }; /////////////////////////////////////////////////////////// @@ -3042,6 +3044,9 @@ define("pyret-base/js/js-numbers", function() { // (protected) r = this * a, r != this,a (HAC 14.12) // "this" should be the larger one if appropriate. function bnpMultiplyTo(a,r) { + if (a === undefined || r === undefined || this === undefined) { + console.log("this", this, "a", a, "r", r) + } var x = this.abs(), y = a.abs(); var i = x.t; r.t = i+y.t; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 61538b4f49..f5019c2614 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -1356,9 +1356,8 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } } - var makeCheckType = function(test, typeName, opts) { - opts = opts || {} - if (arguments.length > 3) { + var makeCheckType = function(test, typeName) { + if (arguments.length !== 2) { // can't use checkArity yet because thisRuntime.ffi isn't initialized throw("MakeCheckType was called with the wrong number of arguments: expected 2, got " + arguments.length); } @@ -2554,7 +2553,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function checkAnn(compilerLoc, ann, val, after) { if(isCheapAnnotation(ann)) { - return returnOrRaise(ann.check(compilerLoc, val, {}), val, after); + return returnOrRaise(ann.check(compilerLoc, val), val, after); } else { return checkAnnSafe(compilerLoc, ann, val, after); @@ -2562,7 +2561,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkAnnSafe(compilerLoc, ann, val, after) { return safeCall(function() { - return ann.check(compilerLoc, val, {}); + return ann.check(compilerLoc, val); }, function(result) { return returnOrRaise(result, val, after); }, @@ -2582,7 +2581,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom ); } if (isCheapAnnotation(ann)) { - var result = ann.check(compilerLoc, args[index], {}); + var result = ann.check(compilerLoc, args[index]); if(thisRuntime.ffi.isOk(result)) { return args[index]; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(wrapReason(result)); @@ -2590,7 +2589,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throw "Internal error: got invalid result from annotation check"; } else { return safeCall(function() { - return ann.check(compilerLoc, args[index], {}); + return ann.check(compilerLoc, args[index]); }, function(result) { if(thisRuntime.ffi.isOk(result)) { return args[index]; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(wrapReason(result)); } @@ -2601,7 +2600,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal1(moduleName, funName, arg, ann) { - var result = ann.check(moduleName, arg, {}); + var result = ann.check(moduleName, arg); if(thisRuntime.ffi.isFail(result)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result, "loc"), @@ -2616,7 +2615,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal2(moduleName, funName, arg1, ann1, arg2, ann2) { - var result1 = ann1.check(moduleName, arg1, {}); + var result1 = ann1.check(moduleName, arg1); if(thisRuntime.ffi.isFail(result1)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result1, "loc"), @@ -2628,7 +2627,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result1, "reason")) )); } - var result2 = ann2.check(moduleName, arg2, {}); + var result2 = ann2.check(moduleName, arg2); if(thisRuntime.ffi.isFail(result2)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result2, "loc"), @@ -2643,7 +2642,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function checkArgsInternal3(moduleName, funName, arg1, ann1, arg2, ann2, arg3, ann3) { - var result1 = ann1.check(moduleName, arg1, {}); + var result1 = ann1.check(moduleName, arg1); if(thisRuntime.ffi.isFail(result1)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result1, "loc"), @@ -2655,7 +2654,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result1, "reason")) )); } - var result2 = ann2.check(moduleName, arg2, {}); + var result2 = ann2.check(moduleName, arg2); if(thisRuntime.ffi.isFail(result2)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result2, "loc"), @@ -2667,7 +2666,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.getField(result2, "reason")) )); } - var result3 = ann3.check(moduleName, arg3, {}); + var result3 = ann3.check(moduleName, arg3); if(thisRuntime.ffi.isFail(result3)) { raiseJSJS(thisRuntime.ffi.contractFail( thisRuntime.getField(result3, "loc"), @@ -2687,7 +2686,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if (!isCheapAnnotation(argsAndAnns[index + 1])) { thisRuntime.ffi.throwMessageException("Internal error: non-stacksafe annotation given to checkArgsInternalInline"); } - var result = argsAndAnns[index + 1].check(moduleName, argsAndAnns[index], {}); + var result = argsAndAnns[index + 1].check(moduleName, argsAndAnns[index]); if(thisRuntime.ffi.isFail(result)) { var onlyArgs = []; for (var i = 0; i < argsAndAnns.length; i += 2) { @@ -2721,13 +2720,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function _checkAnn(compilerLoc, ann, val) { if (isCheapAnnotation(ann)) { - var result = ann.check(compilerLoc, val, {}); + var result = ann.check(compilerLoc, val); if(thisRuntime.ffi.isOk(result)) { return val; } if(thisRuntime.ffi.isFail(result)) { raiseJSJS(result); } throw "Internal error: got invalid result from annotation check"; } else { return safeCall(function() { - var res = ann.check(compilerLoc, val, {}); + var res = ann.check(compilerLoc, val); //if(thisRuntime.isContinuation(res)) { console.trace(); } return res; }, function(result) { @@ -2741,11 +2740,11 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function safeCheckAnnArg(compilerLoc, ann, val, after) { if(isCheapAnnotation(ann)) { - return returnOrRaise(ann.check(compilerLoc, val, {}), val, after); + return returnOrRaise(ann.check(compilerLoc, val), val, after); } else { return safeCall(function() { - var res = ann.check(compilerLoc, val, {}); + var res = ann.check(compilerLoc, val); //if(thisRuntime.isContinuation(res)) { console.trace(); } return res; }, function(result) { @@ -2864,14 +2863,14 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom thisRuntime.ffi.makeTypeMismatch(val, that.name)); } } - PPrimAnn.prototype.check = function(compilerLoc, val, metadata) { + PPrimAnn.prototype.check = function(compilerLoc, val) { var that = this; if(isCheapAnnotation(this)) { - return this.checkOrFail(this.pred(val, metadata), val, compilerLoc); + return this.checkOrFail(this.pred(val), val, compilerLoc); } else { return safeCall(function() { - return that.pred(val, metadata); + return that.pred(val); }, function(passed) { return that.checkOrFail(passed, val, compilerLoc); }, @@ -2883,14 +2882,18 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return new PPrimAnn(name, jsPred); } - function makePrimAnn(name, jsPred) { - var nameC = new PPrimAnn(name, jsPred); + function fillPrimAnnNamespace(name, ann) { // NOTE(joe): the $type$ sadness is because we only have one dynamic // namespace - runtimeTypeBindings[name] = nameC; - runtimeNamespaceBindings['$type$' + name] = nameC; - runtimeNamespaceBindings[name] = nameC; - thisRuntime[name] = nameC; + runtimeTypeBindings[name] = ann; + runtimeNamespaceBindings['$type$' + name] = ann; + runtimeNamespaceBindings[name] = ann; + thisRuntime[name] = ann; + } + + function makePrimAnn(name, jsPred) { + var nameC = new PPrimAnn(name, jsPred); + fillPrimAnnNamespace(name, nameC); } function PAnnList(anns) { @@ -2909,15 +2912,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom this.anns.push({ ann: ann, loc: loc }); } - PAnnList.prototype.check = function(compilerLoc, val, metadata) { + PAnnList.prototype.check = function(compilerLoc, val) { var that = this; function checkI(i) { if(i >= that.anns.length) { return thisRuntime.ffi.contractOk; } else { return safeCall(function() { - // TODO(benmusch): should metadata reset here, or just clear out the - // hasCheckedUnits field? - return that.anns[i].ann.check(compilerLoc, val, {}); + return that.anns[i].ann.check(compilerLoc, val); }, function(passed) { if(thisRuntime.ffi.isOk(passed)) { return checkI(i + 1); } else { @@ -2932,24 +2933,36 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return checkI(0); } - function PUnitAnn(ann, u) { + function PUnitAnn(ann, u, implicit) { this.ann = ann; this.u = u; this.flat = true; this.isAny = jsnums.checkUnit(this.u, { "_": 1 }) + this.implicit = implicit; + } + function makeUnitAnn(ann, u, srcLoc) { + if (ann.withUnit === undefined) { + thisRuntime.ffi.throwMessageException("Units!"); + } + return ann.withUnit(u); } - function makeUnitAnn(ann, u) { - return new PUnitAnn(ann, u); + PUnitAnn.prototype.withUnit = function(unit) { + if (this.implicit) { + return new PUnitAnn(this.ann, unit, false); + } else if (jsnums.checkUnit(this.u, unit)) { + return this; + } else { + thisRuntime.ffi.throwMessageException("Units!"); + } } - PUnitAnn.prototype.check = function(compilerLoc, val, metadata) { - metadata.hasCheckedUnits = true + PUnitAnn.prototype.check = function(compilerLoc, val) { if (!this.isAny && !jsnums.checkUnit(jsnums.getUnit(val), this.u)) { var name = "<" + jsnums.unitToString(this.u) + ">"; return thisRuntime.ffi.contractFail( makeSrcloc(compilerLoc), thisRuntime.ffi.makePredicateFailure(val, name)); } else { - return this.ann.check(compilerLoc, val, metadata); + return this.ann.check(compilerLoc, val); } } @@ -2971,7 +2984,11 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom newAnn.flat = true; return newAnn; } - PPredAnn.prototype.check = function(compilerLoc, val, metadata) { + PPredAnn.prototype.withUnit = function(unit) { + console.log("Got withUnit on pred") + return new PPredAnn(makeUnitAnn(this.ann, unit), this.pred, this.predName, this.flat) + } + PPredAnn.prototype.check = function(compilerLoc, val) { function fail() { return thisRuntime.ffi.contractFail( makeSrcloc(compilerLoc), @@ -2981,7 +2998,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // NOTE(joe): fast, safe path for flat refinement if(that.flat) { - var result = that.ann.check(compilerLoc, val, metadata); + var result = that.ann.check(compilerLoc, val); if(thisRuntime.ffi.isOk(result)) { var predPassed = that.pred.app(val); if(predPassed) { return thisRuntime.ffi.contractOk; } @@ -2993,7 +3010,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } return safeCall(function() { - return that.ann.check(compilerLoc, val, metadata); + return that.ann.check(compilerLoc, val); }, function(result) { if(thisRuntime.ffi.isOk(result)) { return safeCall(function() { @@ -3035,7 +3052,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function makeTupleAnn(locs, anns) { return new PTupleAnn(locs, anns); } - PTupleAnn.prototype.check = function(compilerLoc, val, metadata) { + PTupleAnn.prototype.check = function(compilerLoc, val) { var that = this; if(!isTuple(val)) { return thisRuntime.ffi.contractFail( @@ -3051,7 +3068,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // Fast path for flat refinements, since arbitrary stack space can't be consumed if(that.flat) { for(var i = 0; i < that.anns.length; i++) { - var result = that.anns[i].check(that.locs[i], val.vals[i], {}); + var result = that.anns[i].check(that.locs[i], val.vals[i]); if(!thisRuntime.ffi.isOk(result)) { return this.createTupleFailureError(compilerLoc, val, this.anns[i], result); //return result; @@ -3066,7 +3083,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return safeCall(function() { var thisChecker = remainingAnns.pop(); thisAnn = thisChecker; - return thisChecker.check(that.locs[that.locs.length - remainingAnns.length], val.vals[remainingAnns.length], {}); + return thisChecker.check(that.locs[that.locs.length - remainingAnns.length], val.vals[remainingAnns.length]); }, function(result) { if(thisRuntime.ffi.isOk(result)) { if(remainingAnns.length === 0) { return thisRuntime.ffi.contractOk; } @@ -3156,7 +3173,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom ])) ); }; - PRecordAnn.prototype.check = function(compilerLoc, val, metadata) { + PRecordAnn.prototype.check = function(compilerLoc, val) { var that = this; if(!isObject(val)) { return thisRuntime.ffi.contractFail( @@ -3174,7 +3191,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if(that.flat) { for(var i = 0; i < that.fields.length; i++) { var thisField = that.fields[i]; - var result = that.anns[thisField].check(that.locs[i], getColonField(val, thisField), {}); + var result = that.anns[thisField].check(that.locs[i], getColonField(val, thisField)); if(!thisRuntime.ffi.isOk(result)) { return result; } @@ -3188,7 +3205,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return safeCall(function() { thisField = remainingFields.pop(); var thisChecker = that.anns[thisField]; - return thisChecker.check(that.locs[that.locs.length - remainingFields.length], getColonField(val, thisField), {}); + return thisChecker.check(that.locs[that.locs.length - remainingFields.length], getColonField(val, thisField)); }, function(result) { if(thisRuntime.ffi.isOk(result)) { if(remainingFields.length === 0) { return thisRuntime.ffi.contractOk; } @@ -6095,24 +6112,21 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom 'makePrimAnn': makePrimAnn }; - function withUnitCheck(checkFun) { - return function(val, metadata) { - if (!metadata.hasCheckedUnits && !jsnums.checkUnit(jsnums.getUnit(val), {})) { - return false; - } - return checkFun(val); - } + function makePrimUnitAnn(name, checkFun, unit) { + var ann = makePrimitiveAnn(name, checkFun); + ann = new PUnitAnn(ann, unit, true); + fillPrimAnnNamespace(name, ann); } - makePrimAnn("Number", withUnitCheck(isNumber)); - makePrimAnn("Exactnum", withUnitCheck(jsnums.isRational)); - makePrimAnn("Roughnum", withUnitCheck(jsnums.isRoughnum)); - makePrimAnn("NumInteger", withUnitCheck(jsnums.isInteger)); - makePrimAnn("NumRational", withUnitCheck(jsnums.isRational)); - makePrimAnn("NumPositive", withUnitCheck(jsnums.isPositive)); - makePrimAnn("NumNegative", withUnitCheck(jsnums.isNegative)); - makePrimAnn("NumNonPositive", withUnitCheck(jsnums.isNonPositive)); - makePrimAnn("NumNonNegative", withUnitCheck(jsnums.isNonNegative)); + makePrimUnitAnn("Number", isNumber, {}); + makePrimUnitAnn("Exactnum", jsnums.isRational, {}); + makePrimUnitAnn("Roughnum", jsnums.isRoughnum, {}); + makePrimUnitAnn("NumInteger", jsnums.isInteger, {}); + makePrimUnitAnn("NumRational", jsnums.isRational, {}); + makePrimUnitAnn("NumPositive", jsnums.isPositive, {}); + makePrimUnitAnn("NumNegative", jsnums.isNegative, {}); + makePrimUnitAnn("NumNonPositive", jsnums.isNonPositive, {}); + makePrimUnitAnn("NumNonNegative", jsnums.isNonNegative, {}); makePrimAnn("NumberAnyUnit", isNumber); makePrimAnn("ExactnumAnyUnit", jsnums.isRational); @@ -6132,7 +6146,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom makePrimAnn("Nothing", isNothing); makePrimAnn("Object", isObject); makePrimAnn("Tuple", isTuple); - makePrimAnn("Any", function() { return true; }); + makePrimUnitAnn("Any", function() { return true; }, {"_": 1}); thisRuntime.namespace = Namespace.namespace(runtimeNamespaceBindings); diff --git a/t.arr b/t.arr index e9e6498c37..5a156d3cd4 100644 --- a/t.arr +++ b/t.arr @@ -1,2 +1,6 @@ -fun id(n :: Number%%(num-is-integer)): n end +type N = Number% + +fun id(n :: N%(num-is-integer)): n end id(1%) + +print(within(10)(~1/2%, ~1/2%)) diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 8c5b44f690..fb79275b2c 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -13,6 +13,8 @@ check "Unit equality": # tests for adding/removing units (10 * 1%) + 1% is 11% (10% / 1%) + 1 is 11 + + # test poly 0 end check "Arithmetic binops": From dcc35ead00a5c6e47789e3c382a95f8420315db0 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 14 Jun 2019 15:06:27 -0400 Subject: [PATCH 50/84] Slightly better errors --- src/arr/compiler/anf-loop-compiler.arr | 2 +- src/arr/trove/error.arr | 13 +++++++++++++ src/js/base/runtime.js | 13 ++++++------- src/js/trove/ffi.js | 5 +++++ t.arr | 7 +------ 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index e167e4b040..740ed9f706 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -428,7 +428,7 @@ fun compile-ann(ann :: A.Ann, visitor) -> DAG.CaseResults%(is-c-exp): compiled-base = compile-ann(base, visitor) compiled-unit = compile-unit(u) c-exp( - rt-method("makeUnitAnn", [clist: compiled-base.exp, compiled-unit]), + rt-method("makeUnitAnn", [clist: compiled-base.exp, compiled-unit, visitor.get-loc(l)]), cl-empty) | a-dot(l, m, field) => c-exp( diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 80a1198002..5faca55c9b 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2651,4 +2651,17 @@ data ParseError: method render-reason(self): ED.text("missing-end: " + self.loc.format(true)) end | missing-comma(loc) with: method render-reason(self): ED.text("missing-comma: " + self.loc.format(true)) end + | units-on-unsupported-ann(loc, unit-str) with: + method render-fancy-reason(self): + ED.text("Unittsssss (but fancy)") + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.text("The annotation at "), + ED.loc(self.loc), + ED.text(" is annotated with the unit "), + ED.code(ED.text(self.unit-str)), + ED.text(" but does not support unit annotations.")]] + end end diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index f5019c2614..2d9cd1135a 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2940,19 +2940,19 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom this.isAny = jsnums.checkUnit(this.u, { "_": 1 }) this.implicit = implicit; } - function makeUnitAnn(ann, u, srcLoc) { + function makeUnitAnn(ann, u, srcloc) { if (ann.withUnit === undefined) { - thisRuntime.ffi.throwMessageException("Units!"); + thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(u), makeSrcloc(srcloc)); } return ann.withUnit(u); } - PUnitAnn.prototype.withUnit = function(unit) { + PUnitAnn.prototype.withUnit = function(unit, srcloc) { if (this.implicit) { return new PUnitAnn(this.ann, unit, false); } else if (jsnums.checkUnit(this.u, unit)) { return this; } else { - thisRuntime.ffi.throwMessageException("Units!"); + thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(u), makeSrcloc(srcloc)); } } PUnitAnn.prototype.check = function(compilerLoc, val) { @@ -2984,9 +2984,8 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom newAnn.flat = true; return newAnn; } - PPredAnn.prototype.withUnit = function(unit) { - console.log("Got withUnit on pred") - return new PPredAnn(makeUnitAnn(this.ann, unit), this.pred, this.predName, this.flat) + PPredAnn.prototype.withUnit = function(unit, srcloc) { + return new PPredAnn(makeUnitAnn(this.ann, unit, srcloc), this.pred, this.predName, this.flat) } PPredAnn.prototype.check = function(compilerLoc, val) { function fail() { diff --git a/src/js/trove/ffi.js b/src/js/trove/ffi.js index fb7cf3564d..c7bab87bee 100644 --- a/src/js/trove/ffi.js +++ b/src/js/trove/ffi.js @@ -499,6 +499,10 @@ function throwModuleLoadFailureL(names) { raise(makeModuleLoadFailureL(names)); } + function throwUnitsOnUnsupportedAnn(unitStr, loc) { + runtime.checkString(unitStr); + raise(err("units-on-unsupported-ann")(loc, unitStr)); + } function makeModuleLoadFailureL(names) { var namesList = makeList(names); @@ -657,6 +661,7 @@ throwParseErrorBadNumber: throwParseErrorBadNumber, throwParseErrorBadOper: throwParseErrorBadOper, throwParseErrorBadCheckOper: throwParseErrorBadCheckOper, + throwUnitsOnUnsupportedAnn: throwUnitsOnUnsupportedAnn, makeBadBracketException: makeBadBracketException, makeRecordFieldsFail: makeRecordFieldsFail, diff --git a/t.arr b/t.arr index 5a156d3cd4..5ba0330730 100644 --- a/t.arr +++ b/t.arr @@ -1,6 +1 @@ -type N = Number% - -fun id(n :: N%(num-is-integer)): n end -id(1%) - -print(within(10)(~1/2%, ~1/2%)) +type N = String% From d470bd62c73d87b828fd4d2ae7cf84911575a065 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 14 Jun 2019 15:56:02 -0400 Subject: [PATCH 51/84] deploy From f1ae2b8de6c752466bbee03f3f32e388fcc51fd9 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 14 Jun 2019 18:30:00 -0400 Subject: [PATCH 52/84] Some WIP --- src/js/base/js-numbers.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 2a2a1add72..b2e8e9de4d 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -253,6 +253,11 @@ define("pyret-base/js/js-numbers", function() { (isPyretNumber(n) && n.isRational())); }; + // isUnitnum: pyretnum -> boolean + var isUnitnum = function(n) { + return n instanceof Unitnum; + }; + var isExact = isRational; // isReal: pyretnum -> boolean @@ -4338,6 +4343,7 @@ define("pyret-base/js/js-numbers", function() { Numbers['isPyretNumber'] = isPyretNumber; Numbers['isRational'] = isRational; + Numbers['isUnitnum'] = isUnitnum; Numbers['isReal'] = isReal; Numbers['isExact'] = isExact; Numbers['isInteger'] = isInteger; From 535c6e7e5af5991f6ea51992f713cf4989c38c09 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Sat, 15 Jun 2019 16:38:26 -0400 Subject: [PATCH 53/84] Add a test --- tests/pyret/tests/test-units.arr | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index fb79275b2c..a20a57324d 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -3,6 +3,7 @@ check "Unit equality": 2% == 2% is false 2% is 2% + 2% is 2% 2 is 2% 2% is 2% 2%<(((s * m) ^ 2) / t) ^ 2> is 2% From 33339f60490679041f6f484ca24237a7a7ab99c6 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Sat, 15 Jun 2019 16:42:15 -0400 Subject: [PATCH 54/84] Fix-up render-fancy-reason for error --- src/arr/trove/error.arr | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 5faca55c9b..ddd248c8b3 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2653,7 +2653,14 @@ data ParseError: method render-reason(self): ED.text("missing-comma: " + self.loc.format(true)) end | units-on-unsupported-ann(loc, unit-str) with: method render-fancy-reason(self): - ED.text("Unittsssss (but fancy)") + [ED.error: + [ED.para: + ED.text("The annotation at "), + ED.loc(self.loc), + ED.text(" is annotated with the unit "), + ED.code(ED.text(self.unit-str)), + ED.text(" but does not support unit annotations.")], + ED.cmcode(self.loc)] end, method render-reason(self): [ED.error: From c45a0db4e2a6c57ccde977944156d88bc5795928 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Sat, 15 Jun 2019 17:49:38 -0400 Subject: [PATCH 55/84] Remove wf-check on powers --- src/arr/compiler/well-formed.arr | 7 ------- src/js/base/js-numbers.js | 19 +++++++++++++++++++ tests/pyret/tests/test-well-formed.arr | 4 ---- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index e2a9bfe9ad..2a228c2550 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -297,7 +297,6 @@ fun unit-opname(u :: A.Unit): cases(A.Unit) u: | u-mul(_, _, _, _) => "*" | u-div(_, _, _, _) => "/" - | u-pow(_, _, _, _) => "^" end end fun reachable-ops-unit(self, l, op-l, parent, u): @@ -316,12 +315,6 @@ fun reachable-ops-unit(self, l, op-l, parent, u): else: add-error(C.mixed-unit-ops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) end - | u-pow(l2, op-l2, pow-u, n) => - if A.is-u-pow(self): - reachable-ops-unit(self, l2, op-l2, u, pow-u) - else: - add-error(C.mixed-unit-ops(l, unit-opname(parent), op-l, unit-opname(u), op-l2)) - end | else => u.visit(self) end end diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index b2e8e9de4d..ce79c8e836 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1129,6 +1129,15 @@ define("pyret-base/js/js-numbers", function() { return newUnit; } + var _unitFilter = function(u, f) { + var newUnit = {}; + for (var unitName in u) { + if (!u.hasOwnProperty(unitName) || !f(u[unitName])) continue + newUnit[unitName] = u[unitName]; + } + return newUnit; + } + var _unitEquals = function(u1, u2) { for (var unitName in u1) { if (!u1.hasOwnProperty(unitName)) continue @@ -1163,6 +1172,14 @@ define("pyret-base/js/js-numbers", function() { return _unitMap(u, function(n) { return -1 * n }) } + var _unitNumerator = function(u) { + return _unitFilter(u, function(pow) { return pow > 0 }); + } + + var _unitDenominator = function(u) { + return _unitNumerator(_unitInvert(u)); + } + var _ensureSameUnits = function(u1, u2, errbacks, opName) { var msg; if (opName !== undefined) { @@ -4340,6 +4357,8 @@ define("pyret-base/js/js-numbers", function() { Numbers['getUnit'] = _unitOf; Numbers['checkUnit'] = _unitEquals; Numbers['unitToString'] = _unitToString; + Numbers['unitNumerator'] = _unitNumerator; + Numbers['unitDenominator'] = _unitDenominator; Numbers['isPyretNumber'] = isPyretNumber; Numbers['isRational'] = isRational; diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index 3e53d6db07..16bcc9d949 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -271,16 +271,12 @@ end check "unit annotations": run-str("2%") is%(output) compile-error(CS.is-mixed-unit-ops) - run-str("2%") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("2%") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) - run-str("1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("~1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) - run-str("~1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("~1/2%") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-unit-ops) - run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-mixed-unit-ops) run-str("2%") is%(output) compile-error(CS.is-invalid-unit-power) From ee8026ffe3d0c919e6cf9ffe14b72b706e671638 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Sat, 15 Jun 2019 18:04:48 -0400 Subject: [PATCH 56/84] ??? --- src/arr/trove/error.arr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index ddd248c8b3..7fbf7eadea 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2655,8 +2655,8 @@ data ParseError: method render-fancy-reason(self): [ED.error: [ED.para: - ED.text("The annotation at "), - ED.loc(self.loc), + ED.text("This "), + ED.highlight(ED.text("annotation"), [ED.locs: self.loc], -1), ED.text(" is annotated with the unit "), ED.code(ED.text(self.unit-str)), ED.text(" but does not support unit annotations.")], From bb565665704492920d400a445a9025306ef138de Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 17 Jun 2019 11:30:53 -0400 Subject: [PATCH 57/84] Add support for 1 as a unit --- src/arr/compiler/compile-structs.arr | 28 +++++++++++++++++++++++++- src/arr/compiler/well-formed.arr | 6 +++++- src/js/base/pyret-grammar.bnf | 1 + src/js/base/runtime.js | 2 +- src/js/trove/parse-pyret.js | 12 +++++++---- t.arr | 4 +++- tests/pyret/tests/test-parse.arr | 4 ++++ tests/pyret/tests/test-units.arr | 3 +++ tests/pyret/tests/test-well-formed.arr | 2 ++ 9 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/arr/compiler/compile-structs.arr b/src/arr/compiler/compile-structs.arr index cdf133e351..f854370e2b 100644 --- a/src/arr/compiler/compile-structs.arr +++ b/src/arr/compiler/compile-structs.arr @@ -681,7 +681,7 @@ data CompileError: [ED.para: ED.text("The exponent "), ED.embed(self.power), - ED.text("is not allowed in unit expressions. "), + ED.text(" is not allowed in unit expressions. "), ED.text("Make sure to use a non-zero integer value.")]] end, method render-reason(self): @@ -695,6 +695,32 @@ data CompileError: ED.loc(self.loc), ED.text(". Make sure to use a non-zero integer value.")]] end + | one-as-power-base(loc, power) with: + method render-fancy-reason(self): + [ED.error: + [ED.para: + ED.text("Reading a "), + ED.highlight(ED.text("unit annotation"), [ED.locs: self.loc], 0), + ED.text(" errored:")], + ED.cmcode(self.loc), + [ED.para: + ED.code(ED.text("1")), + ED.text(" is raised to the power "), + ED.embed(self.power), + ED.text(". One cannot be raised to a power")]] + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.code(ED.text("1")), + ED.text(" is raised to the power ")], + [ED.para: + ED.embed(self.power)], + [ED.para: + ED.text("at "), + ED.loc(self.loc), + ED.text(". One cannot be raised to a power")]] + end | mixed-binops(exp-loc, op-a-name, op-a-loc, op-b-name, op-b-loc) with: method render-fancy-reason(self): [ED.error: diff --git a/src/arr/compiler/well-formed.arr b/src/arr/compiler/well-formed.arr index 2a228c2550..a5539dfed9 100644 --- a/src/arr/compiler/well-formed.arr +++ b/src/arr/compiler/well-formed.arr @@ -1044,7 +1044,11 @@ well-formed-visitor = A.default-iter-visitor.{ when (n == 0) or not(num-is-integer(n)): add-error(C.invalid-unit-power(l, n)) end - reachable-ops-unit(self, l, op-l, A.u-pow(l, op-l, u, n), u) + + when A.is-u-one(u): + add-error(C.one-as-power-base(l, n)) + end + u.visit(self) true end, method u-base(self, l :: Loc, id :: A.Name) block: diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index cf91d98ea1..ff98be592a 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -135,6 +135,7 @@ dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) unit-atom: (PARENNOSPACE|PARENSPACE) unit-expr RPAREN | NAME | unit-atom CARET NUMBER + | NUMBER unit-expr: unit-atom ((STAR|SLASH) unit-atom)* diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 2d9cd1135a..7406ae2059 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2952,7 +2952,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } else if (jsnums.checkUnit(this.u, unit)) { return this; } else { - thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(u), makeSrcloc(srcloc)); + thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(unit), makeSrcloc(srcloc)); } } PUnitAnn.prototype.check = function(compilerLoc, val) { diff --git a/src/js/trove/parse-pyret.js b/src/js/trove/parse-pyret.js index 228745666a..77f83da013 100644 --- a/src/js/trove/parse-pyret.js +++ b/src/js/trove/parse-pyret.js @@ -970,20 +970,24 @@ }, 'dim-expr': function(node) { // (LANGLE|LT) unit-expr (RANGLE|GT) | (LANGLE|LT) NUMBER (RANGLE|GT) - // TODO: Handle numbers here somehow return tr(node.kids[1]) }, 'unit-atom': function(node) { - if (node.kids.length === 1) { + if (node.kids.length === 1 && node.kids[0].name === 'NAME') { return RUNTIME.getField(ast, 'u-base') .app(pos(node.pos), name(node.kids[0])) + } else if (node.kids.length === 1 && node.kids[0].name === 'NUMBER') { + if (node.kids[0].value !== '1') { + throw "Invalid number in unit: " + node.kids[0].value; + } + return RUNTIME.getField(ast, 'u-one').app(pos(node.pos)); } else if (node.kids.length === 3 && node.kids[1].name === 'CARET') { return RUNTIME.getField(ast, 'u-pow') - .app(pos(node.pos), pos(node.kids[1].pos), tr(node.kids[0]), number(node.kids[2])) + .app(pos(node.pos), pos(node.kids[1].pos), tr(node.kids[0]), number(node.kids[2])); } else { // (unit-expr PAREN unit-expr PAREN) return RUNTIME.getField(ast, 'u-paren') - .app(pos(node.pos), tr(node.kids[1])) + .app(pos(node.pos), tr(node.kids[1])); } }, 'unit-expr': function(node) { diff --git a/t.arr b/t.arr index 5ba0330730..239e78d58a 100644 --- a/t.arr +++ b/t.arr @@ -1 +1,3 @@ -type N = String% +type N = Number%%(num-is-integer) +fun id(n :: N%): n end +id(1%) diff --git a/tests/pyret/tests/test-parse.arr b/tests/pyret/tests/test-parse.arr index d8e3f5824a..3eadcf9abb 100644 --- a/tests/pyret/tests/test-parse.arr +++ b/tests/pyret/tests/test-parse.arr @@ -478,6 +478,8 @@ check "should parse unit-annotated numbers": does-parse("2%") is true does-parse("2%") is true # should be wf-error does-parse("2%<(m / n) ^ -5 * (o ^ 10)>") is true + does-parse("2%<1>") is true + does-parse("2%<1 * m>") is true does-parse("2%<>") is false does-parse("2%<()>") is false @@ -489,6 +491,8 @@ check "should parse unit-annotated numbers": does-parse("2%") is false does-parse("2%") is false does-parse("2%") is false + does-parse("2%<2>") raises "" + does-parse("2%<-2>") raises "" end check "should parse unit-anns numbers": diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index a20a57324d..5d252325ef 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -3,6 +3,9 @@ check "Unit equality": 2% == 2% is false 2% is 2% + 2%<1 / m> is 2% + 2%<1> is 2 + 2%<1 * m> is 2% 2% is 2% 2 is 2% 2% is 2% diff --git a/tests/pyret/tests/test-well-formed.arr b/tests/pyret/tests/test-well-formed.arr index 16bcc9d949..a6b307ef4a 100644 --- a/tests/pyret/tests/test-well-formed.arr +++ b/tests/pyret/tests/test-well-formed.arr @@ -287,6 +287,8 @@ check "unit annotations": run-str("~1/2%") is%(output) compile-error(CS.is-invalid-unit-power) run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-invalid-unit-power) run-str("var n :: Number% = 0") is%(output) compile-error(CS.is-invalid-unit-power) + + run-str("2%<1 ^ 2>") is%(output) compile-error(CS.is-one-as-power-base) end #| From 32e84d1d22045b8396eaf23c57e3b17cc804fc56 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 17 Jun 2019 11:57:34 -0400 Subject: [PATCH 58/84] sqrt/expt support for unitnums --- src/js/base/js-numbers.js | 25 ++++++++++++++++++++----- src/js/base/runtime.js | 5 +++-- tests/pyret/tests/test-units.arr | 7 ++++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index ce79c8e836..af1f417f7d 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1721,13 +1721,25 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.integerSqrt = function(errbacks) { - // TODO(benmusch): potentially support units here - _throwUnitsUnsupported(this.u, errbacks, "integer square root"); + var newUnit = _unitMap(this.u, function(pow) { + if (pow % 2 !== 0) { + errbacks.throwIncompatibleUnits("`sqrt` on non-even unit exponent"); + } else { + return pow / 2; + } + }); + return _withUnit(integerSqrt(this.n), newUnit, false); }; Unitnum.prototype.sqrt = function(errbacks) { - // TODO(benmusch): potentially support units here - _throwUnitsUnsupported(this.u, errbacks, "square root"); + var newUnit = _unitMap(this.u, function(pow) { + if (pow % 2 !== 0) { + errbacks.throwIncompatibleUnits("`sqrt` on non-even unit exponent"); + } else { + return pow / 2; + } + }); + return _withUnit(sqrt(this.n), newUnit, false); }; Unitnum.prototype.abs = function() { @@ -1766,7 +1778,10 @@ define("pyret-base/js/js-numbers", function() { _throwUnitsUnsupported(this.u, errbacks, "sin"); }; - Unitnum.prototype.expt = function(n) { + Unitnum.prototype.expt = function(n, errbacks) { + if (!isInteger(n)) { + errbacks.throwIncompatibleUnits("`num-expt` on unit with non-integer power"); + } var newUnit = _unitMap(this.u, function(pow) { return n * pow }); return _withUnit(expt(this.n, n), newUnit, false); }; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 7406ae2059..7e7f1b4d3e 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2952,6 +2952,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } else if (jsnums.checkUnit(this.u, unit)) { return this; } else { + // TODO: improve this error message thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(unit), makeSrcloc(srcloc)); } } @@ -5029,7 +5030,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom var num_sqrt = function(n) { if (arguments.length !== 1) { var $a=new Array(arguments.length); for (var $i=0;$i) is 2% num-round-even(3.5%) is 4% num-truncate(3.5%) is 3% + num-sqr(3%) is 9% + num-expt(2%, 3) is 8% + num-expt(2%, -3) is 1/8%<(s / m) ^ 3> + num-sqrt(4%) is 2% + num-sqrt(4%) is 2%<1 / m> end check "Unsupported unops": @@ -130,8 +135,8 @@ check "Unsupported unops": num-tan(2%) raises "" num-cos(2%) raises "" num-exp(2%) raises "" - num-expt(2%, 3) raises "" num-expt(2%, 3%) raises "" + num-expt(2%, 0.5) raises "" num-modulo(2%, 2%) raises "" num-to-string-digits(2/3%, 3%) raises "" num-within-abs(2%) raises "" From f9176792908581b576b213c86c90c15ff52e5f91 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 17 Jun 2019 14:22:08 -0400 Subject: [PATCH 59/84] Add some more unit functions used by the UI --- src/js/base/js-numbers.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index af1f417f7d..63b99d1943 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1120,6 +1120,20 @@ define("pyret-base/js/js-numbers", function() { } }; + var _unitToList = function(u) { + var items = []; + for (var unitName in u) { + if (!u.hasOwnProperty(unitName)) continue + items.push({ name: unitName, pow: u[unitName] }); + } + var comparator = function(a, b) { + if (a.name > b.name) { return 1 } + else if (a.name < b.name) { return -1 } + else { return 0 } + } + return items.sort(comparator); + } + var _unitMap = function(u, f) { var newUnit = {}; for (var unitName in u) { @@ -4374,6 +4388,7 @@ define("pyret-base/js/js-numbers", function() { Numbers['unitToString'] = _unitToString; Numbers['unitNumerator'] = _unitNumerator; Numbers['unitDenominator'] = _unitDenominator; + Numbers['unitToList'] = _unitToList; Numbers['isPyretNumber'] = isPyretNumber; Numbers['isRational'] = isRational; From 021717983c8fcb0a8b2a4df0b18a5446969a45f8 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 17 Jun 2019 18:31:56 -0400 Subject: [PATCH 60/84] Always reset implicit on withUnit() calls --- src/js/base/runtime.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 7e7f1b4d3e..3edfff3b96 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2947,10 +2947,8 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return ann.withUnit(u); } PUnitAnn.prototype.withUnit = function(unit, srcloc) { - if (this.implicit) { + if (this.implicit || jsnums.checkUnit(this.u, unit)) { return new PUnitAnn(this.ann, unit, false); - } else if (jsnums.checkUnit(this.u, unit)) { - return this; } else { // TODO: improve this error message thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(unit), makeSrcloc(srcloc)); From 0ea223dd972f8e0f0c1e41342c1c7c41daba2062 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 00:20:31 -0400 Subject: [PATCH 61/84] Add test for un-implicit-ing a <1> annot --- tests/pyret/tests/test-contracts.arr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/pyret/tests/test-contracts.arr b/tests/pyret/tests/test-contracts.arr index 95b91fa8ea..da8cbe5f9c 100644 --- a/tests/pyret/tests/test-contracts.arr +++ b/tests/pyret/tests/test-contracts.arr @@ -506,6 +506,12 @@ check "Should notice unit mis-matches": fun id(n :: { a :: Number%, b :: Number%}): n end id({ a: 1%, b: 1%}) ```) is%(output) contract-error + run-str( + ``` + type MyAny = Any%<1> + fun id(n :: MyAny%): n end + id(1%) + ```) is%(output) contract-error run-str( ``` From 7761c60590ff7d3e9f18e8135ecc0741d38f0465 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 13:08:41 -0400 Subject: [PATCH 62/84] Pyret error for incompatible units --- src/arr/trove/error.arr | 45 ++++++++++++++++++-------------- src/js/base/js-numbers.js | 32 ++++++++++------------- src/js/base/runtime.js | 2 +- src/js/trove/ffi.js | 9 ++++++- t.arr | 4 +-- tests/pyret/tests/test-units.arr | 14 +++++----- 6 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 7fbf7eadea..1449e3f39d 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2429,6 +2429,31 @@ data RuntimeError: method render-reason(self): ED.text("") end + | units-on-unsupported-ann(loc, unit-str) with: + method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): + self.render-reason() + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.text("The annotation at "), + ED.loc(self.loc), + ED.text(" is annotated with the unit "), + ED.code(ED.text(self.unit-str)), + ED.text(" but does not support unit annotations.")]] + end + | incompatible-units(op-name, l, r) with: + method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): + self.render-reason() + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.text("The " + self.op-name + " operation failed due to incompatible units. "), + ED.text(tostring(self.l)), + ED.text(" is not compatible with "), + ED.text(tostring(self.r))]] + end end data ParseError: @@ -2651,24 +2676,4 @@ data ParseError: method render-reason(self): ED.text("missing-end: " + self.loc.format(true)) end | missing-comma(loc) with: method render-reason(self): ED.text("missing-comma: " + self.loc.format(true)) end - | units-on-unsupported-ann(loc, unit-str) with: - method render-fancy-reason(self): - [ED.error: - [ED.para: - ED.text("This "), - ED.highlight(ED.text("annotation"), [ED.locs: self.loc], -1), - ED.text(" is annotated with the unit "), - ED.code(ED.text(self.unit-str)), - ED.text(" but does not support unit annotations.")], - ED.cmcode(self.loc)] - end, - method render-reason(self): - [ED.error: - [ED.para: - ED.text("The annotation at "), - ED.loc(self.loc), - ED.text(" is annotated with the unit "), - ED.code(ED.text(self.unit-str)), - ED.text(" but does not support unit annotations.")]] - end end diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 63b99d1943..d3b15ec296 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -652,7 +652,7 @@ define("pyret-base/js/js-numbers", function() { }, function(x, y, errbacks) { if (!_unitEquals(_unitOf(y), {})) { - errbacks.throwIncompatibleUnits("expt: power cannot have a unit") + errbacks.throwInternalError("expt: power cannot have a unit") } return x.expt(_withoutUnit(y), errbacks); }, @@ -663,7 +663,7 @@ define("pyret-base/js/js-numbers", function() { onXSpecialCase: function(x, y, errbacks) { if (!_unitEquals(_unitOf(y), {})) { // need this because the isXSpecialCase check cannot look at the y - errbacks.throwIncompatibleUnits("expt: power cannot have a unit") + errbacks.throwInternalError("expt: power cannot have a unit") } if (eqv(x, 0, errbacks)) { @@ -1194,23 +1194,18 @@ define("pyret-base/js/js-numbers", function() { return _unitNumerator(_unitInvert(u)); } - var _ensureSameUnits = function(u1, u2, errbacks, opName) { - var msg; - if (opName !== undefined) { - msg = "Cannot perform " + opName + " operation due to unit mis-match: "; - } else { - msg = "Cannot perform operation due to unit mis-match: "; - } - msg += (_unitToString(u1) + " and " + _unitToString(u2)); + var _ensureSameUnits = function(n1, n2, errbacks, opName) { + var u1 = _unitOf(n1); + var u2 = _unitOf(n2); if (!_unitEquals(u1, u2)) { - errbacks.throwIncompatibleUnits(msg); + errbacks.throwIncompatibleUnits(opName, n1, n2); } } var _throwUnitsUnsupported = function(u, errbacks, opName) { // TODO(benmusch): Use another errback function - errbacks.throwIncompatibleUnits("The " + opName + " operation does not support units" + + errbacks.throwInternalError("The " + opName + " operation does not support units" + " but was given an argument with the unit " + _unitToString(u)) } @@ -1231,6 +1226,7 @@ define("pyret-base/js/js-numbers", function() { return (function(m, n) { if (m instanceof Rational || m instanceof Unitnum) { // TODO(benmusch): Is this okay? Can we always use fixnums? + // TODO(benmusch): Consider lifting both to unitnums m = m.toFixnum(); } @@ -1687,32 +1683,32 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.greaterThan = function(n, errbacks) { - _ensureSameUnits(this.u, _unitOf(n), errbacks, ">"); + _ensureSameUnits(this, n, errbacks, ">"); return greaterThan(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.greaterThanOrEqual = function(n, errbacks) { - _ensureSameUnits(this.u, _unitOf(n), errbacks, ">="); + _ensureSameUnits(this, n, errbacks, ">="); return greaterThanOrEqual(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.lessThan = function(n, errbacks) { - _ensureSameUnits(this.u, _unitOf(n), errbacks, "<"); + _ensureSameUnits(this, n, errbacks, "<"); return lessThan(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.lessThanOrEqual = function(n, errbacks) { - _ensureSameUnits(this.u, _unitOf(n), errbacks, "<="); + _ensureSameUnits(this, n, errbacks, "<="); return lessThanOrEqual(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.add = function(n, errbacks) { - _ensureSameUnits(this.u, _unitOf(n), errbacks, "+"); + _ensureSameUnits(this, n, errbacks, "+"); return _withUnit(add(_withoutUnit(this), _withoutUnit(n), errbacks), this.u, false); }; Unitnum.prototype.subtract = function(n, errbacks) { - _ensureSameUnits(this.u, _unitOf(n), errbacks, "-"); + _ensureSameUnits(this, n, errbacks, "-"); return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n), errbacks), this.u, false); }; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 3edfff3b96..b064e7e8bf 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -3907,7 +3907,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throwSqrtNegative: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwLogNonPositive: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwIncomparableValues: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, - throwIncompatibleUnits: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, + throwIncompatibleUnits: function(op, l, r) { thisRuntime.ffi.throwIncompatibleUnits(op, l, r); }, throwInternalError: function(msg) { thisRuntime.ffi.throwInternalError(msg); }, }; diff --git a/src/js/trove/ffi.js b/src/js/trove/ffi.js index c7bab87bee..8d913f111a 100644 --- a/src/js/trove/ffi.js +++ b/src/js/trove/ffi.js @@ -503,6 +503,12 @@ runtime.checkString(unitStr); raise(err("units-on-unsupported-ann")(loc, unitStr)); } + function throwIncompatibleUnits(opName, l, r) { + runtime.checkString(opName); + runtime.checkNumber(l); + runtime.checkNumber(r); + raise(err("incompatible-units")(opName, l, r)); + } function makeModuleLoadFailureL(names) { var namesList = makeList(names); @@ -653,6 +659,8 @@ throwDuplicateColumn: throwDuplicateColumn, throwUnfinishedTemplate: throwUnfinishedTemplate, throwModuleLoadFailureL: throwModuleLoadFailureL, + throwUnitsOnUnsupportedAnn: throwUnitsOnUnsupportedAnn, + throwIncompatibleUnits: throwIncompatibleUnits, throwParseErrorNextToken: throwParseErrorNextToken, throwParseErrorColonColon: throwParseErrorColonColon, @@ -661,7 +669,6 @@ throwParseErrorBadNumber: throwParseErrorBadNumber, throwParseErrorBadOper: throwParseErrorBadOper, throwParseErrorBadCheckOper: throwParseErrorBadCheckOper, - throwUnitsOnUnsupportedAnn: throwUnitsOnUnsupportedAnn, makeBadBracketException: makeBadBracketException, makeRecordFieldsFail: makeRecordFieldsFail, diff --git a/t.arr b/t.arr index 239e78d58a..af3780a1af 100644 --- a/t.arr +++ b/t.arr @@ -1,3 +1 @@ -type N = Number%%(num-is-integer) -fun id(n :: N%): n end -id(1%) +1% + 1% diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 8ab5066b0b..132734e9a3 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -1,3 +1,5 @@ +import error as E + check "Unit equality": 2 == 2% is false 2% == 2% is false @@ -23,10 +25,10 @@ end check "Arithmetic binops": 2% + 2% is 4% - (2% + 2%) raises "Cannot perform + operation due to unit mis-match: m and s" + (2% + 2%) raises-satisfies E.is-incompatible-units 2% - 2% is 0% - (2% - 2%) raises "Cannot perform - operation due to unit mis-match: m and s" + (2% - 2%) raises-satisfies E.is-incompatible-units 2% * 2% is 4% 2% * 2% is 4 @@ -41,19 +43,19 @@ end check "Logical binops": 2% > 1% is true 2% > 2% is false - 2% > 2% raises "Cannot perform > operation due to unit mis-match: m and s" + 2% > 2% raises-satisfies E.is-incompatible-units 2% >= 2% is true 2% >= 3% is false - 2% >= 2% raises "Cannot perform >= operation due to unit mis-match: m and s" + 2% >= 2% raises-satisfies E.is-incompatible-units 1% < 2% is true 2% < 2% is false - 2% < 2% raises "Cannot perform < operation due to unit mis-match: m and s" + 2% < 2% raises-satisfies E.is-incompatible-units 2% <= 2% is true 2% <= 1% is false - 2% <= 2% raises "Cannot perform <= operation due to unit mis-match: m and s" + 2% <= 2% raises-satisfies E.is-incompatible-units end check "Other supported operations": From b833d0c60472a93d0e50873dcd0dd05e83d02124 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 14:19:07 -0400 Subject: [PATCH 63/84] More pyret errors + better contract failures --- src/arr/trove/contracts.arr | 25 +++++++++++++++++++++++++ src/arr/trove/error.arr | 12 ++++++++++++ src/js/base/js-numbers.js | 15 +++++++++------ src/js/base/runtime.js | 11 ++++++++--- src/js/trove/ffi.js | 17 +++++++++++++++++ t.arr | 6 +++++- tests/pyret/tests/test-units.arr | 8 +++----- 7 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/arr/trove/contracts.arr b/src/arr/trove/contracts.arr index bcd5c1d15e..1d93a8da48 100644 --- a/src/arr/trove/contracts.arr +++ b/src/arr/trove/contracts.arr @@ -294,6 +294,31 @@ data FailureReason: [ED.error: message, ED.embed(self.val)] end end + | unit-fail(val, name, expected, actual, is-implicit) with: + method render-fancy-reason(self, loc, from-fail-arg, maybe-stack-loc, src-available, maybe-ast): + self.render-reason(loc, from-fail-arg) + end, + method render-reason(self, loc, from-fail-arg): + message = [ED.para: + ED.text("The predicate"), ED.code(ED.text(self.name)), + ED.text("in the annotation at"), draw-and-highlight(loc), + ED.text("returned false for this value:"), ED.embed(self.val)] + if self.is-implicit and not(loc.is-builtin()): + [ED.error: + message, + [ED.para: + ED.text("The unit"), ED.code(ED.text(self.actual)), + ED.text("was expected to match"), ED.code(ED.text(self.expected)), + ED.text("due to the default of the"), ED.code(ED.text(self.name)), + ED.text("annotation. Did you mean to specify another unit or the any unit (%<_>)?")]] + else: + [ED.error: + message, + [ED.para: + ED.text("The unit"), ED.code(ED.text(self.actual)), + ED.text("was expected to match"), ED.code(ED.text(self.expected))]] + end + end | record-fields-fail(val, field-failures :: List) with: method render-fancy-reason(self, loc, from-fail-arg, maybe-stack-loc, src-available, maybe-ast): [ED.error: diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 1449e3f39d..a965e981ef 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2454,6 +2454,18 @@ data RuntimeError: ED.text(" is not compatible with "), ED.text(tostring(self.r))]] end + | invalid-unit-state(op-name, n, desc) with: + method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): + self.render-reason() + end, + method render-reason(self): + [ED.error: + [ED.para: + ED.text("The " + self.op-name + " operation failed. "), + ED.text(tostring(self.n)), + ED.text(" is an invalid argument because: "), + ED.text(self.desc)]] + end end data ParseError: diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index d3b15ec296..c465295861 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -652,7 +652,7 @@ define("pyret-base/js/js-numbers", function() { }, function(x, y, errbacks) { if (!_unitEquals(_unitOf(y), {})) { - errbacks.throwInternalError("expt: power cannot have a unit") + errbacks.throwInvalidUnitState("num-expt", y, "power cannot have a unit") } return x.expt(_withoutUnit(y), errbacks); }, @@ -663,7 +663,7 @@ define("pyret-base/js/js-numbers", function() { onXSpecialCase: function(x, y, errbacks) { if (!_unitEquals(_unitOf(y), {})) { // need this because the isXSpecialCase check cannot look at the y - errbacks.throwInternalError("expt: power cannot have a unit") + errbacks.throwInvalidUnitState("num-expt", y, "power cannot have a unit") } if (eqv(x, 0, errbacks)) { @@ -1205,7 +1205,7 @@ define("pyret-base/js/js-numbers", function() { var _throwUnitsUnsupported = function(u, errbacks, opName) { // TODO(benmusch): Use another errback function - errbacks.throwInternalError("The " + opName + " operation does not support units" + + errbacks.throwGeneralError("The " + opName + " operation does not support units" + " but was given an argument with the unit " + _unitToString(u)) } @@ -1731,9 +1731,10 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.integerSqrt = function(errbacks) { + var that = this; var newUnit = _unitMap(this.u, function(pow) { if (pow % 2 !== 0) { - errbacks.throwIncompatibleUnits("`sqrt` on non-even unit exponent"); + errbacks.throwInvalidUnitState("num-sqrt", that, "not all units had an even exponent"); } else { return pow / 2; } @@ -1742,9 +1743,10 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.sqrt = function(errbacks) { + var that = this; var newUnit = _unitMap(this.u, function(pow) { if (pow % 2 !== 0) { - errbacks.throwIncompatibleUnits("`sqrt` on non-even unit exponent"); + errbacks.throwInvalidUnitState("num-sqrt", that, "not all units had an even exponent"); } else { return pow / 2; } @@ -1790,7 +1792,7 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.expt = function(n, errbacks) { if (!isInteger(n)) { - errbacks.throwIncompatibleUnits("`num-expt` on unit with non-integer power"); + errbacks.throwInvalidUnitState("num-expt", n, "a number with a unit cannot be raised to a non-integer power"); } var newUnit = _unitMap(this.u, function(pow) { return n * pow }); return _withUnit(expt(this.n, n), newUnit, false); @@ -4381,6 +4383,7 @@ define("pyret-base/js/js-numbers", function() { Numbers['addUnit'] = addUnit; Numbers['getUnit'] = _unitOf; Numbers['checkUnit'] = _unitEquals; + Numbers['ensureSameUnits'] = _ensureSameUnits; Numbers['unitToString'] = _unitToString; Numbers['unitNumerator'] = _unitNumerator; Numbers['unitDenominator'] = _unitDenominator; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index b064e7e8bf..9257fdb95c 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2955,11 +2955,13 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } } PUnitAnn.prototype.check = function(compilerLoc, val) { - if (!this.isAny && !jsnums.checkUnit(jsnums.getUnit(val), this.u)) { - var name = "<" + jsnums.unitToString(this.u) + ">"; + var u = jsnums.getUnit(val); + if (!this.isAny && !jsnums.checkUnit(u, this.u)) { + var expectedUnit = jsnums.unitToString(this.u); + var actualUnit = jsnums.unitToString(u); return thisRuntime.ffi.contractFail( makeSrcloc(compilerLoc), - thisRuntime.ffi.makePredicateFailure(val, name)); + thisRuntime.ffi.makeUnitFailure(val, this.ann.name || "", expectedUnit, actualUnit, this.implicit)); } else { return this.ann.check(compilerLoc, val); } @@ -3908,6 +3910,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throwLogNonPositive: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwIncomparableValues: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwIncompatibleUnits: function(op, l, r) { thisRuntime.ffi.throwIncompatibleUnits(op, l, r); }, + throwInvalidUnitState: function(op, n, desc) { thisRuntime.ffi.throwInvalidUnitState(op, n, desc) }, throwInternalError: function(msg) { thisRuntime.ffi.throwInternalError(msg); }, }; @@ -4938,6 +4941,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if (arguments.length !== 2) { var $a=new Array(arguments.length); for (var $i=0;$i + 1% +type N = Number%(num-is-integer) +fun f(n :: N): + n +end +f(2%) diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 132734e9a3..1c16b36d61 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -101,16 +101,14 @@ check "Other supported operations": num-to-string-digits(2/3%, 3) is "0.667" num-to-string-digits(-2/3%, 3) is "-0.667" - # TODO: Failing... hmmm.... num-to-string-digits(5%, 2) is "5.00" - num-to-string-digits(5%, 0) is "5" num-to-string-digits(555%, -2) is "600" num-max(2%, 1%) is 2% - num-max(2%, 2%) raises "" + num-max(2%, 2%) raises-satisfies E.is-incompatible-units num-min(2%, 1%) is 1% - num-min(2%, 2%) raises "" + num-min(2%, 2%) raises-satisfies E.is-incompatible-units num-abs(-2%) is 2% num-floor(3/2%) is 1% @@ -127,7 +125,7 @@ check "Other supported operations": end check "Unsupported unops": - num-sqrt(2%) raises "" + num-sqrt(2%) raises-satisfies E.is-invalid-unit-state num-log(2%) raises "" num-atan(2%) raises "" num-atan2(2%) raises "" From 9cb5c1c92d4ef506a628fadc221aaa80c2007f09 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 14:55:23 -0400 Subject: [PATCH 64/84] Better tests for unit errors --- src/js/base/runtime.js | 1 - tests/pyret/tests/test-units.arr | 37 +++++++++++++++++++------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 9257fdb95c..be6c49ca7a 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2950,7 +2950,6 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if (this.implicit || jsnums.checkUnit(this.u, unit)) { return new PUnitAnn(this.ann, unit, false); } else { - // TODO: improve this error message thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(unit), makeSrcloc(srcloc)); } } diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 1c16b36d61..bf0c7b720a 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -1,4 +1,5 @@ import error as E +import contracts as C check "Unit equality": 2 == 2% is false @@ -124,21 +125,27 @@ check "Other supported operations": num-sqrt(4%) is 2%<1 / m> end +fun is-unit-contract-fail(result): + C.is-fail(result) and C.is-failure-at-arg(result.reason) and C.is-unit-fail(result.reason.reason) +end + check "Unsupported unops": num-sqrt(2%) raises-satisfies E.is-invalid-unit-state - num-log(2%) raises "" - num-atan(2%) raises "" - num-atan2(2%) raises "" - num-asin(0%) raises "" - num-acos(0%) raises "" - num-sin(2%) raises "" - num-tan(2%) raises "" - num-cos(2%) raises "" - num-exp(2%) raises "" - num-expt(2%, 3%) raises "" - num-expt(2%, 0.5) raises "" - num-modulo(2%, 2%) raises "" - num-to-string-digits(2/3%, 3%) raises "" - num-within-abs(2%) raises "" - num-within-rel(2%) raises "" + num-expt(2%, 0.5) raises-satisfies E.is-invalid-unit-state + + num-log(2%) raises-satisfies is-unit-contract-fail + num-atan(2%) raises-satisfies is-unit-contract-fail + num-atan2(2%, 2) raises-satisfies is-unit-contract-fail + num-atan2(2, 2%) raises-satisfies is-unit-contract-fail + num-asin(0%) raises-satisfies is-unit-contract-fail + num-acos(0%) raises-satisfies is-unit-contract-fail + num-sin(2%) raises-satisfies is-unit-contract-fail + num-tan(2%) raises-satisfies is-unit-contract-fail + num-cos(2%) raises-satisfies is-unit-contract-fail + num-exp(2%) raises-satisfies is-unit-contract-fail + num-expt(2%, 3%) raises-satisfies is-unit-contract-fail + num-modulo(2%, 2%) raises-satisfies is-unit-contract-fail + num-to-string-digits(2/3%, 3%) raises-satisfies is-unit-contract-fail + num-within-abs(2%) raises-satisfies is-unit-contract-fail + num-within-rel(2%) raises-satisfies is-unit-contract-fail end From 1f404273448ec5610e6cff8a6ab8c9406edc74fb Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 18:24:02 -0400 Subject: [PATCH 65/84] Dont support polymorphic 0, improve jsnums performance on units --- pitometer/programs/anf-loop-compiler.arr | 1 - src/arr/compiler/anf-loop-compiler.arr | 30 ++++- src/js/base/js-numbers.js | 163 ++++++++++++++--------- src/js/base/runtime.js | 40 +++--- src/js/trove/parse-pyret.js | 18 +-- src/js/trove/s-exp.js | 2 +- t.arr | 7 +- tests/pyret/tests/test-units.arr | 4 +- 8 files changed, 158 insertions(+), 107 deletions(-) diff --git a/pitometer/programs/anf-loop-compiler.arr b/pitometer/programs/anf-loop-compiler.arr index 72e6997012..e8d0b8959b 100644 --- a/pitometer/programs/anf-loop-compiler.arr +++ b/pitometer/programs/anf-loop-compiler.arr @@ -1193,7 +1193,6 @@ compiler-visitor = { if num-is-fixnum(n): c-exp(j-parens(j-num(n)), cl-empty) else: - c-exp(rt-method("makeNumberFromString", [clist: j-str(tostring(n))]), cl-empty) end end, method a-str(self, l :: Loc, s :: String): diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index 740ed9f706..bf0214f71e 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -333,7 +333,7 @@ end fun normalize-unit-help(u :: A.Unit, factor :: NumInteger, acc :: D.MutableStringDict) -> D.MutableStringDict: cases (A.Unit) u block: | u-one(_) => acc - | u-base(_, id) => + | u-base(l, id) => acc.set-now(tostring(id), acc.get-now(tostring(id)).or-else(0) + factor) acc | u-mul(_, _, lhs, rhs) => normalize-unit-help(lhs, factor, normalize-unit-help(rhs, factor, acc)) @@ -346,7 +346,7 @@ fun normalize-unit(u :: A.Unit) -> D.StringDict: normalize-unit-help(u, 1, [mutable-string-dict: ]).freeze() end -fun compile-unit(u :: A.Unit) -> J.JExpr: +fun compile-unit-help(u :: A.Unit) -> J.JExpr: normalized = normalize-unit(u) fields = normalized.fold-keys( lam(key, acc): @@ -358,7 +358,23 @@ fun compile-unit(u :: A.Unit) -> J.JExpr: end end, CL.concat-empty) - j-obj(fields) + + if fields.length() == 0: + rt-field("UNIT_ONE") + else: + j-obj(CL.concat-cons(j-field("$count", j-num(fields.length())), fields)) + end +end +fun compile-unit(u :: A.Unit) -> J.JExpr: + cases (A.Unit) u block: + | u-base(l, id) => + if A.is-s-underscore(id): + rt-field("UNIT_ANY") + else: + compile-unit-help(u) + end + | else => compile-unit-help(u) + end end fun compile-ann(ann :: A.Ann, visitor) -> DAG.CaseResults%(is-c-exp): @@ -1708,8 +1724,12 @@ compiler-visitor = { if num-is-fixnum(n) and A.is-u-one(u): c-exp(j-parens(j-num(n)), cl-empty) else: - args = [clist: j-str(tostring(n)), compile-unit(u)] - c-exp(rt-method("makeNumberFromString", args), cl-empty) + make-num-call = rt-method("makeNumberFromString", [clist: j-str(tostring(n))]) + if A.is-u-one(u): + c-exp(make-num-call, cl-empty) + else: + c-exp(rt-method("addUnit", [clist: make-num-call, compile-unit(u)]), cl-empty) + end end end, method a-str(self, l :: Loc, s :: String): diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index c465295861..fa359f1bd6 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -108,6 +108,8 @@ define("pyret-base/js/js-numbers", function() { 'use strict'; // Abbreviation var Numbers = {}; + var UNIT_ONE = { "$name": "ONE" }; + var UNIT_ANY = { "$name": "ANY" }; // makeNumericBinop: (fixnum fixnum -> any) (pyretnum pyretnum -> any) -> (pyretnum pyretnum) X // Creates a binary function that works either on fixnums or boxnums. @@ -135,8 +137,8 @@ define("pyret-base/js/js-numbers", function() { if (x instanceof Unitnum || y instanceof Unitnum) { // if x or y have units, ensure they are both wrapped in unitnums - x = _withUnit(x, _unitOf(x), true); - y = _withUnit(y, _unitOf(y), true); + x = _withUnit(x, getUnit(x), true); + y = _withUnit(y, getUnit(y), true); } else if (x instanceof Roughnum) { // y is rough, rat or bigint if (!(y instanceof Roughnum)) { @@ -369,10 +371,16 @@ define("pyret-base/js/js-numbers", function() { }, {isXSpecialCase: function(x, errbacks) { return isInteger(x) && _integerIsZero(x) }, - onXSpecialCase: function(x, y, errbacks) { return y; }, + onXSpecialCase: function(x, y, errbacks) { + ensureSameUnits(x, y, errbacks, "+"); + return y; + }, isYSpecialCase: function(y, errbacks) { return isInteger(y) && _integerIsZero(y) }, - onYSpecialCase: function(x, y, errbacks) { return x; } + onYSpecialCase: function(x, y, errbacks) { + ensureSameUnits(x, y, errbacks, "+"); + return x; + } }); var subtract = function(x, y, errbacks) { @@ -402,10 +410,16 @@ define("pyret-base/js/js-numbers", function() { }, {isXSpecialCase: function(x, errbacks) { return isInteger(x) && _integerIsZero(x) }, - onXSpecialCase: function(x, y, errbacks) { return negate(y, errbacks); }, + onXSpecialCase: function(x, y, errbacks) { + ensureSameUnits(x, y, errbacks, "-"); + return negate(y, errbacks); + }, isYSpecialCase: function(y, errbacks) { return isInteger(y) && _integerIsZero(y) }, - onYSpecialCase: function(x, y, errbacks) { return x; } + onYSpecialCase: function(x, y, errbacks) { + ensureSameUnits(x, y, errbacks, "-"); + return x; + } }); // mulitply: pyretnum pyretnum -> pyretnum @@ -651,7 +665,7 @@ define("pyret-base/js/js-numbers", function() { } }, function(x, y, errbacks) { - if (!_unitEquals(_unitOf(y), {})) { + if (!checkUnit(getUnit(y), UNIT_ONE)) { errbacks.throwInvalidUnitState("num-expt", y, "power cannot have a unit") } return x.expt(_withoutUnit(y), errbacks); @@ -661,7 +675,7 @@ define("pyret-base/js/js-numbers", function() { return (eqv(x, 0, errbacks) || eqv(x, 1, errbacks)); }, onXSpecialCase: function(x, y, errbacks) { - if (!_unitEquals(_unitOf(y), {})) { + if (!checkUnit(getUnit(y), UNIT_ONE)) { // need this because the isXSpecialCase check cannot look at the y errbacks.throwInvalidUnitState("num-expt", y, "power cannot have a unit") } @@ -1065,11 +1079,12 @@ define("pyret-base/js/js-numbers", function() { // Unit operations + var COUNT_FIELD = "$count"; + var _withUnit = function(n, u, forceUnitnum) { - if (_unitEquals(u, {}) && !forceUnitnum) { + if (checkUnit(u, UNIT_ONE) && !forceUnitnum) { return _withoutUnit(n); } else if (n instanceof Unitnum) { - // TODO: make sure this is okay in all instances return _withUnit(n.n, u, forceUnitnum); } else { return new Unitnum(n, u); @@ -1084,10 +1099,13 @@ define("pyret-base/js/js-numbers", function() { } } - var _unitToString = function(u) { + var unitToString = function(u) { + if (u === UNIT_ONE) return "1"; + if (u === UNIT_ANY) return "_"; + var unitStrs = []; for (var unitName in u) { - if (!u.hasOwnProperty(unitName)) continue + if (!u.hasOwnProperty(unitName) || unitName === COUNT_FIELD) continue var power = u[unitName]; if (power === 1) { @@ -1096,22 +1114,21 @@ define("pyret-base/js/js-numbers", function() { unitStrs = unitStrs.concat(unitName + " ^ " + power) } } - - if (unitStrs.length === 0) { - return "1"; - } else { - return unitStrs.sort().join(" * "); - } + return unitStrs.sort().join(" * "); }; - var _unitOf = function(n) { + var getUnit = function(n) { if (n instanceof Unitnum) { return n.u; } else { - return {}; + return UNIT_ONE; } }; + var addUnit = function(n, u) { + return _withUnit(n, u, false); + }; + var _unitExponentOf = function(u, key) { if (u.hasOwnProperty(key)) { return u[key]; @@ -1120,12 +1137,16 @@ define("pyret-base/js/js-numbers", function() { } }; - var _unitToList = function(u) { + // represent the unit as a list of objects, used by CPO + var unitToList = function(u) { + if (u === UNIT_ONE || u === UNIT_ANY) return []; + var items = []; for (var unitName in u) { - if (!u.hasOwnProperty(unitName)) continue + if (!u.hasOwnProperty(unitName) || unitName === COUNT_FIELD) continue items.push({ name: unitName, pow: u[unitName] }); } + var comparator = function(a, b) { if (a.name > b.name) { return 1 } else if (a.name < b.name) { return -1 } @@ -1135,24 +1156,34 @@ define("pyret-base/js/js-numbers", function() { } var _unitMap = function(u, f) { + if (u === UNIT_ONE || u === UNIT_ANY) return u; + var newUnit = {}; + newUnit[COUNT_FIELD] = u[COUNT_FIELD] for (var unitName in u) { - if (!u.hasOwnProperty(unitName)) continue + if (!u.hasOwnProperty(unitName) || unitName === COUNT_FIELD) continue newUnit[unitName] = f(u[unitName]); } return newUnit; } var _unitFilter = function(u, f) { + if (u === UNIT_ONE || u === UNIT_ANY) return u; + var newUnit = {}; + newUnit[COUNT_FIELD] = 0; for (var unitName in u) { - if (!u.hasOwnProperty(unitName) || !f(u[unitName])) continue + if (!u.hasOwnProperty(unitName) || !f(u[unitName]) || unitName === COUNT_FIELD) continue newUnit[unitName] = u[unitName]; + newUnit[COUNT_FIELD] += 1; } return newUnit; } - var _unitEquals = function(u1, u2) { + var checkUnit = function(u1, u2) { + if (u1 === UNIT_ANY || u2 === UNIT_ANY) return true; + if (u1 === UNIT_ONE || u2 === UNIT_ONE) return u1 === u2; + for (var unitName in u1) { if (!u1.hasOwnProperty(unitName)) continue @@ -1160,53 +1191,57 @@ define("pyret-base/js/js-numbers", function() { return false; } } - - // TODO(benmusch): speed this up by avoiding duplicate checks - for (var unitName in u2) { - if (!u2.hasOwnProperty(unitName)) continue - - if (_unitExponentOf(u1, unitName) !== _unitExponentOf(u2, unitName)) { - return false; - } - } return true; }; var _unitMerge = function(u1, u2) { + if (u1 === UNIT_ONE) return u2; + if (u2 === UNIT_ONE) return u1; + var newUnit = {}; for (var unitName in Object.assign({}, u1, u2)) { if (!u1.hasOwnProperty(unitName) && !u2.hasOwnProperty(unitName)) continue + if (unitName === COUNT_FIELD) continue; - newUnit[unitName] = _unitExponentOf(u1, unitName) + _unitExponentOf(u2, unitName); + var newExp = _unitExponentOf(u1, unitName) + _unitExponentOf(u2, unitName); + if (newExp !== 0) { + newUnit[unitName] = newExp; + } + } + + var numKeys = Object.keys(newUnit).length + if (numKeys !== 0) { + newUnit[COUNT_FIELD] = Object.keys(newUnit).length + return newUnit; + } else { + return UNIT_ONE; } - return newUnit; } var _unitInvert = function(u) { return _unitMap(u, function(n) { return -1 * n }) } - var _unitNumerator = function(u) { + var unitNumerator = function(u) { return _unitFilter(u, function(pow) { return pow > 0 }); } - var _unitDenominator = function(u) { - return _unitNumerator(_unitInvert(u)); + var unitDenominator = function(u) { + return unitNumerator(_unitInvert(u)); } - var _ensureSameUnits = function(n1, n2, errbacks, opName) { - var u1 = _unitOf(n1); - var u2 = _unitOf(n2); + var ensureSameUnits = function(n1, n2, errbacks, opName) { + var u1 = getUnit(n1); + var u2 = getUnit(n2); - if (!_unitEquals(u1, u2)) { + if (!checkUnit(u1, u2)) { errbacks.throwIncompatibleUnits(opName, n1, n2); } } var _throwUnitsUnsupported = function(u, errbacks, opName) { - // TODO(benmusch): Use another errback function errbacks.throwGeneralError("The " + opName + " operation does not support units" + - " but was given an argument with the unit " + _unitToString(u)) + " but was given an argument with the unit " + unitToString(u)) } ////////////////////////////////////////////////////////////////////// @@ -1626,7 +1661,7 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.toString = function() { - var unitStr = "<" + _unitToString(this.u) + ">" + var unitStr = "<" + unitToString(this.u) + ">" if (typeof(this.n) === "number") { return this.n.toString(10) + unitStr; } else { @@ -1683,32 +1718,32 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.greaterThan = function(n, errbacks) { - _ensureSameUnits(this, n, errbacks, ">"); + ensureSameUnits(this, n, errbacks, ">"); return greaterThan(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.greaterThanOrEqual = function(n, errbacks) { - _ensureSameUnits(this, n, errbacks, ">="); + ensureSameUnits(this, n, errbacks, ">="); return greaterThanOrEqual(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.lessThan = function(n, errbacks) { - _ensureSameUnits(this, n, errbacks, "<"); + ensureSameUnits(this, n, errbacks, "<"); return lessThan(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.lessThanOrEqual = function(n, errbacks) { - _ensureSameUnits(this, n, errbacks, "<="); + ensureSameUnits(this, n, errbacks, "<="); return lessThanOrEqual(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.add = function(n, errbacks) { - _ensureSameUnits(this, n, errbacks, "+"); + ensureSameUnits(this, n, errbacks, "+"); return _withUnit(add(_withoutUnit(this), _withoutUnit(n), errbacks), this.u, false); }; Unitnum.prototype.subtract = function(n, errbacks) { - _ensureSameUnits(this, n, errbacks, "-"); + ensureSameUnits(this, n, errbacks, "-"); return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n), errbacks), this.u, false); }; @@ -1823,7 +1858,7 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.equals = function(other) { // TODO(benmusch): should this support a polymorphic zero which ignores // units? - return _unitEquals(this.u, _unitOf(other)) && + return checkUnit(this.u, getUnit(other)) && equals(this.n, other.n); }; @@ -2506,10 +2541,6 @@ define("pyret-base/js/js-numbers", function() { }; - var addUnit = function(n, u) { - return _withUnit(n, u, false); - }; - /////////////////////////////////////////////////////////// // recognizing numbers in (We)Scheme syntax: @@ -4347,7 +4378,7 @@ define("pyret-base/js/js-numbers", function() { } if (n instanceof Unitnum) { - return toStringDigits(n.n, digits, errbacks) + "<" + _unitToString(n.u) + ">"; + return toStringDigits(n.n, digits, errbacks) + "<" + unitToString(n.u) + ">"; } var tenDigits = expt(10, digits, errbacks); var d = toFixnum(digits); @@ -4381,13 +4412,15 @@ define("pyret-base/js/js-numbers", function() { Numbers['makeRational'] = Rational.makeInstance; Numbers['makeRoughnum'] = Roughnum.makeInstance; Numbers['addUnit'] = addUnit; - Numbers['getUnit'] = _unitOf; - Numbers['checkUnit'] = _unitEquals; - Numbers['ensureSameUnits'] = _ensureSameUnits; - Numbers['unitToString'] = _unitToString; - Numbers['unitNumerator'] = _unitNumerator; - Numbers['unitDenominator'] = _unitDenominator; - Numbers['unitToList'] = _unitToList; + Numbers['getUnit'] = getUnit; + Numbers['checkUnit'] = checkUnit; + Numbers['ensureSameUnits'] = ensureSameUnits; + Numbers['unitToString'] = unitToString; + Numbers['unitNumerator'] = unitNumerator; + Numbers['unitDenominator'] = unitDenominator; + Numbers['unitToList'] = unitToList; + Numbers['UNIT_ONE'] = UNIT_ONE; + Numbers['UNIT_ANY'] = UNIT_ANY; Numbers['isPyretNumber'] = isPyretNumber; Numbers['isRational'] = isRational; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index be6c49ca7a..d804529be4 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -620,18 +620,12 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom @param {unit} u @return {!PNumber} with value n and unit u */ - function makeNumberFromString(s, u) { - u = u || {} + function makeNumberFromString(s) { var result = jsnums.fromString(s, NumberErrbacks); if(result === false) { thisRuntime.ffi.throwMessageException("Could not create number from: " + s); } - - if (Object.keys(u).length === 0) { - return result; - } else { - return jsnums.addUnit(result, u) - } + return result; } /********************* @@ -2937,7 +2931,6 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom this.ann = ann; this.u = u; this.flat = true; - this.isAny = jsnums.checkUnit(this.u, { "_": 1 }) this.implicit = implicit; } function makeUnitAnn(ann, u, srcloc) { @@ -2955,7 +2948,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } PUnitAnn.prototype.check = function(compilerLoc, val) { var u = jsnums.getUnit(val); - if (!this.isAny && !jsnums.checkUnit(u, this.u)) { + if (!jsnums.checkUnit(u, this.u)) { var expectedUnit = jsnums.unitToString(this.u); var actualUnit = jsnums.unitToString(u); return thisRuntime.ffi.contractFail( @@ -4888,6 +4881,9 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom var rng = seedrandom("ahoy, world!"); + var UNIT_ONE = jsnums.UNIT_ONE; + var UNIT_ANY = jsnums.UNIT_ANY; + var num_random = function(max) { if (arguments.length !== 1) { var $a=new Array(arguments.length); for (var $i=0;$i) +print(num-expt(2%, -3)) +print(1/8%<(s / m) ^ 3>) diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index bf0c7b720a..97ee6998e1 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -21,15 +21,17 @@ check "Unit equality": (10 * 1%) + 1% is 11% (10% / 1%) + 1 is 11 - # test poly 0 + 0% == 0% is false end check "Arithmetic binops": 2% + 2% is 4% (2% + 2%) raises-satisfies E.is-incompatible-units + (2% + 0%) raises-satisfies E.is-incompatible-units 2% - 2% is 0% (2% - 2%) raises-satisfies E.is-incompatible-units + (2% - 0%) raises-satisfies E.is-incompatible-units 2% * 2% is 4% 2% * 2% is 4 From ac3cb8b0eb15bccfc1428c12520d51644e22ba7a Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 18:25:35 -0400 Subject: [PATCH 66/84] Reset pitometer files for now --- pitometer/programs/anf-loop-compiler.arr | 1 + pitometer/programs/ast.arr | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pitometer/programs/anf-loop-compiler.arr b/pitometer/programs/anf-loop-compiler.arr index e8d0b8959b..72e6997012 100644 --- a/pitometer/programs/anf-loop-compiler.arr +++ b/pitometer/programs/anf-loop-compiler.arr @@ -1193,6 +1193,7 @@ compiler-visitor = { if num-is-fixnum(n): c-exp(j-parens(j-num(n)), cl-empty) else: + c-exp(rt-method("makeNumberFromString", [clist: j-str(tostring(n))]), cl-empty) end end, method a-str(self, l :: Loc, s :: String): diff --git a/pitometer/programs/ast.arr b/pitometer/programs/ast.arr index dfaebe5835..22c3f05c6a 100644 --- a/pitometer/programs/ast.arr +++ b/pitometer/programs/ast.arr @@ -891,7 +891,7 @@ data Expr: | s-srcloc(l :: Loc, loc :: Loc) with: method label(self): "s-srcloc" end, method tosource(self): PP.str(torepr(self.loc)) end - | s-num(l :: Loc, n :: Number, u :: Option) with: + | s-num(l :: Loc, n :: Number) with: method label(self): "s-num" end, method tosource(self): PP.number(self.n) end | s-frac(l :: Loc, num :: Number, den :: Number) with: @@ -2023,8 +2023,8 @@ default-map-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(l, loc) end, - method s-num(self, l :: Loc, n :: Number, u :: Option): - s-num(l, n, u) + method s-num(self, l :: Loc, n :: Number): + s-num(l, n) end, method s-frac(self, l :: Loc, num :: Number, den :: Number): s-frac(l, num, den) @@ -3070,8 +3070,8 @@ dummy-loc-visitor = { method s-srcloc(self, l, shadow loc): s-srcloc(dummy-loc, loc) end, - method s-num(self, l :: Loc, n :: Number, u :: Unit): - s-num(dummy-loc, n, u) + method s-num(self, l :: Loc, n :: Number): + s-num(dummy-loc, n) end, method s-frac(self, l :: Loc, num :: Number, den :: Number): s-frac(dummy-loc, num, den) From 8fe17b2d74be1413671db1e191ea71b5d3f92bdc Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 18:26:22 -0400 Subject: [PATCH 67/84] Reset build dir --- build/phase0/js/runtime.js | 1 - 1 file changed, 1 deletion(-) diff --git a/build/phase0/js/runtime.js b/build/phase0/js/runtime.js index 378726505d..cd3742b3e1 100644 --- a/build/phase0/js/runtime.js +++ b/build/phase0/js/runtime.js @@ -3791,7 +3791,6 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom throwSqrtNegative: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwLogNonPositive: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwIncomparableValues: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, - throwIncompatibleUnits: function(msg) { thisRuntime.ffi.throwMessageException(msg); }, throwInternalError: function(msg) { thisRuntime.ffi.throwInternalError(msg); }, }; From c7a5dbdb5a9a9095d5d102a254accd8fd9fc6adc Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 18:51:22 -0400 Subject: [PATCH 68/84] Final pre-PR cleanup --- src/arr/compiler/anf-loop-compiler.arr | 4 +-- src/arr/trove/error.arr | 1 - src/js/base/js-numbers.js | 34 +++++++------------------- src/js/base/runtime.js | 2 -- t.arr | 2 -- tests/pyret/tests/test-units.arr | 21 ++++++++-------- 6 files changed, 21 insertions(+), 43 deletions(-) delete mode 100644 t.arr diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index bf0214f71e..4df436cab2 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -1690,9 +1690,7 @@ compiler-visitor = { )) method-expr = if len < 9: rt-method(string-append("makeMethod", tostring(len - 1)), [clist: j-id(temp-full), j-str(name)]) - else: #raise("don't do that") - # TODO(benmusch): what's going on here? Why did I need to comment that - # line? + else: rt-method("makeMethodN", [clist: j-id(temp-full), j-str(name)]) end c-exp(method-expr, [clist: full-var]) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index a965e981ef..1d7f1f75bb 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -85,7 +85,6 @@ fun destructure-method-application(l, src-available, maybe-ast): end data RuntimeError: - # note to self: ED.text(to-repr(self)) for simple string-ification | multi-error(errors #|:: List|#) with: method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): # The concatenation of renderings is _not_ a valid rendering; diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index fa359f1bd6..f3fb6de63d 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -475,7 +475,7 @@ define("pyret-base/js/js-numbers", function() { var divide = makeNumericBinop( function(x, y, errbacks) { if (_integerIsZero(y)) - errbacks.throwDivByZero("/: division by zero, " + x + ' ' + y); + errbacks.throwDivByZero("/: division by zero, " + x.toString() + ' ' + y.toString()); var div = x / y; if (isOverflow(div)) { return (makeBignum(x)).divide(makeBignum(y), errbacks); @@ -487,7 +487,7 @@ define("pyret-base/js/js-numbers", function() { }, function(x, y, errbacks) { if (equalsAnyZero(y, errbacks)) { - errbacks.throwDivByZero('/: division by zero, ' + x + ' ' + y); + errbacks.throwDivByZero("/: division by zero, " + x.toString() + ' ' + y.toString()); } return x.divide(y, errbacks); }, @@ -497,7 +497,7 @@ define("pyret-base/js/js-numbers", function() { }, onXSpecialCase: function(x, y, errbacks) { if (equalsAnyZero(y, errbacks)) { - errbacks.throwDivByZero("/: division by zero, " + x + ' ' + y); + errbacks.throwDivByZero("/: division by zero, " + x.toString() + ' ' + y.toString()); } return 0; }, @@ -505,7 +505,7 @@ define("pyret-base/js/js-numbers", function() { return equalsAnyZero(y, errbacks); }, onYSpecialCase: function(x, y, errbacks) { - errbacks.throwDivByZero("/: division by zero, " + x + ' ' + y); + errbacks.throwDivByZero("/: division by zero, " + x.toString() + ' ' + y.toString()); } }); @@ -530,7 +530,7 @@ define("pyret-base/js/js-numbers", function() { var equalsAnyZero = function(x, errbacks) { if (typeof(x) === 'number') return x === 0; if (isRoughnum(x)) return x.n === 0; - return equals(x, 0, errbacks); + return equals(_withoutUnit(x), 0, errbacks); }; // eqv: pyretnum pyretnum -> boolean @@ -1661,12 +1661,8 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.toString = function() { - var unitStr = "<" + unitToString(this.u) + ">" - if (typeof(this.n) === "number") { - return this.n.toString(10) + unitStr; - } else { - return this.n.toString() + unitStr; - } + var unitStr = "%<" + unitToString(this.u) + ">" + return this.n.toString() + unitStr; }; Unitnum.prototype.isFinite = function() { @@ -1806,22 +1802,18 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.tan = function(errbacks) { - // TODO(benmusch): potentially support units here _throwUnitsUnsupported(this.u, errbacks, "tan"); }; Unitnum.prototype.atan = function(errbacks) { - // TODO(benmusch): potentially support units here _throwUnitsUnsupported(this.u, errbacks, "atan"); }; Unitnum.prototype.cos = function(errbacks) { - // TODO(benmusch): potentially support units here _throwUnitsUnsupported(this.u, errbacks, "cos"); }; Unitnum.prototype.sin = function(errbacks) { - // TODO(benmusch): potentially support units here _throwUnitsUnsupported(this.u, errbacks, "sin"); }; @@ -1838,12 +1830,10 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.acos = function(errbacks) { - // TODO(benmusch): potentially support units here _throwUnitsUnsupported(this.u, errbacks, "acos", false); }; Unitnum.prototype.asin = function(errbacks) { - // TODO(benmusch): potentially support units here _throwUnitsUnsupported(this.u, errbacks, "asin", false); }; @@ -1856,10 +1846,7 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.equals = function(other) { - // TODO(benmusch): should this support a polymorphic zero which ignores - // units? - return checkUnit(this.u, getUnit(other)) && - equals(this.n, other.n); + return checkUnit(this.u, getUnit(other)) && equals(this.n, other.n); }; // Rationals @@ -3124,9 +3111,6 @@ define("pyret-base/js/js-numbers", function() { // (protected) r = this * a, r != this,a (HAC 14.12) // "this" should be the larger one if appropriate. function bnpMultiplyTo(a,r) { - if (a === undefined || r === undefined || this === undefined) { - console.log("this", this, "a", a, "r", r) - } var x = this.abs(), y = a.abs(); var i = x.t; r.t = i+y.t; @@ -4378,7 +4362,7 @@ define("pyret-base/js/js-numbers", function() { } if (n instanceof Unitnum) { - return toStringDigits(n.n, digits, errbacks) + "<" + unitToString(n.u) + ">"; + return toStringDigits(n.n, digits, errbacks) + "%<" + unitToString(n.u) + ">"; } var tenDigits = expt(10, digits, errbacks); var d = toFixnum(digits); diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index d804529be4..1a1f179975 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -5145,8 +5145,6 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom n, thisRuntime.NumberAnyUnit); return thisRuntime.makeBoolean(typeof n === "number"); } - // TODO(benmusch): consider supporting units and then validating integer - // power var num_expt = function(n, pow) { if (arguments.length !== 2) { var $a=new Array(arguments.length); for (var $i=0;$i, -3)) -print(1/8%<(s / m) ^ 3>) diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 97ee6998e1..10de4fe32e 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -41,6 +41,7 @@ check "Arithmetic binops": 2% / 2% is 1 2% / 2% is 1% 2% / 2% is 1% + 2% / 0% raises "/: division by zero, 2% 0%" end check "Logical binops": @@ -95,18 +96,18 @@ check "Other supported operations": num-is-non-negative(0%) is true num-is-non-negative(-2%) is false - num-to-string(2.5%) is "5/2" - num-to-string(2%) is "2" - num-to-string(2/3%) is "2/3" - num-to-string(~2.718%) is "~2.718" - num-to-string(~6.022e23%) is "~6.022e+23" + num-to-string(2.5%) is "5/2%" + num-to-string(2%) is "2%" + num-to-string(2/3%) is "2/3%" + num-to-string(~2.718%) is "~2.718%" + num-to-string(~6.022e23%) is "~6.022e+23%" - num-to-string-digits(2/3%, 3) is "0.667" - num-to-string-digits(-2/3%, 3) is "-0.667" + num-to-string-digits(2/3%, 3) is "0.667%" + num-to-string-digits(-2/3%, 3) is "-0.667%" - num-to-string-digits(5%, 2) is "5.00" - num-to-string-digits(5%, 0) is "5" - num-to-string-digits(555%, -2) is "600" + num-to-string-digits(5%, 2) is "5.00%" + num-to-string-digits(5%, 0) is "5%" + num-to-string-digits(555%, -2) is "600%" num-max(2%, 1%) is 2% num-max(2%, 2%) raises-satisfies E.is-incompatible-units From 56be3dee292ade1d3ec881c4549b3ce525488595 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 19:39:04 -0400 Subject: [PATCH 69/84] Fix regression tests --- tests/pyret/regression/parens-after-comments.arr | 6 ++++-- tests/pyret/regression/pretty-print-instantiate.arr | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/pyret/regression/parens-after-comments.arr b/tests/pyret/regression/parens-after-comments.arr index b361fc306c..5d8b03539b 100644 --- a/tests/pyret/regression/parens-after-comments.arr +++ b/tests/pyret/regression/parens-after-comments.arr @@ -6,6 +6,8 @@ fun program-block(contents): empty, A.s-block(A.dummy-loc, contents)) end +one = A.u-one + check "https://github.com/brownplt/pyret-lang/issues/828": P.does-parse(``` ### Comment 1 @@ -22,8 +24,8 @@ check "https://github.com/brownplt/pyret-lang/issues/828": (1 + 2) ```).visit(A.dummy-loc-visitor) is program-block([list: - A.s-op(A.dummy-loc, A.dummy-loc, "op*", A.s-num(A.dummy-loc, 3, none), A.s-num(A.dummy-loc, 2, none)), - A.s-paren(A.dummy-loc, A.s-op(A.dummy-loc, A.dummy-loc, "op+", A.s-num(A.dummy-loc, 1, none), A.s-num(A.dummy-loc, 2, none))) + A.s-op(A.dummy-loc, A.dummy-loc, "op*", A.s-num(A.dummy-loc, 3, one), A.s-num(A.dummy-loc, 2, one)), + A.s-paren(A.dummy-loc, A.s-op(A.dummy-loc, A.dummy-loc, "op+", A.s-num(A.dummy-loc, 1, one), A.s-num(A.dummy-loc, 2, one))) ]) P.get-parse-result("ab#|...|#cd").visit(A.dummy-loc-visitor) diff --git a/tests/pyret/regression/pretty-print-instantiate.arr b/tests/pyret/regression/pretty-print-instantiate.arr index c5255b0a93..93d0fc7cac 100644 --- a/tests/pyret/regression/pretty-print-instantiate.arr +++ b/tests/pyret/regression/pretty-print-instantiate.arr @@ -2,5 +2,5 @@ import ast as A check: d = A.dummy-loc - A.s-instantiate(d, A.s-num(d, 0, none), [list: A.a-any(d)]).tosource().pretty(80) is [list: "0"] + A.s-instantiate(d, A.s-num(d, 0, A.u-one), [list: A.a-any(d)]).tosource().pretty(80) is [list: "0"] end From 84e2dec4e52eb4d0bdba3c15f744de3362400c68 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 18 Jun 2019 23:35:40 -0400 Subject: [PATCH 70/84] Use bigint when necessary on unit powers --- src/arr/compiler/anf-loop-compiler.arr | 7 +++++- src/js/base/js-numbers.js | 24 +++++++++---------- .../regression/parens-after-comments.arr | 2 +- .../regression/pretty-print-instantiate.arr | 2 +- tests/pyret/tests/test-units.arr | 10 ++++++++ 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/arr/compiler/anf-loop-compiler.arr b/src/arr/compiler/anf-loop-compiler.arr index 4df436cab2..b80cd474f6 100644 --- a/src/arr/compiler/anf-loop-compiler.arr +++ b/src/arr/compiler/anf-loop-compiler.arr @@ -354,7 +354,12 @@ fun compile-unit-help(u :: A.Unit) -> J.JExpr: if power == 0: acc else: - CL.concat-cons(j-field(key, j-num(power)), acc) + val = if num-is-fixnum(power): + j-num(power) + else: + rt-method("makeNumberFromString", [clist: j-str(tostring(power))]) + end + CL.concat-cons(j-field(key, val), acc) end end, CL.concat-empty) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index f3fb6de63d..3462e067d6 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1108,10 +1108,10 @@ define("pyret-base/js/js-numbers", function() { if (!u.hasOwnProperty(unitName) || unitName === COUNT_FIELD) continue var power = u[unitName]; - if (power === 1) { + if (_integerIsOne(power)) { unitStrs = unitStrs.concat(unitName) - } else if (power !== 0) { - unitStrs = unitStrs.concat(unitName + " ^ " + power) + } else if (!_integerIsZero(power)) { + unitStrs = unitStrs.concat(unitName + " ^ " + power.toString()) } } return unitStrs.sort().join(" * "); @@ -1187,7 +1187,7 @@ define("pyret-base/js/js-numbers", function() { for (var unitName in u1) { if (!u1.hasOwnProperty(unitName)) continue - if (_unitExponentOf(u1, unitName) !== _unitExponentOf(u2, unitName)) { + if (!_integerEquals(_unitExponentOf(u1, unitName), _unitExponentOf(u2, unitName))) { return false; } } @@ -1203,8 +1203,8 @@ define("pyret-base/js/js-numbers", function() { if (!u1.hasOwnProperty(unitName) && !u2.hasOwnProperty(unitName)) continue if (unitName === COUNT_FIELD) continue; - var newExp = _unitExponentOf(u1, unitName) + _unitExponentOf(u2, unitName); - if (newExp !== 0) { + var newExp = _integerAdd(_unitExponentOf(u1, unitName), _unitExponentOf(u2, unitName)); + if (!_integerIsZero(newExp)) { newUnit[unitName] = newExp; } } @@ -1219,11 +1219,11 @@ define("pyret-base/js/js-numbers", function() { } var _unitInvert = function(u) { - return _unitMap(u, function(n) { return -1 * n }) + return _unitMap(u, function(n) { return _integerMultiply(-1, n) }) } var unitNumerator = function(u) { - return _unitFilter(u, function(pow) { return pow > 0 }); + return _unitFilter(u, function(pow) { return _integerGreaterThan(pow, 0) }); } var unitDenominator = function(u) { @@ -1764,10 +1764,10 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.integerSqrt = function(errbacks) { var that = this; var newUnit = _unitMap(this.u, function(pow) { - if (pow % 2 !== 0) { + if (!_integerIsZero(_integerModulo(pow, 2))) { errbacks.throwInvalidUnitState("num-sqrt", that, "not all units had an even exponent"); } else { - return pow / 2; + return _integerDivideToFixnum(pow, 2); } }); return _withUnit(integerSqrt(this.n), newUnit, false); @@ -1776,10 +1776,10 @@ define("pyret-base/js/js-numbers", function() { Unitnum.prototype.sqrt = function(errbacks) { var that = this; var newUnit = _unitMap(this.u, function(pow) { - if (pow % 2 !== 0) { + if (!_integerIsZero(_integerModulo(pow, 2))) { errbacks.throwInvalidUnitState("num-sqrt", that, "not all units had an even exponent"); } else { - return pow / 2; + return _integerDivideToFixnum(pow, 2); } }); return _withUnit(sqrt(this.n), newUnit, false); diff --git a/tests/pyret/regression/parens-after-comments.arr b/tests/pyret/regression/parens-after-comments.arr index 5d8b03539b..901be935a7 100644 --- a/tests/pyret/regression/parens-after-comments.arr +++ b/tests/pyret/regression/parens-after-comments.arr @@ -6,7 +6,7 @@ fun program-block(contents): empty, A.s-block(A.dummy-loc, contents)) end -one = A.u-one +one = A.u-one(A.dummy-loc) check "https://github.com/brownplt/pyret-lang/issues/828": P.does-parse(``` diff --git a/tests/pyret/regression/pretty-print-instantiate.arr b/tests/pyret/regression/pretty-print-instantiate.arr index 93d0fc7cac..bb1a9669ae 100644 --- a/tests/pyret/regression/pretty-print-instantiate.arr +++ b/tests/pyret/regression/pretty-print-instantiate.arr @@ -2,5 +2,5 @@ import ast as A check: d = A.dummy-loc - A.s-instantiate(d, A.s-num(d, 0, A.u-one), [list: A.a-any(d)]).tosource().pretty(80) is [list: "0"] + A.s-instantiate(d, A.s-num(d, 0, A.u-one(d)), [list: A.a-any(d)]).tosource().pretty(80) is [list: "0"] end diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 10de4fe32e..7ee48493b4 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -152,3 +152,13 @@ check "Unsupported unops": num-within-abs(2%) raises-satisfies is-unit-contract-fail num-within-rel(2%) raises-satisfies is-unit-contract-fail end + +check "Units with bigint powers": + 1% == 1% is false + 1% == 1% is true + + num-sqr(1%) == 1% is true + num-sqrt(1%) == 1% is true + num-sqr(1%) == 1% is false + num-sqrt(1%) == 1% is false +end From e7cd22335b326d2c41d42faec267768c46202144 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 19 Jun 2019 12:55:48 -0400 Subject: [PATCH 71/84] Fix division bug --- src/js/base/js-numbers.js | 4 ++-- tests/pyret/tests/test-units.arr | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 3462e067d6..259165a5db 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1767,7 +1767,7 @@ define("pyret-base/js/js-numbers", function() { if (!_integerIsZero(_integerModulo(pow, 2))) { errbacks.throwInvalidUnitState("num-sqrt", that, "not all units had an even exponent"); } else { - return _integerDivideToFixnum(pow, 2); + return halve(pow, errbacks); } }); return _withUnit(integerSqrt(this.n), newUnit, false); @@ -1779,7 +1779,7 @@ define("pyret-base/js/js-numbers", function() { if (!_integerIsZero(_integerModulo(pow, 2))) { errbacks.throwInvalidUnitState("num-sqrt", that, "not all units had an even exponent"); } else { - return _integerDivideToFixnum(pow, 2); + return halve(pow, errbacks); } }); return _withUnit(sqrt(this.n), newUnit, false); diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 7ee48493b4..3c956fb0bf 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -161,4 +161,6 @@ check "Units with bigint powers": num-sqrt(1%) == 1% is true num-sqr(1%) == 1% is false num-sqrt(1%) == 1% is false + + num-sqrt(num-sqrt(num-sqrt(num-sqr(num-sqr(num-sqr(1%)))))) is 1% end From 780780ef64584014675aac8e4c81e6ec78612467 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 21 Jun 2019 14:49:25 -0400 Subject: [PATCH 72/84] Improve units-on-unsupport-ann message --- src/arr/trove/error.arr | 48 ++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 1d7f1f75bb..53ed6884e5 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2430,16 +2430,48 @@ data RuntimeError: end | units-on-unsupported-ann(loc, unit-str) with: method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): - self.render-reason() + if self.loc.is-builtin(): + [ED.error: + ed-intro("unit annotation", self.loc, -1, true), + ED.cmcode(self.loc), + [ED.para: ED.text("It annotated a type which does not support units.")], + please-report-bug()] + else if src-available(self.loc): + cases(O.Option) maybe-ast(self.loc): + | some(ast) => + [ED.error: + ed-intro("unit annotation", self.loc, -1, true), + ED.cmcode(self.loc), + [ED.para: + ED.highlight(ED.text("unit"), [ED.locs: ast.l], 0), + ED.text(" annotated a "), + ED.highlight(ED.text("base type"), [ED.locs: ast.ann.l], 1), + ED.text(" which does not support units.")]] + | none => + [ED.error: + ed-intro("unit annotation", self.loc, -1, true), + ED.cmcode(self.loc), + [ED.para: + ED.text("It annotated a type which does not support units. "), + ED.text("Consider removing the unit annotation or changing the base type.")]] + end + else: + self.render-reason() + end end, method render-reason(self): - [ED.error: - [ED.para: - ED.text("The annotation at "), - ED.loc(self.loc), - ED.text(" is annotated with the unit "), - ED.code(ED.text(self.unit-str)), - ED.text(" but does not support unit annotations.")]] + base-err = [ED.para: + ed-simple-intro("unit annotation", self.loc), + ED.text("The annotation at "), + ED.loc(self.loc), + ED.text(" is annotated with the unit "), + ED.code(ED.text(self.unit-str)), + ED.text(" but does not support unit annotations.")] + if self.loc.is-builtin(): + [ED.error: base-err, please-report-bug()] + else: + [ED.error: base-err] + end end | incompatible-units(op-name, l, r) with: method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): From 97013248891c34136a500e516cc79e815ab9090d Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 21 Jun 2019 15:59:33 -0400 Subject: [PATCH 73/84] Improve incompatible-units error message --- src/arr/trove/error.arr | 93 ++++++++++++++++++++++++++++++++++++--- src/js/base/js-numbers.js | 2 +- src/js/trove/ffi.js | 4 +- 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 53ed6884e5..9bcd34ea63 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2475,15 +2475,94 @@ data RuntimeError: end | incompatible-units(op-name, l, r) with: method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): - self.render-reason() + [ED.error: + cases(O.Option) maybe-stack-loc(0, false): + | some(loc) => + if loc.is-builtin(): + [ED.sequence: + ed-intro(self.op-name + " operation", loc, -1, true), + [ED.para: + ED.text("The left side had the unit: "), + ED.code(ED.text(self.l))], + [ED.para: + ED.text("The right side had the unit: "), + ED.code(ED.text(self.r))], + [ED.para: ED.text("These units are not compatible")], + please-report-bug()] + else if src-available(loc): + cases(O.Option) maybe-ast(loc): + | some(ast) => + left-loc = ast.left.l + right-loc = ast.right.l + [ED.sequence: + ed-intro(self.op-name + " operation", loc, -1, true), + ED.cmcode(loc), + [ED.para: + ED.text("The "), + ED.highlight(ED.text("left side"), [ED.locs: left-loc], 0), + ED.text(" had the unit:")], + ED.code(ED.text(self.l)), + [ED.para: + ED.text("The "), + ED.highlight(ED.text("right side"), [ED.locs: right-loc], 1), + ED.text(" had the unit: ")], + ED.code(ED.text(self.r)), + [ED.para: ED.text("These units are not compatible")]] + | none => ED.text("TODO") + end + else: + [ED.sequence: + ed-intro(self.op-name + " operation", loc, -1, true), + ED.cmcode(loc), + [ED.para: + ED.text("The left side had the unit: "), + ED.code(ED.text(self.l))], + [ED.para: + ED.text("The right side had the unit: "), + ED.code(ED.text(self.r))], + [ED.para: ED.text("These units are not compatible")]] + end + | none => + [ED.sequence: + [ED.para: + ED.text("A "), + ED.code(ED.text(self.opname)), + ED.text(" operation errored.")], + [ED.para: + ED.text("The left side had the unit: "), + ED.code(ED.text(self.l))], + [ED.para: + ED.text("The right side had the unit: "), + ED.code(ED.text(self.r))], + [ED.para: ED.text("These units are not compatible")]] + end] end, method render-reason(self): - [ED.error: - [ED.para: - ED.text("The " + self.op-name + " operation failed due to incompatible units. "), - ED.text(tostring(self.l)), - ED.text(" is not compatible with "), - ED.text(tostring(self.r))]] + [ED.error: ED.maybe-stack-loc(0, false, + lam(loc): + [ED.sequence: + ed-simple-intro(self.op-name + " operation", loc), + ED.cmcode(loc), + [ED.para: + ED.text("The left side had the unit: "), + ED.code(ED.text(self.l))], + [ED.para: + ED.text("The right side had the unit: "), + ED.code(ED.text(self.r))], + [ED.para: ED.text("These units are not compatible")]] + end, + [ED.sequence: + [ED.para: + ED.text("A "), + ED.code(ED.text(self.opname)), + ED.text(" operation errored.")], + [ED.para: + ED.text("The left side had the unit: "), + ED.code(ED.text(self.l))], + [ED.para: + ED.text("The right side had the unit: "), + ED.code(ED.text(self.r))], + [ED.para: ED.text("These units are not compatible")]])] end | invalid-unit-state(op-name, n, desc) with: method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 259165a5db..f65dd162b1 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1235,7 +1235,7 @@ define("pyret-base/js/js-numbers", function() { var u2 = getUnit(n2); if (!checkUnit(u1, u2)) { - errbacks.throwIncompatibleUnits(opName, n1, n2); + errbacks.throwIncompatibleUnits(opName, unitToString(u1), unitToString(u2)); } } diff --git a/src/js/trove/ffi.js b/src/js/trove/ffi.js index 38b32820bd..b16b05cec4 100644 --- a/src/js/trove/ffi.js +++ b/src/js/trove/ffi.js @@ -505,8 +505,8 @@ } function throwIncompatibleUnits(opName, l, r) { runtime.checkString(opName); - runtime.checkNumber(l); - runtime.checkNumber(r); + runtime.checkString(l); + runtime.checkString(r); raise(err("incompatible-units")(opName, l, r)); } function throwInvalidUnitState(opName, n, desc) { From 0a032adde3ced078f75744b59c8d753f231559ba Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Fri, 21 Jun 2019 18:49:38 -0400 Subject: [PATCH 74/84] Better invalid-unit-state messages --- src/arr/trove/error.arr | 64 +++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 9bcd34ea63..46d612b925 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2508,7 +2508,17 @@ data RuntimeError: ED.text(" had the unit: ")], ED.code(ED.text(self.r)), [ED.para: ED.text("These units are not compatible")]] - | none => ED.text("TODO") + | none => + [ED.sequence: + ed-intro(self.op-name + " operation", loc, -1, true), + ED.cmcode(loc), + [ED.para: + ED.text("The left side had the unit: "), + ED.code(ED.text(self.l))], + [ED.para: + ED.text("The right side had the unit: "), + ED.code(ED.text(self.r))], + [ED.para: ED.text("These units are not compatible")]] end else: [ED.sequence: @@ -2542,7 +2552,6 @@ data RuntimeError: lam(loc): [ED.sequence: ed-simple-intro(self.op-name + " operation", loc), - ED.cmcode(loc), [ED.para: ED.text("The left side had the unit: "), ED.code(ED.text(self.l))], @@ -2566,15 +2575,52 @@ data RuntimeError: end | invalid-unit-state(op-name, n, desc) with: method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): - self.render-reason() + [ED.error: + cases(O.Option) maybe-stack-loc(0, false): + | some(loc) => + if loc.is-builtin(): + [ED.sequence: + ed-intro(self.op-name + " function", loc, -1, true), + [ED.para: + ED.code(ED.text(tostring(self.n))), + ED.text(" is an invalid argument because: "), + ED.text(self.desc)], + please-report-bug()] + else if src-available(loc): + [ED.sequence: + ed-intro(self.op-name + " function", loc, -1, true), + ED.cmcode(loc), + [ED.para: + ED.code(ED.text(tostring(self.n))), + ED.text(" is an invalid argument because: "), + ED.text(self.desc)]] + end + | none => + [ED.sequence: + [ED.para: + ED.text("The " + self.op-name + " function failed.")], + [ED.para: + ED.code(ED.text(tostring(self.n))), + ED.text(" is an invalid argument because: "), + ED.text(self.desc)]] + end] end, method render-reason(self): - [ED.error: - [ED.para: - ED.text("The " + self.op-name + " operation failed. "), - ED.text(tostring(self.n)), - ED.text(" is an invalid argument because: "), - ED.text(self.desc)]] + [ED.error: ED.maybe-stack-loc(0, false, + lam(loc): + [ED.sequence: + ed-simple-intro(self.op-name + " function", loc), + [ED.para: + ED.code(ED.text(tostring(self.n))), + ED.text(" is an invalid argument because: "), + ED.text(self.desc)]] + end, + [ED.sequence: + [ED.para: ED.text("The " + self.op-name + " function failed.")], + [ED.para: + ED.code(ED.text(tostring(self.n))), + ED.text(" is an invalid argument because: "), + ED.text(self.desc)]])] end end From 6d1de41ea05d0ba008ad9c61024e8b8b3e380ad0 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Sun, 23 Jun 2019 11:23:00 -0400 Subject: [PATCH 75/84] Remove TODO in grammar --- src/js/base/pyret-grammar.bnf | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/base/pyret-grammar.bnf b/src/js/base/pyret-grammar.bnf index ff98be592a..513e34a3e4 100644 --- a/src/js/base/pyret-grammar.bnf +++ b/src/js/base/pyret-grammar.bnf @@ -131,7 +131,6 @@ id-expr: NAME prim-expr: num-expr | frac-expr | rfrac-expr | bool-expr | string-expr dim-expr: (LANGLE|LT) unit-expr (RANGLE|GT) -# TODO(benmusch): Consider adding | (LANGLE|LT) NUMBER (RANGLE|GT) unit-atom: (PARENNOSPACE|PARENSPACE) unit-expr RPAREN | NAME | unit-atom CARET NUMBER From e76f4d3d992bb712d71b4718bf9d8a119525ef9f Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Sun, 23 Jun 2019 11:46:06 -0400 Subject: [PATCH 76/84] Fix bug that drops srcloc on withUnit calls --- src/arr/trove/error.arr | 6 ++---- src/js/base/runtime.js | 6 +++--- src/js/trove/ffi.js | 1 + 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index 46d612b925..b48bc55969 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2459,12 +2459,10 @@ data RuntimeError: self.render-reason() end end, - method render-reason(self): + method render-reason(self) block: base-err = [ED.para: ed-simple-intro("unit annotation", self.loc), - ED.text("The annotation at "), - ED.loc(self.loc), - ED.text(" is annotated with the unit "), + ED.text("The annotation is annotated with the unit "), ED.code(ED.text(self.unit-str)), ED.text(" but does not support unit annotations.")] if self.loc.is-builtin(): diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 1a1f179975..2291d129b3 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2935,15 +2935,15 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } function makeUnitAnn(ann, u, srcloc) { if (ann.withUnit === undefined) { - thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(u), makeSrcloc(srcloc)); + thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(u), srcloc); } - return ann.withUnit(u); + return ann.withUnit(u, srcloc); } PUnitAnn.prototype.withUnit = function(unit, srcloc) { if (this.implicit || jsnums.checkUnit(this.u, unit)) { return new PUnitAnn(this.ann, unit, false); } else { - thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(unit), makeSrcloc(srcloc)); + thisRuntime.ffi.throwUnitsOnUnsupportedAnn(jsnums.unitToString(unit), srcloc); } } PUnitAnn.prototype.check = function(compilerLoc, val) { diff --git a/src/js/trove/ffi.js b/src/js/trove/ffi.js index b16b05cec4..99fe32bf52 100644 --- a/src/js/trove/ffi.js +++ b/src/js/trove/ffi.js @@ -500,6 +500,7 @@ raise(makeModuleLoadFailureL(names)); } function throwUnitsOnUnsupportedAnn(unitStr, loc) { + loc = runtime.makeSrcloc(loc); runtime.checkString(unitStr); raise(err("units-on-unsupported-ann")(loc, unitStr)); } From 7ddfc6731ad26bb34b876f9dd4c5ccd9d2d0f809 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 26 Jun 2019 16:36:11 -0400 Subject: [PATCH 77/84] Some progress, still need to test all error messages --- src/arr/trove/contracts.arr | 18 ++-- src/arr/trove/error.arr | 141 +++++++++++++------------------ src/js/base/js-numbers.js | 36 +++++--- src/js/base/runtime.js | 36 ++++---- t.arr | 1 + tests/pyret/main2.arr | 2 + tests/pyret/tests/test-units.arr | 61 +++++++++++-- 7 files changed, 168 insertions(+), 127 deletions(-) create mode 100644 t.arr diff --git a/src/arr/trove/contracts.arr b/src/arr/trove/contracts.arr index 1d93a8da48..8534752806 100644 --- a/src/arr/trove/contracts.arr +++ b/src/arr/trove/contracts.arr @@ -300,23 +300,23 @@ data FailureReason: end, method render-reason(self, loc, from-fail-arg): message = [ED.para: - ED.text("The predicate"), ED.code(ED.text(self.name)), - ED.text("in the annotation at"), draw-and-highlight(loc), - ED.text("returned false for this value:"), ED.embed(self.val)] + ED.text("The predicate "), ED.code(ED.text(self.name)), + ED.text(" in the annotation at "), draw-and-highlight(loc), + ED.text(" returned false for this value: "), ED.embed(self.val)] if self.is-implicit and not(loc.is-builtin()): [ED.error: message, [ED.para: - ED.text("The unit"), ED.code(ED.text(self.actual)), - ED.text("was expected to match"), ED.code(ED.text(self.expected)), - ED.text("due to the default of the"), ED.code(ED.text(self.name)), - ED.text("annotation. Did you mean to specify another unit or the any unit (%<_>)?")]] + ED.text("The unit "), ED.code(ED.text(self.actual)), + ED.text(" was expected to match "), ED.code(ED.text(self.expected)), + ED.text(" due to the default of the "), ED.code(ED.text(self.name)), + ED.text(" annotation. Did you mean to specify another unit or the any unit (%<_>)?")]] else: [ED.error: message, [ED.para: - ED.text("The unit"), ED.code(ED.text(self.actual)), - ED.text("was expected to match"), ED.code(ED.text(self.expected))]] + ED.text("The unit "), ED.code(ED.text(self.actual)), + ED.text(" was expected to match "), ED.code(ED.text(self.expected))]] end end | record-fields-fail(val, field-failures :: List) with: diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index b48bc55969..da44e27fce 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2471,114 +2471,93 @@ data RuntimeError: [ED.error: base-err] end end - | incompatible-units(op-name, l, r) with: - method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): + | incompatible-units(reason, u, v) with: + # TODO Fix spacing in render-fancy-reason (and maybe render-reason) + method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast) block: + base-err-msg = [ED.para: + ED.text("The units "), + ED.code(ED.text(self.u)), + ED.text(" and "), + ED.code(ED.text(self.v)), + ED.text(" are not compatible")] [ED.error: - cases(O.Option) maybe-stack-loc(0, false): + cases(O.Option) maybe-stack-loc(0, true) block: | some(loc) => + default-err-msg = [ED.sequence: + ed-intro(self.reason, loc, -1, true), + ED.cmcode(loc), + base-err-msg] if loc.is-builtin(): [ED.sequence: - ed-intro(self.op-name + " operation", loc, -1, true), - [ED.para: - ED.text("The left side had the unit: "), - ED.code(ED.text(self.l))], - [ED.para: - ED.text("The right side had the unit: "), - ED.code(ED.text(self.r))], - [ED.para: ED.text("These units are not compatible")], + ed-intro(self.reason, loc, -1, true), + base-err-msg, please-report-bug()] else if src-available(loc): cases(O.Option) maybe-ast(loc): | some(ast) => - left-loc = ast.left.l - right-loc = ast.right.l - [ED.sequence: - ed-intro(self.op-name + " operation", loc, -1, true), - ED.cmcode(loc), - [ED.para: - ED.text("The "), - ED.highlight(ED.text("left side"), [ED.locs: left-loc], 0), - ED.text(" had the unit:")], - ED.code(ED.text(self.l)), - [ED.para: - ED.text("The "), - ED.highlight(ED.text("right side"), [ED.locs: right-loc], 1), - ED.text(" had the unit: ")], - ED.code(ED.text(self.r)), - [ED.para: ED.text("These units are not compatible")]] - | none => - [ED.sequence: - ed-intro(self.op-name + " operation", loc, -1, true), - ED.cmcode(loc), - [ED.para: - ED.text("The left side had the unit: "), - ED.code(ED.text(self.l))], - [ED.para: - ED.text("The right side had the unit: "), - ED.code(ED.text(self.r))], - [ED.para: ED.text("These units are not compatible")]] + cases(Any) ast: + | s-op(_l, op-l, opname, l, r) => + left-loc = l.l + right-loc = r.l + [ED.sequence: + ed-intro(self.reason, loc, -1, true), + ED.cmcode(loc), + [ED.para: + ED.text("The "), + ED.highlight(ED.text("left side"), [ED.locs: left-loc], 0), + ED.text(" had the unit:")], + ED.code(ED.text(self.u)), + [ED.para: + ED.text("The "), + ED.highlight(ED.text("right side"), [ED.locs: right-loc], 1), + ED.text(" had the unit: ")], + ED.code(ED.text(self.v)), + [ED.para: ED.text("These units are not compatible")]] + | else => default-err-msg + end + | none => default-err-msg end else: - [ED.sequence: - ed-intro(self.op-name + " operation", loc, -1, true), - ED.cmcode(loc), - [ED.para: - ED.text("The left side had the unit: "), - ED.code(ED.text(self.l))], - [ED.para: - ED.text("The right side had the unit: "), - ED.code(ED.text(self.r))], - [ED.para: ED.text("These units are not compatible")]] + default-err-msg end | none => [ED.sequence: [ED.para: ED.text("A "), - ED.code(ED.text(self.opname)), - ED.text(" operation errored.")], - [ED.para: - ED.text("The left side had the unit: "), - ED.code(ED.text(self.l))], - [ED.para: - ED.text("The right side had the unit: "), - ED.code(ED.text(self.r))], - [ED.para: ED.text("These units are not compatible")]] + ED.code(ED.text(self.reason)), + ED.text(" errored.")], + base-err-msg] end] end, - method render-reason(self): + method render-reason(self) block: + base-err-msg = [ED.para: + ED.text("The units "), + ED.code(ED.text(self.u)), + ED.text(" and "), + ED.code(ED.text(self.v)), + ED.text(" are not compatible")] [ED.error: ED.maybe-stack-loc(0, false, lam(loc): [ED.sequence: - ed-simple-intro(self.op-name + " operation", loc), - [ED.para: - ED.text("The left side had the unit: "), - ED.code(ED.text(self.l))], - [ED.para: - ED.text("The right side had the unit: "), - ED.code(ED.text(self.r))], - [ED.para: ED.text("These units are not compatible")]] + ed-simple-intro(self.reason, loc), + base-err-msg] end, [ED.sequence: [ED.para: ED.text("A "), - ED.code(ED.text(self.opname)), - ED.text(" operation errored.")], - [ED.para: - ED.text("The left side had the unit: "), - ED.code(ED.text(self.l))], - [ED.para: - ED.text("The right side had the unit: "), - ED.code(ED.text(self.r))], - [ED.para: ED.text("These units are not compatible")]])] + ED.code(ED.text(self.reason)), + ED.text(" errored.")], + base-err-msg])] end - | invalid-unit-state(op-name, n, desc) with: + | invalid-unit-state(opname, n, desc) with: + # TODO: update method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): [ED.error: cases(O.Option) maybe-stack-loc(0, false): | some(loc) => if loc.is-builtin(): [ED.sequence: - ed-intro(self.op-name + " function", loc, -1, true), + ed-intro(self.opname + " function", loc, -1, true), [ED.para: ED.code(ED.text(tostring(self.n))), ED.text(" is an invalid argument because: "), @@ -2586,7 +2565,7 @@ data RuntimeError: please-report-bug()] else if src-available(loc): [ED.sequence: - ed-intro(self.op-name + " function", loc, -1, true), + ed-intro(self.opname + " function", loc, -1, true), ED.cmcode(loc), [ED.para: ED.code(ED.text(tostring(self.n))), @@ -2596,7 +2575,7 @@ data RuntimeError: | none => [ED.sequence: [ED.para: - ED.text("The " + self.op-name + " function failed.")], + ED.text("The " + self.opname + " function failed.")], [ED.para: ED.code(ED.text(tostring(self.n))), ED.text(" is an invalid argument because: "), @@ -2607,14 +2586,14 @@ data RuntimeError: [ED.error: ED.maybe-stack-loc(0, false, lam(loc): [ED.sequence: - ed-simple-intro(self.op-name + " function", loc), + ed-simple-intro(self.opname + " function", loc), [ED.para: ED.code(ED.text(tostring(self.n))), ED.text(" is an invalid argument because: "), ED.text(self.desc)]] end, [ED.sequence: - [ED.para: ED.text("The " + self.op-name + " function failed.")], + [ED.para: ED.text("The " + self.opname + " function failed.")], [ED.para: ED.code(ED.text(tostring(self.n))), ED.text(" is an invalid argument because: "), diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index f65dd162b1..6fb647b996 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -372,13 +372,13 @@ define("pyret-base/js/js-numbers", function() { {isXSpecialCase: function(x, errbacks) { return isInteger(x) && _integerIsZero(x) }, onXSpecialCase: function(x, y, errbacks) { - ensureSameUnits(x, y, errbacks, "+"); + ensureSameUnits(x, y, errbacks, "+ operation"); return y; }, isYSpecialCase: function(y, errbacks) { return isInteger(y) && _integerIsZero(y) }, onYSpecialCase: function(x, y, errbacks) { - ensureSameUnits(x, y, errbacks, "+"); + ensureSameUnits(x, y, errbacks, "+ operation"); return x; } }); @@ -411,13 +411,13 @@ define("pyret-base/js/js-numbers", function() { {isXSpecialCase: function(x, errbacks) { return isInteger(x) && _integerIsZero(x) }, onXSpecialCase: function(x, y, errbacks) { - ensureSameUnits(x, y, errbacks, "-"); + ensureSameUnits(x, y, errbacks, "- operation"); return negate(y, errbacks); }, isYSpecialCase: function(y, errbacks) { return isInteger(y) && _integerIsZero(y) }, onYSpecialCase: function(x, y, errbacks) { - ensureSameUnits(x, y, errbacks, "-"); + ensureSameUnits(x, y, errbacks, "- operation"); return x; } }); @@ -550,7 +550,10 @@ define("pyret-base/js/js-numbers", function() { }; // used for within - var roughlyEquals = function(x, y, delta, errbacks) { + var roughlyEquals = function(x, y, delta, where, errbacks) { + ensureSameUnits(x, y, errbacks, where + "'s arguments"); + ensureSameUnits(y, delta, errbacks, where + "'s tolerance"); + if (isNegative(delta)) { errbacks.throwToleranceError("negative tolerance " + delta); } @@ -559,7 +562,7 @@ define("pyret-base/js/js-numbers", function() { if (isRoughnum(delta) && delta.n === Number.MIN_VALUE) { if ((isRoughnum(x) || isRoughnum(y)) && - (Math.abs(subtract(x,y).n) === Number.MIN_VALUE)) { + (Math.abs(_withoutUnit(subtract(x,y)).n) === Number.MIN_VALUE)) { errbacks.throwToleranceError("roughnum tolerance too small for meaningful comparison, " + x + ' ' + y + ' ' + delta); } } @@ -571,7 +574,12 @@ define("pyret-base/js/js-numbers", function() { return approxEquals(ratx, raty, ratdelta, errbacks); }; - var roughlyEqualsRel = function(computedValue, trueValue, delta, errbacks) { + var roughlyEqualsRel = function(computedValue, trueValue, delta, where, errbacks) { + if (!checkUnit(getUnit(delta), UNIT_ONE)) { + errbacks.throwInvalidUnitState("relative rough equality", delta, "tolerance cannot have a unit"); + } + ensureSameUnits(computedValue, trueValue, errbacks, where + "'s arguments"); + if (isNegative(delta)) { errbacks.throwRelToleranceError('negative relative tolerance ' + delta) } @@ -1177,6 +1185,8 @@ define("pyret-base/js/js-numbers", function() { newUnit[unitName] = u[unitName]; newUnit[COUNT_FIELD] += 1; } + + if (newUnit[COUNT_FIELD] === 0) return UNIT_ONE; return newUnit; } @@ -1714,32 +1724,32 @@ define("pyret-base/js/js-numbers", function() { }; Unitnum.prototype.greaterThan = function(n, errbacks) { - ensureSameUnits(this, n, errbacks, ">"); + ensureSameUnits(this, n, errbacks, "> operation"); return greaterThan(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.greaterThanOrEqual = function(n, errbacks) { - ensureSameUnits(this, n, errbacks, ">="); + ensureSameUnits(this, n, errbacks, ">= operation"); return greaterThanOrEqual(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.lessThan = function(n, errbacks) { - ensureSameUnits(this, n, errbacks, "<"); + ensureSameUnits(this, n, errbacks, "< operation"); return lessThan(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.lessThanOrEqual = function(n, errbacks) { - ensureSameUnits(this, n, errbacks, "<="); + ensureSameUnits(this, n, errbacks, "<= operation"); return lessThanOrEqual(_withoutUnit(this.n), _withoutUnit(n)); }; Unitnum.prototype.add = function(n, errbacks) { - ensureSameUnits(this, n, errbacks, "+"); + ensureSameUnits(this, n, errbacks, "+ operation"); return _withUnit(add(_withoutUnit(this), _withoutUnit(n), errbacks), this.u, false); }; Unitnum.prototype.subtract = function(n, errbacks) { - ensureSameUnits(this, n, errbacks, "-"); + ensureSameUnits(this, n, errbacks, "- operation"); return _withUnit(subtract(_withoutUnit(this), _withoutUnit(n), errbacks), this.u, false); }; diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 2291d129b3..3301ffccf5 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -1988,17 +1988,21 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom curLeft = current.left; curRight = current.right; - if (thisRuntime.ffi.isEqual(identical3(curLeft, curRight))) { + if (!fromWithin && thisRuntime.ffi.isEqual(identical3(curLeft, curRight))) { continue; } else if (isNumber(curLeft) && isNumber(curRight)) { - if (tol) { + if (!jsnums.checkUnit(jsnums.getUnit(curLeft), jsnums.getUnit(curRight))) { + toCompare.curAns = thisRuntime.ffi.notEqual.app(current.path + ".units", curLeft, curRight); + } else if (tol) { if (rel) { - if (jsnums.roughlyEqualsRel(curLeft, curRight, tol, NumberErrbacks)) { + if (jsnums.roughlyEqualsRel(curLeft, curRight, tol, "equality", NumberErrbacks)) { continue; } else { toCompare.curAns = thisRuntime.ffi.notEqual.app(current.path, curLeft, curRight); } - } else if (jsnums.roughlyEquals(curLeft, curRight, tol, NumberErrbacks)) { + } else if (!jsnums.checkUnit(jsnums.getUnit(curLeft), jsnums.getUnit(tol))) { + toCompare.curAns = thisRuntime.ffi.notEqual.app(current.path + ".tolerance-units", curLeft, curRight); + } else if (jsnums.roughlyEquals(curLeft, curRight, tol, "equality", NumberErrbacks)) { continue; } else { toCompare.curAns = thisRuntime.ffi.notEqual.app(current.path, curLeft, curRight); @@ -2212,7 +2216,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom function equalWithinAbsNow3(tol) { if (arguments.length !== 1) { var $a=new Array(arguments.length); for (var $i=0;$i, ~3) diff --git a/tests/pyret/main2.arr b/tests/pyret/main2.arr index 1066fce74c..a01a08d62e 100644 --- a/tests/pyret/main2.arr +++ b/tests/pyret/main2.arr @@ -1,3 +1,4 @@ +#| import file("./tests/test-strings.arr") as _ import file("./tests/test-format.arr") as _ import file("./tests/test-numbers.arr") as _ @@ -41,4 +42,5 @@ import file("./tests/test-tail-call.arr") as _ import file("./tests/test-parse.arr") as _ import file("./tests/test-parse-errors.arr") as _ import file("./tests/test-flatness.arr") as _ +|# import file("./tests/test-units.arr") as _ diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 3c956fb0bf..39c48daf08 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -1,6 +1,12 @@ import error as E import contracts as C +fun is-unit-contract-fail(result): + C.is-fail(result) and C.is-failure-at-arg(result.reason) and C.is-unit-fail(result.reason.reason) +end + +#| + check "Unit equality": 2 == 2% is false 2% == 2% is false @@ -128,11 +134,7 @@ check "Other supported operations": num-sqrt(4%) is 2%<1 / m> end -fun is-unit-contract-fail(result): - C.is-fail(result) and C.is-failure-at-arg(result.reason) and C.is-unit-fail(result.reason.reason) -end - -check "Unsupported unops": +check "Unsupported operations": num-sqrt(2%) raises-satisfies E.is-invalid-unit-state num-expt(2%, 0.5) raises-satisfies E.is-invalid-unit-state @@ -149,8 +151,6 @@ check "Unsupported unops": num-expt(2%, 3%) raises-satisfies is-unit-contract-fail num-modulo(2%, 2%) raises-satisfies is-unit-contract-fail num-to-string-digits(2/3%, 3%) raises-satisfies is-unit-contract-fail - num-within-abs(2%) raises-satisfies is-unit-contract-fail - num-within-rel(2%) raises-satisfies is-unit-contract-fail end check "Units with bigint powers": @@ -164,3 +164,50 @@ check "Units with bigint powers": num-sqrt(num-sqrt(num-sqrt(num-sqr(num-sqr(num-sqr(1%)))))) is 1% end +|# + +check "Within builtins": + within(2%) raises-satisfies is-unit-contract-fail + + within(0.5)(3%, 5/2%) is true + within(0.5)(3%, ~2.999999%) is true + within(0.5)(3%, 2%) is true + within(0.5)(3%, 1%) is false + within(0.5)([list: 1%, 1%], [list: 1%, 4%]) is false + within(0.5)([list: 1%, 2%], [list: 1%, 2.5%]) is true + + within(0.5)(3%, 2) is false + within(0.5)(3%, 2%

) is false + + within-abs(1%)(2%, 5/2%) is true + within-abs(1%)(2%, ~2.99999%) is true + within-abs(1%)(2%, 3%) is true + within-abs(1%)(2%, 4%) is false + within-abs(1%)([list: 1%, 2%], [list: 1%, 4%]) is false + within-abs(1%)([list: 1%, 3.5%], [list: 1%, 4%]) is true + + within-abs(1)(2, 3%) is false + within-abs(1%)(2, 3%) is false + within-abs(1)(2%, 3%

) is false + within-abs(1%

)(2%, 3%) is false + + num-within-rel(2%) raises-satisfies is-unit-contract-fail + + num-within-rel(0.5)(3%, 5/2%) is true + num-within-rel(0.5)(3%, ~2.999999%) is true + num-within-rel(0.5)(3%, 2%) is true + num-within-rel(0.5)(3%, 1%) is false + + num-within-rel(0.5)(3%, 2) raises-satisfies E.is-incompatible-units + num-within-rel(0.5)(3%, 2%

) raises-satisfies E.is-incompatible-units + + num-within-abs(1%)(2%, 5/2%) is true + num-within-abs(1%)(2%, ~2.99999%) is true + num-within-abs(1%)(2%, 3%) is true + num-within-abs(1%)(2%, 4%) is false + + num-within-abs(1)(2, 3%) raises-satisfies E.is-incompatible-units + num-within-abs(1%)(2, 3%) raises-satisfies E.is-incompatible-units + num-within-abs(1)(2%, 3%

) raises-satisfies E.is-incompatible-units + num-within-abs(1%

)(2%, 3%) raises-satisfies E.is-incompatible-units +end From 7854c1987ac2ad10d4bbb6d40e6fc140a0ebf37f Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 8 Jul 2019 12:48:18 -0400 Subject: [PATCH 78/84] Remove error message TODOs --- src/arr/trove/error.arr | 2 -- tests/pyret/main2.arr | 2 -- tests/pyret/tests/test-units.arr | 7 +++++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index da44e27fce..e5984b043a 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2472,7 +2472,6 @@ data RuntimeError: end end | incompatible-units(reason, u, v) with: - # TODO Fix spacing in render-fancy-reason (and maybe render-reason) method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast) block: base-err-msg = [ED.para: ED.text("The units "), @@ -2550,7 +2549,6 @@ data RuntimeError: base-err-msg])] end | invalid-unit-state(opname, n, desc) with: - # TODO: update method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): [ED.error: cases(O.Option) maybe-stack-loc(0, false): diff --git a/tests/pyret/main2.arr b/tests/pyret/main2.arr index a01a08d62e..1066fce74c 100644 --- a/tests/pyret/main2.arr +++ b/tests/pyret/main2.arr @@ -1,4 +1,3 @@ -#| import file("./tests/test-strings.arr") as _ import file("./tests/test-format.arr") as _ import file("./tests/test-numbers.arr") as _ @@ -42,5 +41,4 @@ import file("./tests/test-tail-call.arr") as _ import file("./tests/test-parse.arr") as _ import file("./tests/test-parse-errors.arr") as _ import file("./tests/test-flatness.arr") as _ -|# import file("./tests/test-units.arr") as _ diff --git a/tests/pyret/tests/test-units.arr b/tests/pyret/tests/test-units.arr index 39c48daf08..a93a287f0c 100644 --- a/tests/pyret/tests/test-units.arr +++ b/tests/pyret/tests/test-units.arr @@ -5,7 +5,6 @@ fun is-unit-contract-fail(result): C.is-fail(result) and C.is-failure-at-arg(result.reason) and C.is-unit-fail(result.reason.reason) end -#| check "Unit equality": 2 == 2% is false @@ -28,6 +27,11 @@ check "Unit equality": (10% / 1%) + 1 is 11 0% == 0% is false + + 2% =~ 2% is true + 2% =~ 2% is false + 2% <=> 2% is true + 2% <=> 2% is false end check "Arithmetic binops": @@ -164,7 +168,6 @@ check "Units with bigint powers": num-sqrt(num-sqrt(num-sqrt(num-sqr(num-sqr(num-sqr(1%)))))) is 1% end -|# check "Within builtins": within(2%) raises-satisfies is-unit-contract-fail From 75a1dc99de0ccc178f9b51f87e1e62c3ef14a015 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 9 Jul 2019 15:59:37 -0400 Subject: [PATCH 79/84] Fix identical equality --- src/js/base/runtime.js | 2 +- t.arr | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 t.arr diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 3301ffccf5..154a0cdd47 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2446,7 +2446,7 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return thisRuntime.ffi.unknown.app('Methods', v1, v2); } else if (jsnums.isRoughnum(v1) && jsnums.isRoughnum(v2)) { return thisRuntime.ffi.unknown.app('Roughnums', v1, v2); - } else if (v1 === v2) { + } else if (v1 === v2 || (jsnums.isPyretNumber(v1) && jsnums.isPyretNumber(v2) && jsnums.equals(v1, v2))) { return thisRuntime.ffi.equal; } else { return thisRuntime.ffi.notEqual.app("", v1, v2); diff --git a/t.arr b/t.arr deleted file mode 100644 index b48c555107..0000000000 --- a/t.arr +++ /dev/null @@ -1 +0,0 @@ -num-within-rel(0.00001)(3%, ~3) From 4348552dea86dc15be8738cb34156d7d96dadc8b Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Mon, 15 Jul 2019 14:16:39 -0400 Subject: [PATCH 80/84] slight changes in error messages --- src/arr/trove/error.arr | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index e5984b043a..aa7464e76a 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2446,8 +2446,8 @@ data RuntimeError: ED.highlight(ED.text("unit"), [ED.locs: ast.l], 0), ED.text(" annotated a "), ED.highlight(ED.text("base type"), [ED.locs: ast.ann.l], 1), - ED.text(" which does not support units.")]] - | none => + ED.text(" which does not support units. Consider removing the unit annotation or changing the base type.")]] + | none => [ED.error: ed-intro("unit annotation", self.loc, -1, true), ED.cmcode(self.loc), @@ -2517,7 +2517,7 @@ data RuntimeError: | none => default-err-msg end else: - default-err-msg + base-err-msg end | none => [ED.sequence: @@ -2550,6 +2550,13 @@ data RuntimeError: end | invalid-unit-state(opname, n, desc) with: method render-fancy-reason(self, maybe-stack-loc, src-available, maybe-ast): + base-err-msg = [ED.sequence: + [ED.para: + ED.text("The " + self.opname + " function failed.")], + [ED.para: + ED.code(ED.text(tostring(self.n))), + ED.text(" is an invalid argument because: "), + ED.text(self.desc)]] [ED.error: cases(O.Option) maybe-stack-loc(0, false): | some(loc) => @@ -2569,15 +2576,10 @@ data RuntimeError: ED.code(ED.text(tostring(self.n))), ED.text(" is an invalid argument because: "), ED.text(self.desc)]] + else: + base-err-msg end - | none => - [ED.sequence: - [ED.para: - ED.text("The " + self.opname + " function failed.")], - [ED.para: - ED.code(ED.text(tostring(self.n))), - ED.text(" is an invalid argument because: "), - ED.text(self.desc)]] + | none => base-err-msg end] end, method render-reason(self): From a7885362db617a4ad3b9c5c78ea39fce057922f3 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 16 Jul 2019 11:36:41 -0400 Subject: [PATCH 81/84] Fix equality bug --- src/js/base/runtime.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 154a0cdd47..11620ddb0d 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -2440,13 +2440,16 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom // JS function from Pyret values to Pyret equality answers function identical3(v1, v2) { + var v1IsRough = jsnums.isRoughnum(v1) + var v2IsRough = jsnums.isRoughnum(v2) + if (isFunction(v1) && isFunction(v2)) { return thisRuntime.ffi.unknown.app("Functions", v1, v2); } else if (isMethod(v1) && isMethod(v2)) { return thisRuntime.ffi.unknown.app('Methods', v1, v2); - } else if (jsnums.isRoughnum(v1) && jsnums.isRoughnum(v2)) { + } else if (v1IsRough && v2IsRough) { return thisRuntime.ffi.unknown.app('Roughnums', v1, v2); - } else if (v1 === v2 || (jsnums.isPyretNumber(v1) && jsnums.isPyretNumber(v2) && jsnums.equals(v1, v2))) { + } else if (v1 === v2 || (!(v1IsRough || v2IsRough) && jsnums.isPyretNumber(v1) && jsnums.isPyretNumber(v2) && jsnums.equals(v1, v2, NumberErrbacks))) { return thisRuntime.ffi.equal; } else { return thisRuntime.ffi.notEqual.app("", v1, v2); From 7c663026f7b3a9078ffbdb0737beba72a7e8d23b Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Tue, 16 Jul 2019 12:37:42 -0400 Subject: [PATCH 82/84] Fix bug in equal3 for within() checks on strings We need to return quickly if the arguments are identical and are not numbers, but need to avoid the identical-ness check for numbers so that we can return false in the case of within(1%)(1%, 1%). The previous code had a logic bug where it skipped identical-ness checks on all within() calls, when it should only skip them for within() calls on numbers --- src/js/base/runtime.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index 11620ddb0d..a1732c771a 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -1987,8 +1987,11 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom } curLeft = current.left; curRight = current.right; + var isIdentical = thisRuntime.ffi.isEqual(identical3(curLeft, curRight)) - if (!fromWithin && thisRuntime.ffi.isEqual(identical3(curLeft, curRight))) { + if (!fromWithin && isIdentical) { + continue; + } else if (isIdentical && !(isNumber(curLeft) || isNumber(curRight)) && !jsnums.isUnitnum(tol)) { continue; } else if (isNumber(curLeft) && isNumber(curRight)) { if (!jsnums.checkUnit(jsnums.getUnit(curLeft), jsnums.getUnit(curRight))) { From 8637982d7708efc4edba22cfde9fbc2a8e88961a Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 17 Jul 2019 14:09:59 -0400 Subject: [PATCH 83/84] Add tests for within() comparisons of strings, fix error message grammar --- src/arr/trove/error.arr | 2 +- tests/pyret/tests/test-within.arr | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/arr/trove/error.arr b/src/arr/trove/error.arr index aa7464e76a..734d66fbcc 100644 --- a/src/arr/trove/error.arr +++ b/src/arr/trove/error.arr @@ -2443,7 +2443,7 @@ data RuntimeError: ed-intro("unit annotation", self.loc, -1, true), ED.cmcode(self.loc), [ED.para: - ED.highlight(ED.text("unit"), [ED.locs: ast.l], 0), + ED.highlight(ED.text("The unit " + self.unit-str), [ED.locs: ast.l], 0), ED.text(" annotated a "), ED.highlight(ED.text("base type"), [ED.locs: ast.ann.l], 1), ED.text(" which does not support units. Consider removing the unit annotation or changing the base type.")]] diff --git a/tests/pyret/tests/test-within.arr b/tests/pyret/tests/test-within.arr index 1bedd762be..fcf504c868 100644 --- a/tests/pyret/tests/test-within.arr +++ b/tests/pyret/tests/test-within.arr @@ -181,3 +181,25 @@ check "all within variants are defined": within-abs-now3 does-not-raise within3 does-not-raise end + +check "comparing strings": + within(0)("dog", "dog") is true + within(0)("dog", "Dog") is false + within(0.5)("dog", "Dog") is true + within(0.25)("dog", "Dog") is false + + within-abs(0)("dog", "dog") is true + within-abs(0)("dog", "Dog") is false + within-abs(1)("dog", "Dog") is true + within-abs(1)("dog", "DOg") is false + + within(0)("dog", "dog") is true + within(0)("dog", "Dog") is false + within(0.5)("dog", "Dog") is true + within(0.25)("dog", "Dog") is false + + within-abs(0)("dog", "dog") is true + within-abs(0)("dog", "Dog") is false + within-abs(1)("dog", "Dog") is true + within-abs(1)("dog", "DOg") is false +end From b849f93c5badbd8fa12433cf86cf342b8f58ef22 Mon Sep 17 00:00:00 2001 From: Ben Muschol Date: Wed, 17 Jul 2019 14:15:00 -0400 Subject: [PATCH 84/84] Add comment explaining logic of equalHelp() --- src/js/base/runtime.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index a1732c771a..c06d3580a4 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -1992,6 +1992,10 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom if (!fromWithin && isIdentical) { continue; } else if (isIdentical && !(isNumber(curLeft) || isNumber(curRight)) && !jsnums.isUnitnum(tol)) { + // when this equality check is from a within call and the tolerance + // has a unit, we can't just check for equality because we need to + // check for unit-mismatches between the tolerance and compared values + // see commits 7c66302 and 7ddfc67 continue; } else if (isNumber(curLeft) && isNumber(curRight)) { if (!jsnums.checkUnit(jsnums.getUnit(curLeft), jsnums.getUnit(curRight))) {