From c34ef5de73e9a8d8c2d76ba914c301d58c625707 Mon Sep 17 00:00:00 2001 From: Marius Andra Date: Wed, 21 Aug 2024 16:45:40 +0200 Subject: [PATCH] upvalues --- hogvm/__tests__/0.hog | 2 +- hogvm/__tests__/__snapshots__/0.hoge | 4 +- hogvm/__tests__/__snapshots__/0.stdout | 6 + hogvm/__tests__/__snapshots__/catch.hoge | 6 +- hogvm/__tests__/__snapshots__/exceptions.hoge | 9 +- .../__tests__/__snapshots__/exceptions.stdout | 8 ++ .../__tests__/__snapshots__/functionVars.hoge | 13 +- .../__snapshots__/functionVars.stdout | 7 +- hogvm/__tests__/__snapshots__/functions.hoge | 28 ++-- hogvm/__tests__/__snapshots__/mandelbrot.hoge | 15 ++- hogvm/__tests__/__snapshots__/operations.hoge | 2 +- hogvm/__tests__/exceptions.hog | 69 +++++----- hogvm/__tests__/functionVars.hog | 30 ++--- hogvm/__tests__/loops.hog | 2 - hogvm/__tests__/mandelbrot.hog | 48 ++++--- hogvm/__tests__/operations.hog | 122 ++++++++--------- hogvm/__tests__/recursion.hog | 12 ++ hogvm/__tests__/scope.hog | 64 +++++++++ hogvm/python/debugger.py | 26 +++- hogvm/python/execute.py | 51 ++++++- hogvm/python/objects.py | 6 +- hogvm/python/operation.py | 2 + hogvm/python/test/test_execute.py | 2 + hogvm/typescript/src/execute.ts | 67 +++++++++- hogvm/typescript/src/objects.ts | 28 +++- hogvm/typescript/src/operation.ts | 2 + posthog/hogql/bytecode.py | 125 +++++++++++++++--- 27 files changed, 540 insertions(+), 216 deletions(-) create mode 100644 hogvm/__tests__/recursion.hog create mode 100644 hogvm/__tests__/scope.hog diff --git a/hogvm/__tests__/0.hog b/hogvm/__tests__/0.hog index dce7d720184f0..c4f75a50eed0d 100644 --- a/hogvm/__tests__/0.hog +++ b/hogvm/__tests__/0.hog @@ -1,4 +1,4 @@ -// TODO: implement closures +// TODO: closing of upvalues //fn returnCallable(a) { // return x -> x * a diff --git a/hogvm/__tests__/__snapshots__/0.hoge b/hogvm/__tests__/__snapshots__/0.hoge index 8bda882db8864..fe06ab2e2a3f5 100644 --- a/hogvm/__tests__/__snapshots__/0.hoge +++ b/hogvm/__tests__/__snapshots__/0.hoge @@ -1 +1,3 @@ -["_h"] +["_h", 33, 3, 52, "outer", 0, 1, 18, 55, 0, 2, "print", 1, 35, 33, 4, 56, 0, 55, 0, 2, "print", 1, 35, 31, 38, 53, 1, +true, 0, 52, "inner", 0, 2, 19, 55, 0, 2, "print", 1, 35, 55, 1, 54, 0, 35, 55, 0, 2, "print", 1, 35, 31, 38, 53, 2, +true, 0, true, 1, 36, 0, 2, "print", 1, 35, 36, 2, 54, 0, 35, 36, 0, 2, "print", 1, 35, 35, 35, 35] diff --git a/hogvm/__tests__/__snapshots__/0.stdout b/hogvm/__tests__/__snapshots__/0.stdout index e69de29bb2d1d..dd622fe392d88 100644 --- a/hogvm/__tests__/__snapshots__/0.stdout +++ b/hogvm/__tests__/__snapshots__/0.stdout @@ -0,0 +1,6 @@ +3 +3 +3 +4 +4 +4 diff --git a/hogvm/__tests__/__snapshots__/catch.hoge b/hogvm/__tests__/__snapshots__/catch.hoge index 2a0e7dda5643a..bc9845b8623e7 100644 --- a/hogvm/__tests__/__snapshots__/catch.hoge +++ b/hogvm/__tests__/__snapshots__/catch.hoge @@ -1,6 +1,6 @@ -["_h", 52, "FishError", 1, 8, 36, 0, 32, "FishError", 2, "HogError", 2, 38, 53, 52, "FoodError", 1, 8, 36, 0, 32, -"FoodError", 2, "HogError", 2, 38, 53, 50, 11, 32, "You forgot to feed your fish", 36, 0, 54, 1, 49, 51, 39, 55, 36, 2, -32, "type", 45, 32, "FoodError", 36, 3, 11, 40, 16, 36, 2, 32, "message", 45, 32, "Problem with your food: ", 2, +["_h", 52, "FishError", 1, 0, 8, 36, 0, 32, "FishError", 2, "HogError", 2, 38, 53, 0, 52, "FoodError", 1, 0, 8, 36, 0, +32, "FoodError", 2, "HogError", 2, 38, 53, 0, 50, 11, 32, "You forgot to feed your fish", 36, 0, 54, 1, 49, 51, 39, 55, +36, 2, 32, "type", 45, 32, "FoodError", 36, 3, 11, 40, 16, 36, 2, 32, "message", 45, 32, "Problem with your food: ", 2, "concat", 2, 2, "print", 1, 35, 39, 25, 32, "FishError", 36, 3, 11, 40, 16, 36, 2, 32, "message", 45, 32, "Problem with your fish: ", 2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35, 50, 11, 32, "Your fish are hungry", 36, 1, 54, 1, 49, 51, 39, 55, 36, 2, 32, "type", 45, 32, "FoodError", 36, 3, 11, 40, 16, 36, 2, diff --git a/hogvm/__tests__/__snapshots__/exceptions.hoge b/hogvm/__tests__/__snapshots__/exceptions.hoge index 50b051687791f..0961e0f0ed549 100644 --- a/hogvm/__tests__/__snapshots__/exceptions.hoge +++ b/hogvm/__tests__/__snapshots__/exceptions.hoge @@ -5,4 +5,11 @@ 35, 50, 14, 32, "try again", 2, "print", 1, 35, 2, "Error", 0, 49, 51, 39, 22, 36, 0, 32, "type", 45, 32, " was the exception", 36, 0, 2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35, 32, "------------------", 2, "print", 1, 35, 50, 14, 32, "try again", 2, "print", 1, 35, 2, "Error", 0, 49, 51, 39, 17, 36, 0, 32, "type", 45, 32, -"No var for error", 2, "print", 1, 35, 39, 2, 35, 49, 35, 35, 32, "------------------", 2, "print", 1, 35] +"No var for error", 2, "print", 1, 35, 39, 2, 35, 49, 35, 35, 32, "------------------", 2, "print", 1, 35, 52, "third", +0, 0, 14, 32, "Throwing in third", 2, "print", 1, 35, 32, "Threw in third", 2, "Error", 1, 49, 31, 38, 53, 0, 52, +"second", 0, 1, 13, 32, "second", 2, "print", 1, 35, 55, 0, 54, 0, 35, 31, 38, 53, 1, true, 0, 52, "first", 0, 1, 13, +32, "first", 2, "print", 1, 35, 55, 0, 54, 0, 35, 31, 38, 53, 1, true, 1, 52, "base", 0, 1, 43, 32, "base", 2, "print", +1, 35, 50, 9, 55, 0, 54, 0, 35, 51, 39, 25, 36, 0, 32, "type", 45, 36, 0, 32, "Caught in base: ", 2, "concat", 2, 2, +"print", 1, 35, 36, 0, 49, 39, 2, 35, 49, 35, 35, 31, 38, 53, 1, true, 2, 50, 9, 36, 3, 54, 0, 35, 51, 39, 22, 36, 4, +32, "type", 45, 36, 4, 32, "Caught in root: ", 2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35, 32, "The end", +2, "print", 1, 35, 32, "------------------", 2, "print", 1, 35, 35, 35, 35, 35] diff --git a/hogvm/__tests__/__snapshots__/exceptions.stdout b/hogvm/__tests__/__snapshots__/exceptions.stdout index 1ade0100367b3..058fb3ffb3cd9 100644 --- a/hogvm/__tests__/__snapshots__/exceptions.stdout +++ b/hogvm/__tests__/__snapshots__/exceptions.stdout @@ -10,3 +10,11 @@ Error('An error occurred') was the exception try again No var for error ------------------ +base +first +second +Throwing in third +Caught in base: Error('Threw in third') +Caught in root: Error('Threw in third') +The end +------------------ diff --git a/hogvm/__tests__/__snapshots__/functionVars.hoge b/hogvm/__tests__/__snapshots__/functionVars.hoge index 970ad3674cf18..b0ea215f1572f 100644 --- a/hogvm/__tests__/__snapshots__/functionVars.hoge +++ b/hogvm/__tests__/__snapshots__/functionVars.hoge @@ -1,5 +1,8 @@ -["_h", 52, "execFunction", 0, 8, 32, "execFunction", 2, "print", 1, 35, 31, 38, 53, 52, "execFunctionNested", 0, 27, 52, -"execFunction", 0, 8, 32, "execFunctionNew", 2, "print", 1, 35, 31, 38, 53, 32, "execFunctionNested", 2, "print", 1, 35, -36, 0, 54, 0, 35, 31, 38, 35, 53, 36, 0, 54, 0, 35, 36, 1, 54, 0, 35, 36, 0, 54, 0, 35, 32, "--------", 2, "print", 1, -35, 52, "lambda", 1, 6, 33, 2, 36, 0, 8, 38, 53, 36, 2, 2, "print", 1, 35, 33, 2, 36, 2, 54, 1, 2, "print", 1, 35, 33, -8, 36, 2, 54, 1, 2, "print", 1, 35, 35, 35, 35] +["_h", 52, "execFunction", 0, 0, 8, 32, "execFunction", 2, "print", 1, 35, 31, 38, 53, 0, 52, "execFunctionNested", 0, +0, 29, 52, "execFunction", 0, 0, 8, 32, "execFunctionNew", 2, "print", 1, 35, 31, 38, 53, 0, 32, "execFunctionNested", +2, "print", 1, 35, 36, 0, 54, 0, 35, 31, 38, 35, 53, 0, 36, 0, 54, 0, 35, 36, 1, 54, 0, 35, 36, 0, 54, 0, 35, 32, +"--------", 2, "print", 1, 35, 52, "secondExecFunction", 0, 0, 8, 32, "secondExecFunction", 2, "print", 1, 35, 31, 38, +53, 0, 52, "secondExecFunctionNested", 0, 1, 13, 32, "secondExecFunctionNested", 2, "print", 1, 35, 55, 0, 54, 0, 35, +31, 38, 53, 1, true, 2, 36, 2, 54, 0, 35, 36, 3, 54, 0, 35, 36, 2, 54, 0, 35, 32, "--------", 2, "print", 1, 35, 52, +"lambda", 1, 0, 6, 33, 2, 36, 0, 8, 38, 53, 0, 36, 4, 2, "print", 1, 35, 33, 2, 36, 4, 54, 1, 2, "print", 1, 35, 33, 8, +36, 4, 54, 1, 2, "print", 1, 35, 35, 35, 35, 35, 35] diff --git a/hogvm/__tests__/__snapshots__/functionVars.stdout b/hogvm/__tests__/__snapshots__/functionVars.stdout index d80e5db76d7df..2e4d0a65866e7 100644 --- a/hogvm/__tests__/__snapshots__/functionVars.stdout +++ b/hogvm/__tests__/__snapshots__/functionVars.stdout @@ -3,6 +3,11 @@ execFunctionNested execFunctionNew execFunction -------- -{'__hogClosure__': true, 'callable': {'__hogCallable__': 'local', 'argCount': 1, 'ip': 70, 'name': 'lambda'}, 'captured': []} +secondExecFunction +secondExecFunctionNested +secondExecFunction +secondExecFunction +-------- +{'__hogClosure__': true, 'callable': {'__hogCallable__': 'local', 'argCount': 1, 'upvalueCount': 0, 'ip': 136, 'name': 'lambda'}, 'upvalues': []} 4 16 diff --git a/hogvm/__tests__/__snapshots__/functions.hoge b/hogvm/__tests__/__snapshots__/functions.hoge index 195804f2bb0f5..e7fdc67834d74 100644 --- a/hogvm/__tests__/__snapshots__/functions.hoge +++ b/hogvm/__tests__/__snapshots__/functions.hoge @@ -1,14 +1,14 @@ -["_h", 32, "-- test functions --", 2, "print", 1, 35, 52, "add", 2, 6, 36, 0, 36, 1, 6, 38, 53, 52, "add2", 2, 9, 36, 0, -36, 1, 6, 36, 2, 38, 35, 53, 52, "mult", 2, 6, 36, 0, 36, 1, 8, 38, 53, 52, "noArgs", 0, 12, 32, "basdfasdf", 33, 3, 33, -2, 6, 36, 1, 38, 35, 35, 53, 52, "empty", 0, 2, 31, 38, 53, 52, "empty2", 0, 2, 31, 38, 53, 52, "empty3", 0, 2, 31, 38, -53, 52, "noReturn", 0, 14, 33, 1, 33, 2, 36, 1, 36, 0, 6, 31, 38, 35, 35, 35, 53, 52, "emptyReturn", 0, 2, 31, 38, 53, -52, "emptyReturnBeforeOtherStuff", 0, 10, 31, 38, 33, 2, 33, 2, 6, 35, 31, 38, 53, 52, -"emptyReturnBeforeOtherStuffNoSemicolon", 0, 6, 33, 2, 33, 2, 6, 38, 53, 52, "ifThenReturn", 0, 8, 30, 40, 2, 31, 38, -33, 4, 38, 53, 33, 4, 33, 3, 36, 0, 54, 2, 2, "print", 1, 35, 33, 1, 33, 1, 36, 0, 54, 2, 33, 100, 33, 4, 33, 3, 36, 0, -54, 2, 6, 6, 2, "print", 1, 35, 36, 3, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 4, 54, 0, 47, 3, 35, 33, -1, 2, -"print", 1, 35, 36, 5, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 6, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, -36, 7, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 8, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 9, 54, 0, -47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 10, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 11, 54, 0, 47, 3, 35, 33, --1, 2, "print", 1, 35, 33, 2, 33, 1, 33, 2, 36, 0, 54, 2, 33, 100, 33, 4, 33, 3, 36, 0, 54, 2, 6, 6, 36, 2, 54, 2, 2, -"print", 1, 35, 33, 10, 33, 1, 33, 2, 36, 1, 54, 2, 33, 100, 33, 4, 33, 3, 36, 1, 54, 2, 6, 6, 36, 2, 54, 2, 2, "print", -1, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35] +["_h", 32, "-- test functions --", 2, "print", 1, 35, 52, "add", 2, 0, 6, 36, 0, 36, 1, 6, 38, 53, 0, 52, "add2", 2, 0, +9, 36, 0, 36, 1, 6, 36, 2, 38, 35, 53, 0, 52, "mult", 2, 0, 6, 36, 0, 36, 1, 8, 38, 53, 0, 52, "noArgs", 0, 0, 12, 32, +"basdfasdf", 33, 3, 33, 2, 6, 36, 1, 38, 35, 35, 53, 0, 52, "empty", 0, 0, 2, 31, 38, 53, 0, 52, "empty2", 0, 0, 2, 31, +38, 53, 0, 52, "empty3", 0, 0, 2, 31, 38, 53, 0, 52, "noReturn", 0, 0, 14, 33, 1, 33, 2, 36, 1, 36, 0, 6, 31, 38, 35, +35, 35, 53, 0, 52, "emptyReturn", 0, 0, 2, 31, 38, 53, 0, 52, "emptyReturnBeforeOtherStuff", 0, 0, 10, 31, 38, 33, 2, +33, 2, 6, 35, 31, 38, 53, 0, 52, "emptyReturnBeforeOtherStuffNoSemicolon", 0, 0, 6, 33, 2, 33, 2, 6, 38, 53, 0, 52, +"ifThenReturn", 0, 0, 8, 30, 40, 2, 31, 38, 33, 4, 38, 53, 0, 33, 4, 33, 3, 36, 0, 54, 2, 2, "print", 1, 35, 33, 1, 33, +1, 36, 0, 54, 2, 33, 100, 33, 4, 33, 3, 36, 0, 54, 2, 6, 6, 2, "print", 1, 35, 36, 3, 54, 0, 47, 3, 35, 33, -1, 2, +"print", 1, 35, 36, 4, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 5, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, +36, 6, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 7, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 8, 54, 0, +47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 9, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 36, 10, 54, 0, 47, 3, 35, 33, +-1, 2, "print", 1, 35, 36, 11, 54, 0, 47, 3, 35, 33, -1, 2, "print", 1, 35, 33, 2, 33, 1, 33, 2, 36, 0, 54, 2, 33, 100, +33, 4, 33, 3, 36, 0, 54, 2, 6, 6, 36, 2, 54, 2, 2, "print", 1, 35, 33, 10, 33, 1, 33, 2, 36, 1, 54, 2, 33, 100, 33, 4, +33, 3, 36, 1, 54, 2, 6, 6, 36, 2, 54, 2, 2, "print", 1, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35] diff --git a/hogvm/__tests__/__snapshots__/mandelbrot.hoge b/hogvm/__tests__/__snapshots__/mandelbrot.hoge index 16a5ff691f13c..f7db422370b76 100644 --- a/hogvm/__tests__/__snapshots__/mandelbrot.hoge +++ b/hogvm/__tests__/__snapshots__/mandelbrot.hoge @@ -1,7 +1,8 @@ -["_h", 52, "mandelbrot", 3, 93, 34, 0.0, 34, 0.0, 33, 0, 36, 0, 36, 5, 15, 33, 4, 36, 4, 36, 4, 8, 36, 3, 36, 3, 8, 6, -16, 3, 2, 40, 44, 36, 2, 36, 4, 36, 4, 8, 36, 3, 36, 3, 8, 7, 6, 36, 1, 36, 4, 36, 3, 33, 2, 8, 8, 6, 36, 6, 37, 3, 36, -7, 37, 4, 33, 1, 36, 5, 6, 37, 5, 35, 35, 39, -67, 36, 0, 36, 5, 11, 40, 5, 32, " ", 38, 39, 3, 32, "#", 38, 31, 38, 35, -35, 35, 53, 33, 80, 33, 24, 34, -2.0, 34, 1.0, 34, -1.0, 34, 1.0, 33, 30, 33, 0, 36, 2, 36, 8, 15, 40, 87, 32, "", 33, -0, 36, 1, 36, 10, 15, 40, 59, 36, 3, 36, 3, 36, 4, 7, 36, 1, 36, 10, 9, 8, 6, 36, 5, 36, 5, 36, 6, 7, 36, 2, 36, 8, 9, -8, 6, 36, 7, 36, 12, 36, 11, 36, 0, 54, 3, 36, 13, 36, 9, 2, "concat", 2, 37, 9, 33, 1, 36, 10, 6, 37, 10, 35, 35, 35, -39, -66, 36, 9, 2, "print", 1, 35, 33, 1, 36, 8, 6, 37, 8, 35, 35, 39, -94, 35, 35, 35, 35, 35, 35, 35, 35, 35] +["_h", 52, "mandelbrot", 3, 0, 93, 34, 0.0, 34, 0.0, 33, 0, 36, 0, 36, 5, 15, 33, 4, 36, 4, 36, 4, 8, 36, 3, 36, 3, 8, +6, 16, 3, 2, 40, 44, 36, 2, 36, 4, 36, 4, 8, 36, 3, 36, 3, 8, 7, 6, 36, 1, 36, 4, 36, 3, 33, 2, 8, 8, 6, 36, 6, 37, 3, +36, 7, 37, 4, 33, 1, 36, 5, 6, 37, 5, 35, 35, 39, -67, 36, 0, 36, 5, 11, 40, 5, 32, " ", 38, 39, 3, 32, "#", 38, 31, 38, +35, 35, 35, 53, 0, 52, "main", 0, 1, 120, 33, 80, 33, 24, 34, -2.0, 34, 1.0, 34, -1.0, 34, 1.0, 33, 30, 33, 0, 36, 1, +36, 7, 15, 40, 87, 32, "", 33, 0, 36, 0, 36, 9, 15, 40, 59, 36, 2, 36, 2, 36, 3, 7, 36, 0, 36, 9, 9, 8, 6, 36, 4, 36, 4, +36, 5, 7, 36, 1, 36, 7, 9, 8, 6, 36, 6, 36, 11, 36, 10, 55, 0, 54, 3, 36, 12, 36, 8, 2, "concat", 2, 37, 8, 33, 1, 36, +9, 6, 37, 9, 35, 35, 35, 39, -66, 36, 8, 2, "print", 1, 35, 33, 1, 36, 7, 6, 37, 7, 35, 35, 39, -94, 31, 38, 35, 35, 35, +35, 35, 35, 35, 35, 53, 1, true, 0, 36, 1, 54, 0, 35, 35, 35] diff --git a/hogvm/__tests__/__snapshots__/operations.hoge b/hogvm/__tests__/__snapshots__/operations.hoge index 203d2cf424681..8d14268109860 100644 --- a/hogvm/__tests__/__snapshots__/operations.hoge +++ b/hogvm/__tests__/__snapshots__/operations.hoge @@ -1,4 +1,4 @@ -["_h", 52, "test", 1, 11, 36, 0, 2, "jsonStringify", 1, 2, "print", 1, 35, 31, 38, 53, 32, +["_h", 52, "test", 1, 0, 11, 36, 0, 2, "jsonStringify", 1, 2, "print", 1, 35, 31, 38, 53, 0, 32, "-- test the most common expressions --", 2, "print", 1, 35, 33, 2, 33, 1, 6, 36, 0, 54, 1, 35, 33, 2, 33, 1, 7, 36, 0, 54, 1, 35, 33, 2, 33, 3, 8, 36, 0, 54, 1, 35, 33, 2, 33, 3, 9, 36, 0, 54, 1, 35, 33, 2, 33, 3, 10, 36, 0, 54, 1, 35, 33, 2, 33, 1, 3, 2, 36, 0, 54, 1, 35, 33, 0, 33, 1, 4, 2, 36, 0, 54, 1, 35, 33, 0, 33, 1, 3, 2, 36, 0, 54, 1, 35, 33, 2, 33, diff --git a/hogvm/__tests__/exceptions.hog b/hogvm/__tests__/exceptions.hog index 5313645465e36..1b29aadfab1e2 100644 --- a/hogvm/__tests__/exceptions.hog +++ b/hogvm/__tests__/exceptions.hog @@ -35,45 +35,36 @@ try { print('------------------') -// TODO: need closures to access +fn third() { + print('Throwing in third') + throw Error('Threw in third') +} + +fn second() { + print('second') + third() +} + +fn first() { + print('first') + second() +} -//fn third() { -// print('Throwing in third') -// throw Error('Threw in third') -//} -// -//fn second() { -// print('second') -// third() -//} -// -//fn first() { -// print('first') -// second() -//} -// -//fn base() { -// print('base') -// try { -// first() -// } catch (e) { -// print(f'Caught in base: {e}') -// throw e -// } -//} -// -//try { -// base() -//} catch (e) { -// print(f'Caught in root: {e}') -//} -//print('The end') +fn base() { + print('base') + try { + first() + } catch (e) { + print(f'Caught in base: {e}') + throw e + } +} -//print('------------------') +try { + base() +} catch (e) { + print(f'Caught in root: {e}') +} +print('The end') -// TODO: throw undefined global access in Hog -//try { -// print({key: 'value'}) -//} catch(e) { -// print(e) -//} +print('------------------') diff --git a/hogvm/__tests__/functionVars.hog b/hogvm/__tests__/functionVars.hog index 2dbed4e1975d4..b195ec91967b4 100644 --- a/hogvm/__tests__/functionVars.hog +++ b/hogvm/__tests__/functionVars.hog @@ -14,22 +14,20 @@ execFunction() execFunctionNested() execFunction() -// TODO: implement closures to access variables declared outside -// -//print('--------') -// -//fn secondExecFunction() { -// print('secondExecFunction') -//} -// -//fn secondExecFunctionNested() { -// print('secondExecFunctionNested') -// secondExecFunction() -//} -// -//secondExecFunction() -//secondExecFunctionNested() -//secondExecFunction() +print('--------') + +fn secondExecFunction() { + print('secondExecFunction') +} + +fn secondExecFunctionNested() { + print('secondExecFunctionNested') + secondExecFunction() +} + +secondExecFunction() +secondExecFunctionNested() +secondExecFunction() print('--------') diff --git a/hogvm/__tests__/loops.hog b/hogvm/__tests__/loops.hog index 26813c099e810..f43fe7fcc1c39 100644 --- a/hogvm/__tests__/loops.hog +++ b/hogvm/__tests__/loops.hog @@ -13,8 +13,6 @@ print('-- test for loop --') for (let i := 0; i < 3; i := i + 1) { print(i) // prints 3 times } - // TODO: test that this throws - // print(i) -- global does not print } print('-- test emptier for loop --') diff --git a/hogvm/__tests__/mandelbrot.hog b/hogvm/__tests__/mandelbrot.hog index 27a3902d3d51a..144ddf701ab87 100644 --- a/hogvm/__tests__/mandelbrot.hog +++ b/hogvm/__tests__/mandelbrot.hog @@ -16,31 +16,29 @@ fn mandelbrot(re, im, max_iter) { } } -// TODO: implement closures to access variables declared outside +fn main() { + let width := 80 + let height := 24 + let xmin := -2.0 + let xmax := 1.0 + let ymin := -1.0 + let ymax := 1.0 + let max_iter := 30 -//fn main() { -let width := 80 -let height := 24 -let xmin := -2.0 -let xmax := 1.0 -let ymin := -1.0 -let ymax := 1.0 -let max_iter := 30 - -let y := 0 -while(y < height) { - let row := '' - let x := 0 - while (x < width) { - let re := x / width * (xmax - xmin) + xmin - let im := y / height * (ymax - ymin) + ymin - let letter := mandelbrot(re, im, max_iter) - row := concat(row, letter) - x := x + 1 + let y := 0 + while(y < height) { + let row := '' + let x := 0 + while (x < width) { + let re := x / width * (xmax - xmin) + xmin + let im := y / height * (ymax - ymin) + ymin + let letter := mandelbrot(re, im, max_iter) + row := concat(row, letter) + x := x + 1 + } + print(row) + y := y + 1 } - print(row) - y := y + 1 } -//} -// -//main() + +main() diff --git a/hogvm/__tests__/operations.hog b/hogvm/__tests__/operations.hog index d7d0d51671d9a..8445bf17cee67 100644 --- a/hogvm/__tests__/operations.hog +++ b/hogvm/__tests__/operations.hog @@ -3,64 +3,64 @@ fn test(val) { } print('-- test the most common expressions --') -test(1 + 2) -- 3 -test(1 - 2) -- -1 -test(3 * 2) -- 6 -test(3 / 2) -- 1.5 -test(3 % 2) -- 1 -test(1 and 2) -- true -test(1 or 0) -- true -test(1 and 0) -- false -test(1 or (0 and 1) or 2) -- true -test((1 and 0) and 1) -- false -test((1 or 2) and (1 or 2)) -- true -test(true) -- true -test(not true) -- false -test(false) -- false -test(null) -- null -test(3.14) -- 3.14 -test(1 = 2) -- false -test(1 == 2) -- false -test(1 != 2) -- true -test(1 < 2) -- true -test(1 <= 2) -- true -test(1 > 2) -- false -test(1 >= 2) -- false -test('a' like 'b') -- false -test('baa' like '%a%') -- true -test('baa' like '%x%') -- false -test('baa' ilike '%A%') -- true -test('baa' ilike '%C%') -- false -test('a' ilike 'b') -- false -test('a' not like 'b') -- true -test('a' not ilike 'b') -- true -test('a' in 'car') -- true -test('a' in 'foo') -- false -test('a' not in 'car') -- false -test(concat('arg', 'another')) -- 'arganother' -test(concat(1, NULL)) -- '1' -test(concat(true, false)) -- 'truefalse' -test(match('test', 'e.*')) -- true -test(match('test', '^e.*')) -- false -test(match('test', 'x.*')) -- false -test('test' =~ 'e.*') -- true -test('test' !~ 'e.*') -- false -test('test' =~ '^e.*') -- false -test('test' !~ '^e.*') -- true -test('test' =~ 'x.*') -- false -test('test' !~ 'x.*') -- true -test('test' ~* 'EST') -- true -test('test' =~* 'EST') -- true -test('test' !~* 'EST') -- false -test(toString(1)) -- '1' -test(toString(1.5)) -- '1.5' -test(toString(true)) -- 'true' -test(toString(null)) -- 'null' -test(toString('string')) -- 'string' -test(toInt('1')) -- 1 -test(toInt('bla')) -- null -test(toFloat('1.2')) -- 1.2 -test(toFloat('bla')) -- null -test(toUUID('asd')) -- 'asd' -test(1 == null) -- false -test(1 != null) -- true +test(1 + 2) // 3 +test(1 - 2) // -1 +test(3 * 2) // 6 +test(3 / 2) // 1.5 +test(3 % 2) // 1 +test(1 and 2) // true +test(1 or 0) // true +test(1 and 0) // false +test(1 or (0 and 1) or 2) // true +test((1 and 0) and 1) // false +test((1 or 2) and (1 or 2)) // true +test(true) // true +test(not true) // false +test(false) // false +test(null) // null +test(3.14) // 3.14 +test(1 = 2) // false +test(1 == 2) // false +test(1 != 2) // true +test(1 < 2) // true +test(1 <= 2) // true +test(1 > 2) // false +test(1 >= 2) // false +test('a' like 'b') // false +test('baa' like '%a%') // true +test('baa' like '%x%') // false +test('baa' ilike '%A%') // true +test('baa' ilike '%C%') // false +test('a' ilike 'b') // false +test('a' not like 'b') // true +test('a' not ilike 'b') // true +test('a' in 'car') // true +test('a' in 'foo') // false +test('a' not in 'car') // false +test(concat('arg', 'another')) // 'arganother' +test(concat(1, NULL)) // '1' +test(concat(true, false)) // 'truefalse' +test(match('test', 'e.*')) // true +test(match('test', '^e.*')) // false +test(match('test', 'x.*')) // false +test('test' =~ 'e.*') // true +test('test' !~ 'e.*') // false +test('test' =~ '^e.*') // false +test('test' !~ '^e.*') // true +test('test' =~ 'x.*') // false +test('test' !~ 'x.*') // true +test('test' ~* 'EST') // true +test('test' =~* 'EST') // true +test('test' !~* 'EST') // false +test(toString(1)) // '1' +test(toString(1.5)) // '1.5' +test(toString(true)) // 'true' +test(toString(null)) // 'null' +test(toString('string')) // 'string' +test(toInt('1')) // 1 +test(toInt('bla')) // null +test(toFloat('1.2')) // 1.2 +test(toFloat('bla')) // null +test(toUUID('asd')) // 'asd' +test(1 == null) // false +test(1 != null) // true diff --git a/hogvm/__tests__/recursion.hog b/hogvm/__tests__/recursion.hog new file mode 100644 index 0000000000000..7e05f4a00c9dd --- /dev/null +++ b/hogvm/__tests__/recursion.hog @@ -0,0 +1,12 @@ +// TODO: get this working +// +//fn fibonacci(number) { +// if (number < 2) { +// return number; +// } else { +// return fibonacci(number - 1) + fibonacci(number - 2); +// } +//} +// +//print(fibonacci(6)) +// diff --git a/hogvm/__tests__/scope.hog b/hogvm/__tests__/scope.hog new file mode 100644 index 0000000000000..46ecb95872527 --- /dev/null +++ b/hogvm/__tests__/scope.hog @@ -0,0 +1,64 @@ +let a := 3 + +fn outerA() { + print(a) + a := 4 + print(a) +} + +fn innerA() { + print(a) + outerA() + print(a) +} + +print(a) +innerA() +print(a) + +print('--------') + +let b := {'key': 3} + +fn outerB() { + print(b) + b.key := 4 + print(b) +} + +fn innerB() { + print(b) + outerB() + print(b) +} + +print(b) +innerB() +print(b) + +print('--------') + +fn outerC() { + let x := 'outside' + fn innerC() { + print(x) + } + innerC() +} +outerC() + +print('--------') + +fn myFunctionOuter() { + let b := 3 + fn myFunction(a) { + return a + b + } + print(myFunction(2)) + print(myFunction(3)) +} + +myFunctionOuter() + +print('--------') + diff --git a/hogvm/python/debugger.py b/hogvm/python/debugger.py index 355e5f3322f0f..1ae5335854c87 100644 --- a/hogvm/python/debugger.py +++ b/hogvm/python/debugger.py @@ -140,7 +140,11 @@ def print_symbol(symbol: Operation, ip: int, bytecode: list, stack: list, call_s case Operation.GET_LOCAL: return f"GET_LOCAL({bytecode[ip+1]})" case Operation.SET_LOCAL: - return f"GET_LOCAL({bytecode[ip + 1]}, {stack[-1]})" + return f"SET_LOCAL({bytecode[ip + 1]}, {stack[-1]})" + case Operation.GET_UPVALUE: + return f"GET_UPVALUE({bytecode[ip+1]})" + case Operation.SET_UPVALUE: + return f"SET_UPVALUE({bytecode[ip + 1]})" case Operation.GET_PROPERTY: return f"GET_PROPERTY({stack[-2]}, {stack[-1]})" case Operation.GET_PROPERTY_NULLISH: @@ -164,7 +168,7 @@ def print_symbol(symbol: Operation, ip: int, bytecode: list, stack: list, call_s case Operation.DECLARE_FN: return f"DECLARE_FN({bytecode[ip+1]}, args={bytecode[ip+2]}, ops={bytecode[ip+3]})" case Operation.CALLABLE: - return f"CALLABLE({bytecode[ip+1]}, args={bytecode[ip+2]}, ops={bytecode[ip+3]})" + return f"CALLABLE({bytecode[ip+1]}, args={bytecode[ip+2]}, upvalues={bytecode[ip+3]}, ops={bytecode[ip+4]})" case Operation.CLOSURE: return f"CLOSURE" case Operation.CALL_GLOBAL: @@ -260,6 +264,10 @@ def color_bytecode(bytecode: list) -> list: add = ["op.GET_LOCAL", f"index: {bytecode[ip+1]}"] case Operation.SET_LOCAL: add = ["op.SET_LOCAL", f"index: {bytecode[ip+1]}"] + case Operation.GET_UPVALUE: + add = ["op.GET_UPVALUE", f"index: {bytecode[ip+1]}"] + case Operation.SET_UPVALUE: + add = ["op.SET_UPVALUE", f"index: {bytecode[ip+1]}"] case Operation.GET_PROPERTY: add = ["op.GET_PROPERTY"] case Operation.GET_PROPERTY_NULLISH: @@ -281,9 +289,19 @@ def color_bytecode(bytecode: list) -> list: case Operation.DECLARE_FN: add = ["op.DECLARE_FN", f"name: {bytecode[ip+1]}", f"args: {bytecode[ip+2]}", f"ops: {bytecode[ip+3]}"] case Operation.CALLABLE: - add = ["op.CALLABLE", f"name: {bytecode[ip+1]}", f"args: {bytecode[ip+2]}", f"ops: {bytecode[ip+3]}"] + add = [ + "op.CALLABLE", + f"name: {bytecode[ip+1]}", + f"args: {bytecode[ip+2]}", + f"upvalues: {bytecode[ip+3]}", + f"ops: {bytecode[ip+4]}", + ] case Operation.CLOSURE: - add = ["op.CLOSURE"] + upvalue_count = bytecode[ip + 1] + add = ["op.CLOSURE", f"upvalues: {upvalue_count}"] + for i in range(upvalue_count): + add.append(f"is_local({i}): {bytecode[ip + 2 + i * 2]}") + add.append(f"index({i}): {bytecode[ip + 2 + i * 2 + 1]}") case Operation.CALL_LOCAL: add = ["op.CALL_LOCAL", f"args: {bytecode[ip+1]}"] case Operation.CALL_GLOBAL: diff --git a/hogvm/python/execute.py b/hogvm/python/execute.py index 50e324005a5fd..08a02bfebfe03 100644 --- a/hogvm/python/execute.py +++ b/hogvm/python/execute.py @@ -198,7 +198,8 @@ def check_timeout(): new_hog_closure( { "__hogCallable__": "stl", - "argCount": 0, # TODO + "argCount": 0, + "upvalueCount": 0, "ip": -1, "name": chain[0], } @@ -304,11 +305,12 @@ def check_timeout(): case Operation.CALLABLE: name = next_token() # TODO: do we need it? it could change as the variable is reassigned arg_count = next_token() + upvalue_count = next_token() body_length = next_token() callable = { "__hogCallable__": "local", "argCount": arg_count, - # "upvalueCount": upvalue_count, + "upvalueCount": upvalue_count, "ip": frame.ip + 1, "name": name, } @@ -317,9 +319,48 @@ def check_timeout(): case Operation.CLOSURE: callable = pop_stack() closure = new_hog_closure(callable) - # for _ in range(callable["upvalueCount"]): - # closure["upvalues"].append([next_token(), next_token()]) + stack_start = frame.stack_start + upvalue_count = next_token() + if upvalue_count != callable["upvalueCount"]: + raise HogVMException( + f"Invalid upvalue count. Expected {callable['upvalueCount']}, got {upvalue_count}" + ) + for _ in range(callable["upvalueCount"]): + is_local, index = next_token(), next_token() + if is_local: + + def new_upvalue(index): + return { + "__hogUpvalue__": True, + "location": index, + } + + def capture_upvalue(index): + upvalue = new_upvalue(index) + return upvalue + + closure["upvalues"].append(capture_upvalue(stack_start + index)) + else: + closure["upvalues"].append(frame.closure["upvalues"][index]) push_stack(closure) + case Operation.GET_UPVALUE: + index = next_token() + closure = frame.closure + if index >= len(closure["upvalues"]): + raise HogVMException(f"Invalid upvalue index: {index}") + upvalue = closure["upvalues"][index] + if not isinstance(upvalue, dict) or upvalue.get("__hogUpvalue__") is None: + raise HogVMException(f"Invalid upvalue: {upvalue}") + push_stack(stack[upvalue["location"]]) + case Operation.SET_UPVALUE: + index = next_token() + closure = frame.closure + if index >= len(closure["upvalues"]): + raise HogVMException(f"Invalid upvalue index: {index}") + upvalue = closure["upvalues"][index] + if not isinstance(upvalue, dict) or upvalue.get("__hogUpvalue__") is None: + raise HogVMException(f"Invalid upvalue: {upvalue}") + stack[upvalue["location"]] = pop_stack() case Operation.CALL_GLOBAL: check_timeout() name = next_token() @@ -335,7 +376,7 @@ def check_timeout(): { "__hogCallable__": "stl", "argCount": arg_len, - # "upvalueCount": 0, + "upvalueCount": 0, "ip": -1, "name": name, } diff --git a/hogvm/python/objects.py b/hogvm/python/objects.py index 4da6ad5e41480..8726c71bb586b 100644 --- a/hogvm/python/objects.py +++ b/hogvm/python/objects.py @@ -43,12 +43,12 @@ def is_hog_callable(obj: Any) -> bool: def is_hog_closure(obj: Any) -> bool: - return isinstance(obj, dict) and "__hogClosure__" in obj and "callable" in obj and "captured" in obj + return isinstance(obj, dict) and "__hogClosure__" in obj and "callable" in obj and "upvalues" in obj -def new_hog_closure(callable: dict, captured: Optional[dict] = None) -> dict: +def new_hog_closure(callable: dict, upvalues: Optional[dict] = None) -> dict: return { "__hogClosure__": True, "callable": callable, - "captured": captured or [], + "upvalues": upvalues or [], } diff --git a/hogvm/python/operation.py b/hogvm/python/operation.py index 0d2a7feccb2de..97fda3ad490ea 100644 --- a/hogvm/python/operation.py +++ b/hogvm/python/operation.py @@ -59,3 +59,5 @@ class Operation(int, Enum): CALLABLE = 52 CLOSURE = 53 CALL_LOCAL = 54 + GET_UPVALUE = 55 + SET_UPVALUE = 56 diff --git a/hogvm/python/test/test_execute.py b/hogvm/python/test/test_execute.py index 694c39eb8c119..7149d05de4a9b 100644 --- a/hogvm/python/test/test_execute.py +++ b/hogvm/python/test/test_execute.py @@ -495,6 +495,7 @@ def test_bytecode_functions(self): op.CALLABLE, "add", 2, + 0, 6, op.GET_LOCAL, 0, @@ -503,6 +504,7 @@ def test_bytecode_functions(self): op.PLUS, op.RETURN, op.CLOSURE, + 0, op.INTEGER, 4, op.INTEGER, diff --git a/hogvm/typescript/src/execute.ts b/hogvm/typescript/src/execute.ts index b95061165f430..6d18704ec2440 100644 --- a/hogvm/typescript/src/execute.ts +++ b/hogvm/typescript/src/execute.ts @@ -1,6 +1,16 @@ import RE2 from 're2' -import { CallFrame, HogCallable, isHogCallable, isHogClosure, isHogError, newHogClosure, ThrowFrame } from './objects' +import { + CallFrame, + HogCallable, + HogUpValue, + isHogCallable, + isHogClosure, + isHogError, + isHogUpValue, + newHogClosure, + ThrowFrame, +} from './objects' import { Operation } from './operation' import { ASYNC_STL, STL } from './stl/stl' import { @@ -337,6 +347,7 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { __hogCallable__: 'async', name: chain[0], argCount: 0, // TODO + upvalueCount: 0, ip: -1, } satisfies HogCallable) ) @@ -345,6 +356,7 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { newHogClosure({ __hogCallable__: 'async', argCount: 0, // TODO + upvalueCount: 0, ip: -1, } satisfies HogCallable) ) @@ -353,6 +365,7 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { newHogClosure({ __hogCallable__: 'stl', argCount: 0, // TODO + upvalueCount: 0, ip: -1, name: chain[0], } satisfies HogCallable) @@ -453,11 +466,12 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { case Operation.CALLABLE: { const name = next() // TODO: do we need it? it could change as the variable is reassigned const argCount = next() + const upvalueCount = next() const bodyLength = next() const callable = { __hogCallable__: 'local', argCount, - // upvalueCount, + upvalueCount, ip: frame.ip + 1, name, } satisfies HogCallable @@ -466,7 +480,54 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { break } case Operation.CLOSURE: { - pushStack(newHogClosure(popStack(), [])) + const callable = popStack() + if (!isHogCallable(callable)) { + throw new HogVMException(`Invalid callable: ${JSON.stringify(callable)}`) + } + const upvalueCount = next() + const upvalues: HogUpValue[] = [] + if (upvalueCount !== callable.upvalueCount) { + throw new HogVMException( + `Invalid upvalue count. Expected ${callable.upvalueCount}, got ${upvalueCount}` + ) + } + const stackStart = frame.stackStart + for (let i = 0; i < callable.upvalueCount; i++) { + const [isLocal, index] = [next(), next()] + if (isLocal) { + upvalues.push({ + __hogUpValue__: true, + index: stackStart + index, + } satisfies HogUpValue) + } else { + upvalues.push(frame.closure.upvalues[index]) + } + } + pushStack(newHogClosure(callable, upvalues)) + break + } + case Operation.GET_UPVALUE: { + const index = next() + if (index >= frame.closure.upvalues.length) { + throw new HogVMException(`Invalid upvalue index: ${index}`) + } + const upvalue = frame.closure.upvalues[index] + if (!isHogUpValue(upvalue)) { + throw new HogVMException(`Invalid upvalue: ${upvalue}`) + } + pushStack(stack[upvalue.index]) + break + } + case Operation.SET_UPVALUE: { + const index = next() + if (index >= frame.closure.upvalues.length) { + throw new HogVMException(`Invalid upvalue index: ${index}`) + } + const upvalue = frame.closure.upvalues[index] + if (!isHogUpValue(upvalue)) { + throw new HogVMException(`Invalid upvalue: ${upvalue}`) + } + stack[upvalue.index] = popStack() break } case Operation.CALL_GLOBAL: { diff --git a/hogvm/typescript/src/objects.ts b/hogvm/typescript/src/objects.ts index 2e237a2e5cff4..d80f1de35a02e 100644 --- a/hogvm/typescript/src/objects.ts +++ b/hogvm/typescript/src/objects.ts @@ -23,13 +23,19 @@ export interface HogCallable { __hogCallable__: 'local' | 'stl' | 'async' | 'main' name?: string argCount: number + upvalueCount: number ip: number } +export interface HogUpValue { + __hogUpValue__: true + index: number +} + export interface HogClosure { __hogClosure__: true callable: HogCallable - captured: any[] + upvalues: HogUpValue[] } export interface CallFrame { @@ -44,7 +50,6 @@ export interface ThrowFrame { stackLen: number catchIp: number } - export function isHogDate(obj: any): obj is HogDate { return obj && typeof obj === 'object' && '__hogDate__' in obj && 'year' in obj && 'month' in obj && 'day' in obj } @@ -67,17 +72,28 @@ export function newHogError(type: string, message: string, payload?: Record int: + for i, upvalue in enumerate(self.upvalues): + if upvalue.index == index and upvalue.is_local == is_local: + return i + self.upvalues.append(UpValue(index, is_local)) + return len(self.upvalues) - 1 + + def _resolve_upvalue(self, name: str) -> int: + if not self.enclosing: + return -1 + + for index, local in reversed(list(enumerate(self.enclosing.locals))): + if local.name == name: + return self._add_upvalue(index, True) + + upvalue = self.enclosing._resolve_upvalue(name) + if upvalue != -1: + return self._add_upvalue(upvalue, False) + + return -1 + def visit_field(self, node: ast.Field): + ops: list[str | int] = [] for index, local in reversed(list(enumerate(self.locals))): if local.name == node.chain[0]: - if len(node.chain) == 1: - return [Operation.GET_LOCAL, index] - else: - ops: list[str | int] = [Operation.GET_LOCAL, index] - for element in node.chain[1:]: - if isinstance(element, int): - ops.extend([Operation.INTEGER, element, Operation.GET_PROPERTY]) - else: - ops.extend([Operation.STRING, str(element), Operation.GET_PROPERTY]) - return ops + ops = [Operation.GET_LOCAL, index] + break + + if len(ops) == 0: + arg = self._resolve_upvalue(str(node.chain[0])) + if arg != -1: + ops = [Operation.GET_UPVALUE, arg] + + if len(ops) > 0: + if len(node.chain) > 1: + for element in node.chain[1:]: + if isinstance(element, int): + ops.extend([Operation.INTEGER, element, Operation.GET_PROPERTY]) + else: + ops.extend([Operation.STRING, str(element), Operation.GET_PROPERTY]) + return ops + + # Did not find a local nor an upvalue, must be a global. + chain = [] for element in reversed(node.chain): chain.extend([Operation.STRING, element]) @@ -284,10 +322,13 @@ def visit_call(self, node: ast.Call): if found_local_with_name: field = self.visit(ast.Field(chain=[node.name])) - response.extend([*field, Operation.CALL_LOCAL, len(node.args)]) - else: - response.extend([Operation.CALL_GLOBAL, node.name, len(node.args)]) - return response + return [*response, *field, Operation.CALL_LOCAL, len(node.args)] + + upvalue = self._resolve_upvalue(node.name) + if upvalue != -1: + return [*response, Operation.GET_UPVALUE, upvalue, Operation.CALL_LOCAL, len(node.args)] + + return [*response, Operation.CALL_GLOBAL, node.name, len(node.args)] def visit_program(self, node: ast.Program): response = [] @@ -607,6 +648,27 @@ def visit_variable_assignment(self, node: ast.VariableAssignment): return ops + upvalue_index = self._resolve_upvalue(str(chain[0])) + if upvalue_index != -1: + # Set an upvalue + if len(node.left.chain) == 1: + return [*self.visit(cast(AST, node.right)), Operation.SET_UPVALUE, upvalue_index] + + # else set a property on an upvalue object + ops: list = [Operation.GET_UPVALUE, upvalue_index] + for element in chain[1:-1]: + if isinstance(element, int): + ops.extend([Operation.INTEGER, element, Operation.GET_PROPERTY]) + else: + ops.extend([Operation.STRING, str(element), Operation.GET_PROPERTY]) + if isinstance(chain[-1], int): + ops.extend([Operation.INTEGER, chain[-1], *self.visit(node.right), Operation.SET_PROPERTY]) + else: + ops.extend([Operation.STRING, str(chain[-1]), *self.visit(node.right), Operation.SET_PROPERTY]) + + return ops + # print(arg) + # # raise QueryError(f'Variable "{name}" not declared in this scope. Can not assign to globals.') raise QueryError(f"Can not assign to this type of expression") @@ -620,9 +682,23 @@ def visit_function(self, node: ast.Function): elif not isinstance(node.body, ast.ReturnStatement): body = ast.Block(declarations=[node.body, ast.ReturnStatement(expr=None)]) - bytecode = create_bytecode(body, None, node.params, self.context, self) + compiler = BytecodeBuilder(self.supported_functions, node.params, self.context, self) + bytecode = compiler.visit(body) + self._declare_local(node.name) - return [Operation.CALLABLE, node.name, len(node.params), len(bytecode), *bytecode, Operation.CLOSURE] + ops = [ + Operation.CALLABLE, + node.name, + len(node.params), + len(compiler.upvalues), + len(bytecode), + *bytecode, + Operation.CLOSURE, + len(compiler.upvalues), + ] + for upvalue in compiler.upvalues: + ops.extend([upvalue.is_local, upvalue.index]) + return ops def visit_lambda(self, node: ast.Lambda): # add an implicit return if none at the end of the function @@ -636,8 +712,21 @@ def visit_lambda(self, node: ast.Lambda): else: expr = ast.ReturnStatement(expr=expr) - bytecode = create_bytecode(expr, None, node.args, self.context, self) - return [Operation.CALLABLE, "lambda", len(node.args), len(bytecode), *bytecode, Operation.CLOSURE] + compiler = BytecodeBuilder(self.supported_functions, node.args, self.context, self) + bytecode = compiler.visit(expr) + ops = [ + Operation.CALLABLE, + "lambda", + len(node.args), + len(compiler.upvalues), + len(bytecode), + *bytecode, + Operation.CLOSURE, + len(compiler.upvalues), + ] + for upvalue in compiler.upvalues: + ops.extend([upvalue.is_local, upvalue.index]) + return ops def visit_dict(self, node: ast.Dict): response = []