From 08f63a1dbc9fbc3759dfd773dcff2b7fe610b9e2 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Mon, 2 Nov 2026 13:25:58 +0100 Subject: [PATCH 1/5] Derive AST binding scopes and separate global variable nodes --- Dockerfile | 1 + doc/README.md | 1 + doc/SUMMARY.md | 1 + doc/advanced/ast-bindings.md | 48 ++++ doc/advanced/register-vm.md | 3 +- src/prometheus/ast.lua | 116 ++++++++-- src/prometheus/bindinganalysis.lua | 209 ++++++++++++++++++ src/prometheus/cff/compile_top.lua | 17 +- src/prometheus/cff/compiler.lua | 33 +-- src/prometheus/cff/emit.lua | 8 - src/prometheus/cff/expressions.lua | 1 + src/prometheus/cff/expressions/variable.lua | 1 - src/prometheus/cff/native.lua | 15 +- src/prometheus/cff/register.lua | 19 -- src/prometheus/cff/statements/assignment.lua | 3 +- .../cff/statements/break_statement.lua | 1 - src/prometheus/cff/statements/compound.lua | 1 - .../cff/statements/continue_statement.lua | 1 - .../cff/statements/for_in_statement.lua | 1 - .../cff/statements/for_statement.lua | 3 - .../cff/statements/function_declaration.lua | 2 - .../statements/local_function_declaration.lua | 1 - .../statements/local_variable_declaration.lua | 1 - .../cff/statements/repeat_statement.lua | 1 - src/prometheus/cff/strings.lua | 1 - src/prometheus/cff/upvalue.lua | 19 -- src/prometheus/parser.lua | 12 +- src/prometheus/pipeline.lua | 3 + src/prometheus/scope.lua | 125 ++--------- src/prometheus/steps/AntiTamper.lua | 1 - src/prometheus/steps/ConstantArray.lua | 41 +--- src/prometheus/steps/EncryptStrings.lua | 17 +- src/prometheus/steps/ProxifyLocals.lua | 54 +++-- src/prometheus/steps/SplitStrings.lua | 10 - src/prometheus/steps/Watermark.lua | 1 - src/prometheus/steps/WrapInFunction.lua | 1 - src/prometheus/unparser.lua | 26 ++- src/prometheus/visitast.lua | 13 +- src/prometheus/vm/analysis.lua | 28 +-- src/prometheus/vm/expressions.lua | 2 +- src/prometheus/vm/flatten.lua | 53 ++++- src/prometheus/vm/statements.lua | 2 +- src/prometheus/vm/strings.lua | 3 - tests/harness/ast-bindings.lua | 153 +++++++++++++ 44 files changed, 693 insertions(+), 360 deletions(-) create mode 100644 doc/advanced/ast-bindings.md create mode 100644 src/prometheus/bindinganalysis.lua create mode 100644 tests/harness/ast-bindings.lua diff --git a/Dockerfile b/Dockerfile index c0057c9b..784ae330 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ WORKDIR /app COPY . /app RUN lua5.1 tests/harness/metadata.lua \ + && timeout 60s lua5.1 tests/harness/ast-bindings.lua \ && timeout 60s lua5.1 tests/harness/numbers-to-expressions.lua \ && timeout 60s lua5.1 tests/harness/cff-arity.lua \ && timeout 60s lua5.1 tests/harness/cff-native.lua \ diff --git a/doc/README.md b/doc/README.md index 9e448eb0..a0b3aa4c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -14,6 +14,7 @@ This documentation covers: - configuration and presets - all built-in obfuscation steps - embedding Prometheus as a library +- [AST binding and transform guidance](advanced/ast-bindings.md) ## Who this is for diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md index 5d0a170f..b8d5f2a7 100644 --- a/doc/SUMMARY.md +++ b/doc/SUMMARY.md @@ -36,4 +36,5 @@ ## Advanced * [How the Pipeline Works](advanced/how-the-pipeline-works.md) +* [AST Bindings](advanced/ast-bindings.md) * [Register VM](advanced/register-vm.md) diff --git a/doc/advanced/ast-bindings.md b/doc/advanced/ast-bindings.md new file mode 100644 index 00000000..f2036b58 --- /dev/null +++ b/doc/advanced/ast-bindings.md @@ -0,0 +1,48 @@ +# AST Bindings + +The AST represents each local binding with a stable `LocalSymbol`. A symbol is the identity of one declaration; its name can change during transformations, and binding analysis derives its lexical scope and slot. Local reference nodes point to the same symbol, so transforms do not need to keep references synchronized with scope tables. + +## Constructing local bindings + +Create a symbol for each local declaration, then use that same object for every reference to the binding: + +```lua +local Ast = require("prometheus.ast") +local symbol = Ast.LocalSymbol("value") + +local declaration = Ast.LocalVariableDeclaration({symbol}, { + Ast.NumberExpression(10), +}) +local reference = Ast.VariableExpression(symbol) +local write = Ast.AssignmentVariable(symbol) +``` + +`Ast.LocalFunctionDeclaration(symbol, args, body)` and `Ast.FunctionDeclaration(globalReference, indices, args, body)` also accept symbol-based inputs. `Ast.ForStatement(symbol, initial, final, increment, body)` and `Ast.ForInStatement(symbols, expressions, body)` define loop bindings. `Ast.Block(statements)` and `Ast.TopNode(body)` need no scope arguments. Function arguments are local symbols; use `Ast.VarargExpression()` for a vararg argument. The AST constructors that accept an existing scope and numeric ID remain available as compatibility construction adapters for existing generators. + +## Globals + +Globals are names, not local bindings. Use `Ast.GlobalVariableExpression(name)` for a read and `Ast.AssignmentGlobalVariable(name)` for a write. Analysis fills their compatibility `scope` and `id` fields, while `.name` remains the source-level global name. `Ast.VariableExpression(scope, id)` and `Ast.AssignmentVariable(scope, id)` still act as construction adapters when the supplied scope is global. + +## Running binding analysis + +`require("prometheus.bindinganalysis")(ast)` rebuilds lexical metadata for a `TopNode` and returns an analysis record. `Parser:parse` runs it before returning, and `Unparser:unparse` runs it before output, so callers using those APIs directly get analyzed ASTs. AST transforms that need metadata before either entry point can invoke the analysis module themselves after making their changes. + +Analysis derives the legacy `scope`, `id`, declaration `ids`, and scope reference tables from symbols and tree structure. Treat those fields as compatibility data: do not use them as the identity of a local or manually adjust reference counts, upvalue counts, parent links, or scope levels. For a local reference, `.symbol` is authoritative. Global references use `.name` as their identity. + +The returned record exposes `declarations[symbol]`, `references[symbol]`, `reads[symbol]`, `writes[symbol]`, `captured[symbol]`, and `owners[scope]`. Read/write lists describe reference occurrences; declaration initialization is represented by the declaration itself. The pipeline refreshes analysis after each transformation and before renaming, and compiler entry points analyze their inputs. + +The unparser preserves existing local names where possible and changes colliding names as needed to preserve binding identity, even when obfuscating renaming has not been requested. + +## Visibility and captures + +The tree order determines visibility. A local declaration's initializer expressions are analyzed before that declaration becomes active, matching Lua's rule that a local is not visible in its own initializer. Local functions are active while their bodies are analyzed, allowing recursion. Function arguments and loop variables are active in their function or loop body. A reference is valid only while its symbol is active in the enclosing lexical path. References from a nested function are recorded as captures. + +Binding analysis does not widen a scope to make a reference valid. If a transform moves a reference outside the symbol's lexical region, it must also restructure the declarations and references so the binding is valid; analysis will report an inaccessible local symbol otherwise. + +## Moving and cloning nodes + +Moving a complete subtree is safe when it remains within the lexical region of every symbol it uses and the destination preserves the intended declaration order. Re-run binding analysis after structural changes that affect declarations, references, or scope ownership. + +Do not place one declaration symbol in two declaration nodes or share the same block object at multiple tree positions. Analysis rejects duplicate symbol declarations and repeated blocks. When cloning code that introduces a new local binding, create a fresh `Ast.LocalSymbol` for the clone and point its cloned references to that new symbol. Reusing the original symbol would make two declarations claim one binding identity. Global nodes can be cloned by name because globals are identified by their string names. + +Older transforms may still construct nodes using `scope` and numeric IDs. These adapters are supported for compatibility, but new transforms should create symbols and let binding analysis rebuild derived scope metadata instead of manually wiring references. The old scope-parent and reference mutation methods have been removed. Numeric IDs may change after every analysis; do not retain them across transformations. diff --git a/doc/advanced/register-vm.md b/doc/advanced/register-vm.md index ba712367..9d7057e6 100644 --- a/doc/advanced/register-vm.md +++ b/doc/advanced/register-vm.md @@ -6,7 +6,8 @@ compiler, or `loadstring` to execute the original source. ## Modules -- `vm/analysis.lua`: lexical scope ownership and captured-local analysis. +- `bindinganalysis.lua`: shared lexical ownership, visibility validation, and captured-local analysis. +- `vm/analysis.lua`: adapts binding analysis to VM scope and slot lookups. - `vm/compiler.lua`: function prototypes, constants, registers, and captures. - `vm/expressions.lua` and `vm/expressions_tables.lua`: expression lowering, value lists, calls, branches, and table constructors. diff --git a/src/prometheus/ast.lua b/src/prometheus/ast.lua index ef390dd7..d03cad1b 100644 --- a/src/prometheus/ast.lua +++ b/src/prometheus/ast.lua @@ -40,6 +40,7 @@ local AstKind = { -- Assignment Index AssignmentIndexing = "AssignmentIndexing"; AssignmentVariable = "AssignmentVariable"; + AssignmentGlobalVariable = "AssignmentGlobalVariable"; -- Expression Nodes BooleanExpression = "BooleanExpression"; @@ -69,6 +70,7 @@ local AstKind = { FunctionCallExpression = "FunctionCallExpression"; PassSelfFunctionCallExpression = "PassSelfFunctionCallExpression"; VariableExpression = "VariableExpression"; + GlobalVariableExpression = "GlobalVariableExpression"; FunctionLiteralExpression = "FunctionLiteralExpression"; TableConstructorExpression = "TableConstructorExpression"; @@ -111,6 +113,8 @@ local astKindExpressionLookup = { [AstKind.FunctionCallExpression] = 2; [AstKind.PassSelfFunctionCallExpression] = 2; [AstKind.VariableExpression] = 0; + [AstKind.GlobalVariableExpression] = 0; + [AstKind.AssignmentGlobalVariable] = 0; [AstKind.AssignmentVariable] = 0; [AstKind.FunctionLiteralExpression] = 3; [AstKind.TableConstructorExpression] = 3; @@ -194,6 +198,7 @@ end -- Create Statement Block function Ast.Block(statements, scope) + scope = scope or require("prometheus.scope"):new(); return { kind = AstKind.Block, statements = statements, @@ -328,7 +333,15 @@ function Ast.WhileStatement(body, condition, parentScope) end function Ast.ForInStatement(scope, vars, expressions, body, parentScope) + local symbols = {}; + if not scope.getSymbol then + symbols, expressions, body = scope, vars, expressions; + scope, vars = nil, {}; + else + for i, id in ipairs(vars) do symbols[i] = scope:getSymbol(id); end + end return { + symbols = symbols, kind = AstKind.ForInStatement, scope = scope, ids = vars, @@ -340,7 +353,13 @@ function Ast.ForInStatement(scope, vars, expressions, body, parentScope) end function Ast.ForStatement(scope, id, initialValue, finalValue, incrementBy, body, parentScope) + local symbol; + if scope.kind == "LocalSymbol" then + symbol, initialValue, finalValue, incrementBy, body = scope, id, initialValue, finalValue, incrementBy; + scope, id = nil, nil; + else symbol = scope:getSymbol(id); end return { + symbol = symbol, kind = AstKind.ForStatement, scope = scope, id = id, @@ -372,7 +391,21 @@ function Ast.IfStatement(condition, body, elseifs, elsebody) end function Ast.FunctionDeclaration(scope, id, indices, args, body) + local symbol, name; + if not scope.getSymbol then + local target = scope; + indices, args, body = id, indices, args; + symbol, name, scope, id = target.symbol, target.name, target.scope, target.id; + else + symbol = not scope.isGlobal and scope:getSymbol(id) or nil; + name = scope.isGlobal and scope:getVariableName(id) or nil; + end + for i, arg in ipairs(args) do + if arg.kind == "LocalSymbol" then args[i] = Ast.VariableExpression(arg); end + end return { + symbol = symbol, + name = name, kind = AstKind.FunctionDeclaration, scope = scope, baseScope = scope, @@ -382,26 +415,42 @@ function Ast.FunctionDeclaration(scope, id, indices, args, body) args = args, body = body, getName = function(self) - return self.scope:getVariableName(self.id); + return self.symbol and self.symbol.name or self.name; end, } end function Ast.LocalFunctionDeclaration(scope, id, args, body) + local symbol; + if scope.kind == "LocalSymbol" then + symbol, args, body, scope, id = scope, id, args, nil, nil; + else symbol = scope:getSymbol(id); end + for i, arg in ipairs(args) do + if arg.kind == "LocalSymbol" then args[i] = Ast.VariableExpression(arg); end + end return { + symbol = symbol, kind = AstKind.LocalFunctionDeclaration, scope = scope, id = id, args = args, body = body, getName = function(self) - return self.scope:getVariableName(self.id); + return self.symbol and self.symbol.name or self.name; end, } end function Ast.LocalVariableDeclaration(scope, ids, expressions) + local symbols = {}; + if not scope.getSymbol then + symbols, expressions, scope = scope, ids, nil; + ids = {}; + else + for i, id in ipairs(ids) do symbols[i] = scope:getSymbol(id); end + end return { + symbols = symbols, kind = AstKind.LocalVariableDeclaration, scope = scope, ids = ids, @@ -769,31 +818,58 @@ function Ast.FunctionCallExpression(base, args) } end -function Ast.VariableExpression(scope, id) - scope:addReference(id); +-- A symbol identifies a binding, independently of its name or lexical owner. +function Ast.LocalSymbol(name) + return { kind = "LocalSymbol", name = name }; +end + +local function localReference(kind, scope, id) + local symbol; + if scope.kind == "LocalSymbol" then + symbol, scope, id = scope, scope.scope, scope.id; + else + symbol = scope:getSymbol(id); + end return { - kind = AstKind.VariableExpression, - scope = scope, - id = id, - getName = function(self) - return self.scope.getVariableName(self.id); - end, - } + kind = kind, symbol = symbol, scope = scope, id = id, + getName = function(self) return self.symbol.name end, + }; +end + +function Ast.GlobalVariableExpression(name) + return {kind = AstKind.GlobalVariableExpression, name = name}; +end + +function Ast.AssignmentGlobalVariable(name) + return {kind = AstKind.AssignmentGlobalVariable, name = name}; +end + +-- The scope/id forms remain construction adapters for existing generators. +function Ast.VariableExpression(scope, id) + if scope.isGlobal then + local node = Ast.GlobalVariableExpression(scope:getVariableName(id)); + node.scope, node.id = scope, id; + return node; + end + return localReference(AstKind.VariableExpression, scope, id); end function Ast.AssignmentVariable(scope, id) - scope:addReference(id); - return { - kind = AstKind.AssignmentVariable, - scope = scope, - id = id, - getName = function(self) - return self.scope.getVariableName(self.id); - end, - } + if scope.isGlobal then + local node = Ast.AssignmentGlobalVariable(scope:getVariableName(id)); + node.scope, node.id = scope, id; + return node; + end + return localReference(AstKind.AssignmentVariable, scope, id); end +Ast.LocalVariableExpression = Ast.VariableExpression; +Ast.AssignmentLocalVariable = Ast.AssignmentVariable; + function Ast.FunctionLiteralExpression(args, body) + for i, arg in ipairs(args) do + if arg.kind == "LocalSymbol" then args[i] = Ast.VariableExpression(arg); end + end return { kind = AstKind.FunctionLiteralExpression, args = args, diff --git a/src/prometheus/bindinganalysis.lua b/src/prometheus/bindinganalysis.lua new file mode 100644 index 00000000..c4ea4f31 --- /dev/null +++ b/src/prometheus/bindinganalysis.lua @@ -0,0 +1,209 @@ +-- Rebuild lexical metadata from declarations and uses. Scope/id fields are +-- derived adapters for the lowering backends; symbols are binding identities. +local Ast = require('prometheus.ast') +local Scope = require('prometheus.scope') +local K = Ast.AstKind + +local binary = {} +for _, name in ipairs({'Or', 'And', 'LessThan', 'GreaterThan', 'LessThanOrEquals', + 'GreaterThanOrEquals', 'NotEquals', 'Equals', 'StrCat', 'Add', 'Sub', 'Mul', + 'Div', 'Mod', 'Pow'}) do binary[K[name .. 'Expression']] = true end +local compound = {} +for _, name in ipairs({'Add', 'Sub', 'Mul', 'Div', 'Mod', 'Pow', 'Concat'}) do + compound[K['Compound' .. name .. 'Statement']] = true +end + +return function(ast) + assert(ast.kind == K.TopNode, 'Binding analysis expects a TopNode') + ast.bindingAnalysis = nil + local global = ast.globalScope or Scope:newGlobal() + assert(global.isGlobal, 'TopNode requires a global environment scope') + ast.globalScope = global + local analysis = {owners = {}, captured = {}, declarations = {}, references = {}, reads = {}, writes = {}} + local initialized, blocks, active, loops = {}, {}, {}, {} + local expression, statement, block, functionBody + + local function initialize(scope, parent, owner) + assert(not initialized[scope], 'A lexical scope is shared by multiple blocks') + initialized[scope] = true + scope.parentScope, scope.level = parent, parent and parent.level + 1 or 0 + scope.children, scope.referenceCounts, scope.variablesFromHigherScopes = {}, {}, {} + scope.variables, scope.variablesLookup, scope.symbols, scope.skipIdLookup = {}, {}, {}, {} + if parent then parent.children[#parent.children + 1] = scope end + analysis.owners[scope] = owner or scope + end + initialize(global) + + local function bind(symbol, node, scope, declared) + assert(symbol and symbol.kind == 'LocalSymbol', 'Declaration requires a local symbol') + assert(symbol.name == nil or type(symbol.name) == 'string', 'Local symbol name must be a string') + assert(not analysis.declarations[symbol], 'Local symbol has multiple declarations: ' .. tostring(symbol.name)) + analysis.declarations[symbol] = node + local id = #scope.variables + 1 + local name = symbol.name or ('__prometheus_binding_' .. id) + symbol.scope, symbol.id, symbol.name = scope, id, name + scope.variables[id], scope.symbols[id], scope.variablesLookup[name] = name, symbol, id + active[symbol] = true + declared[#declared + 1] = symbol + return id + end + + local function reference(node, scope, owner) + local target, id + if node.kind == K.GlobalVariableExpression or node.kind == K.AssignmentGlobalVariable + or (node.kind == K.FunctionDeclaration and not node.symbol) then + assert(type(node.name) == 'string', 'Global reference requires a name') + target, id = global, global.variablesLookup[node.name] + if not id then + id = #global.variables + 1 + global.variables[id], global.variablesLookup[node.name] = node.name, id + end + else + local symbol = node.symbol + assert(symbol and active[symbol], 'Local binding is not accessible at this point: ' .. tostring(symbol and symbol.name)) + target, id = symbol.scope, symbol.id + local refs = analysis.references[symbol] or {} + refs[#refs + 1], analysis.references[symbol] = node, refs + local writes = node.kind == K.AssignmentVariable + or (node.kind == K.FunctionDeclaration and #node.indices == 0) + local uses = writes and analysis.writes or analysis.reads + uses[symbol] = uses[symbol] or {} + uses[symbol][#uses[symbol] + 1] = node + if analysis.owners[target] ~= owner then analysis.captured[symbol] = true end + end + node.scope, node.id = target, id + if node.kind == K.FunctionDeclaration then node.baseScope, node.baseId = target, id end + target.referenceCounts[id] = (target.referenceCounts[id] or 0) + 1 + local current = scope + while current and current ~= target do + local refs = current.variablesFromHigherScopes[target] or {} + current.variablesFromHigherScopes[target] = refs + refs[id] = (refs[id] or 0) + 1 + current = current.parentScope + end + assert(current == target, 'Local binding owner is not an enclosing scope') + end + + local function expressions(list, scope, owner) + for _, node in ipairs(list or {}) do expression(node, scope, owner) end + end + + function expression(node, scope, owner) + local kind = node.kind + if kind == K.VariableExpression or kind == K.AssignmentVariable + or kind == K.GlobalVariableExpression or kind == K.AssignmentGlobalVariable then + reference(node, scope, owner) + elseif binary[kind] then + expression(node.lhs, scope, owner); expression(node.rhs, scope, owner) + elseif kind == K.NotExpression or kind == K.NegateExpression or kind == K.LenExpression then + expression(node.rhs, scope, owner) + elseif kind == K.IndexExpression or kind == K.AssignmentIndexing then + expression(node.base, scope, owner); expression(node.index, scope, owner) + elseif kind == K.FunctionCallExpression or kind == K.PassSelfFunctionCallExpression then + expression(node.base, scope, owner); expressions(node.args, scope, owner) + elseif kind == K.FunctionLiteralExpression then + functionBody(node, scope) + elseif kind == K.TableConstructorExpression then + for _, entry in ipairs(node.entries) do + if entry.kind == K.KeyedTableEntry then expression(entry.key, scope, owner) end + expression(entry.value, scope, owner) + end + elseif kind == K.IfElseExpression then + expression(node.condition, scope, owner); expression(node.true_value, scope, owner) + for _, branch in ipairs(node.elseifs) do + expression(branch.condition, scope, owner); expression(branch.value, scope, owner) + end + expression(node.false_value, scope, owner) + else + assert(kind == K.BooleanExpression or kind == K.NumberExpression or kind == K.StringExpression + or kind == K.NilExpression or kind == K.VarargExpression, 'Unknown expression kind: ' .. tostring(kind)) + end + end + + function functionBody(node, parent) + local savedLoops = loops + loops = {} + block(node.body, parent, nil, function(scope, declared) + for _, arg in ipairs(node.args or {}) do + if arg.kind ~= K.VarargExpression then + arg.id = bind(arg.symbol, arg, scope, declared) + arg.scope = scope + end + end + end) + loops = savedLoops + end + + function block(node, parent, owner, before, after) + assert(node and node.kind == K.Block, 'Expected a block') + assert(not blocks[node], 'A block occurs multiple times in the AST') + blocks[node] = true + local scope = node.scope or Scope:new() + node.scope = scope + initialize(scope, parent, owner) + owner = owner or scope + local declared = {} + if before then before(scope, declared) end + for _, child in ipairs(node.statements) do statement(child, scope, owner, declared) end + if after then after(scope, owner) end + for _, symbol in ipairs(declared) do active[symbol] = nil end + end + + function statement(node, scope, owner, declared) + local kind = node.kind + if kind == K.LocalVariableDeclaration then + expressions(node.expressions, scope, owner) + node.scope, node.ids = scope, {} + for i, symbol in ipairs(node.symbols) do node.ids[i] = bind(symbol, node, scope, declared) end + elseif kind == K.LocalFunctionDeclaration then + node.id, node.scope = bind(node.symbol, node, scope, declared), scope + functionBody(node, scope) + elseif kind == K.FunctionDeclaration then + reference(node, scope, owner) + functionBody(node, scope) + elseif kind == K.AssignmentStatement then + expressions(node.lhs, scope, owner); expressions(node.rhs, scope, owner) + elseif compound[kind] then + expression(node.lhs, scope, owner); expression(node.rhs, scope, owner) + elseif kind == K.ReturnStatement then expressions(node.args, scope, owner) + elseif kind == K.FunctionCallStatement or kind == K.PassSelfFunctionCallStatement then + expression(node.base, scope, owner); expressions(node.args, scope, owner) + elseif kind == K.DoStatement then block(node.body, scope, owner) + elseif kind == K.IfStatement then + expression(node.condition, scope, owner); block(node.body, scope, owner) + for _, branch in ipairs(node.elseifs) do + expression(branch.condition, scope, owner); block(branch.body, scope, owner) + end + if node.elsebody then block(node.elsebody, scope, owner) end + elseif kind == K.WhileStatement or kind == K.RepeatStatement + or kind == K.ForStatement or kind == K.ForInStatement then + node.parentScope = scope + loops[#loops + 1] = node + if kind == K.WhileStatement then expression(node.condition, scope, owner) + elseif kind == K.ForStatement then + expression(node.initialValue, scope, owner); expression(node.finalValue, scope, owner) + expression(node.incrementBy, scope, owner) + elseif kind == K.ForInStatement then expressions(node.expressions, scope, owner) end + block(node.body, scope, owner, function(inner, locals) + if kind == K.ForStatement then + node.scope, node.id = inner, bind(node.symbol, node, inner, locals) + elseif kind == K.ForInStatement then + node.scope, node.ids = inner, {} + for i, symbol in ipairs(node.symbols) do node.ids[i] = bind(symbol, node, inner, locals) end + node.vars = node.ids + end + end, function(inner) + if kind == K.RepeatStatement then expression(node.condition, inner, owner) end + end) + loops[#loops] = nil + elseif kind == K.BreakStatement or kind == K.ContinueStatement then + assert(#loops > 0, 'Loop control outside a loop') + assert(not node.loop or node.loop == loops[#loops], 'Loop control targets a non-enclosing loop') + node.loop, node.scope = loops[#loops], scope + else assert(kind == K.NopStatement, 'Unknown statement kind: ' .. tostring(kind)) end + end + + functionBody(ast, global) + ast.bindingAnalysis = analysis + return analysis +end diff --git a/src/prometheus/cff/compile_top.lua b/src/prometheus/cff/compile_top.lua index b3a77c8b..eb518078 100644 --- a/src/prometheus/cff/compile_top.lua +++ b/src/prometheus/cff/compile_top.lua @@ -21,7 +21,9 @@ return function(Compiler) local varAccessLookup = lookupify{ AstKind.AssignmentVariable, + AstKind.AssignmentGlobalVariable, AstKind.VariableExpression, + AstKind.GlobalVariableExpression, AstKind.FunctionDeclaration, AstKind.LocalFunctionDeclaration, } @@ -49,9 +51,6 @@ return function(Compiler) end, nil, nil) self.varargReg = self:allocRegister(true); - scope:addReferenceToHigherScope(self.containerFuncScope, self.argsVar); - scope:addReferenceToHigherScope(self.scope, self.selectVar); - scope:addReferenceToHigherScope(self.scope, self.unpackVar); self:addStatement(self:setRegister(scope, self.varargReg, Ast.VariableExpression(self.containerFuncScope, self.argsVar)), {self.varargReg}, {}, false); self:compileBlock(node.body, 0); @@ -86,7 +85,6 @@ return function(Compiler) local scopeFuncDepth = self.scopeFunctionDepths[scope]; local expression; if(scopeFuncDepth == funcDepth) then - oldActiveBlock.scope:addReferenceToHigherScope(self.scope, self.allocUpvalFunction); expression = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {}); elseif(scopeFuncDepth == funcDepth - 1) then local varReg = self:getVarRegister(scope, id, scopeFuncDepth, nil); @@ -94,7 +92,6 @@ return function(Compiler) table.insert(usedRegs, varReg); else local higherId = oldGetUpvalueId(self, scope, id); - oldActiveBlock.scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar); expression = Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(higherId)); end table.insert(upvalueExpressions, Ast.TableEntry(expression)); @@ -108,22 +105,17 @@ return function(Compiler) local scope = self.activeBlock.scope; self:pushRegisterUsageInfo(); for i, arg in ipairs(node.args) do - if(arg.kind == AstKind.VariableExpression) then + if(arg.kind == AstKind.VariableExpression or arg.kind == AstKind.GlobalVariableExpression) then if(self:isUpvalue(arg.scope, arg.id)) then - scope:addReferenceToHigherScope(self.scope, self.allocUpvalFunction); local argReg = self:getVarRegister(arg.scope, arg.id, funcDepth, nil); self:addStatement(self:setRegister(scope, argReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {argReg}, {}, false); self:addStatement(self:setUpvalueMember(scope, self:register(scope, argReg), Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.argsVar), Ast.NumberExpression(i))), {}, {argReg}, true); else local argReg = self:getVarRegister(arg.scope, arg.id, funcDepth, nil); - scope:addReferenceToHigherScope(self.containerFuncScope, self.argsVar); self:addStatement(self:setRegister(scope, argReg, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.argsVar), Ast.NumberExpression(i))), {argReg}, {}, false); end else self.varargReg = self:allocRegister(true); - scope:addReferenceToHigherScope(self.containerFuncScope, self.argsVar); - scope:addReferenceToHigherScope(self.scope, self.selectVar); - scope:addReferenceToHigherScope(self.scope, self.unpackVar); self:addStatement(self:setRegister(scope, self.varargReg, self:pack(scope, { Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.selectVar), { Ast.NumberExpression(i); @@ -159,7 +151,6 @@ return function(Compiler) local retrieveExpression if isVarargFunction then - scope:addReferenceToHigherScope(self.scope, self.createVarargClosureVar); retrieveExpression = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.createVarargClosureVar), { Ast.NumberExpression(block.id), Ast.TableConstructorExpression(upvalueExpressions), @@ -167,7 +158,6 @@ return function(Compiler) }); else local varScope, var = self:getCreateClosureVar(#node.args + math.random(0, 5)); - scope:addReferenceToHigherScope(varScope, var); retrieveExpression = Ast.FunctionCallExpression(Ast.VariableExpression(varScope, var), { Ast.NumberExpression(block.id), Ast.TableConstructorExpression(upvalueExpressions), @@ -188,7 +178,6 @@ return function(Compiler) for id, name in ipairs(block.scope.variables) do local varReg = self:getVarRegister(block.scope, id, funcDepth, nil); if self:isUpvalue(block.scope, id) then - scope:addReferenceToHigherScope(self.scope, self.freeUpvalueFunc); self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.freeUpvalueFunc), { self:register(scope, varReg) })), {varReg}, {varReg}, false); diff --git a/src/prometheus/cff/compiler.lua b/src/prometheus/cff/compiler.lua index 305799bf..240cd444 100644 --- a/src/prometheus/cff/compiler.lua +++ b/src/prometheus/cff/compiler.lua @@ -5,6 +5,7 @@ -- This Script is the main compiler module. local Ast = require("prometheus.ast"); +local analyzeBindings = require("prometheus.bindinganalysis"); local Scope = require("prometheus.scope"); local util = require("prometheus.util"); @@ -83,6 +84,7 @@ function Compiler:popRegisterUsageInfo() end function Compiler:compile(ast) + analyzeBindings(ast); self.blocks = {}; self.registers = {}; self.activeBlock = nil; @@ -113,17 +115,6 @@ function Compiler:compile(ast) local _, tonumberVar = newGlobalScope:resolve("tonumber"); local _, errorVar = newGlobalScope:resolve("error"); - psc:addReferenceToHigherScope(newGlobalScope, getfenvVar, 3); - psc:addReferenceToHigherScope(newGlobalScope, setfenvVar); - psc:addReferenceToHigherScope(newGlobalScope, tableVar); - psc:addReferenceToHigherScope(newGlobalScope, unpackVar); - psc:addReferenceToHigherScope(newGlobalScope, envVar); - psc:addReferenceToHigherScope(newGlobalScope, newproxyVar); - psc:addReferenceToHigherScope(newGlobalScope, setmetatableVar); - psc:addReferenceToHigherScope(newGlobalScope, getmetatableVar); - psc:addReferenceToHigherScope(newGlobalScope, selectVar); - psc:addReferenceToHigherScope(newGlobalScope, tonumberVar); - psc:addReferenceToHigherScope(newGlobalScope, errorVar); self.scope = Scope:new(psc); self.envVar = self.scope:addVariable(); @@ -192,31 +183,22 @@ function Compiler:compile(ast) return uid; end - createClosureSubScope:addReferenceToHigherScope(self.scope, self.containerFuncVar); - createClosureSubScope:addReferenceToHigherScope(createClosureScope, createClosurePosArg) - createClosureSubScope:addReferenceToHigherScope(createClosureScope, createClosureUpvalsArg, 1) - createClosureScope:addReferenceToHigherScope(self.scope, self.upvaluesProxyFunctionVar) - createClosureSubScope:addReferenceToHigherScope(createClosureScope, createClosureProxyObject); self:compileTopNode(ast); -- VM value lists carry an explicit count: table length loses trailing nils. local packScope = Scope:new(self.scope); - packScope:addReferenceToHigherScope(self.scope, self.selectVar); local unpackScope = Scope:new(self.scope); local unpackValuesVar = unpackScope:addVariable(); - unpackScope:addReferenceToHigherScope(self.scope, self.unpackVar); -- Keep query handling shared: a per-call closure would capture the frame's -- environment and delay its collection when another Cff pass wraps it. -- Ordinary functions receive exactly their original arguments via a tail call. local callScope = Scope:new(self.scope); local callFuncVar, callEnvVar = callScope:addVariable(), callScope:addVariable(); - callScope:addReferenceToHigherScope(self.scope, self.getfenvVar, 2); local getterScope = Scope:new(callScope); local levelVar = getterScope:addVariable(); local currentScope = Scope:new(getterScope); - currentScope:addReferenceToHigherScope(callScope, callEnvVar); local functionNodeAssignments = { { @@ -418,8 +400,6 @@ end -- Each wrapper reads its own environment at entry, before tail-calling the VM. -- The VM keeps this table in its call frame so recursion and yields are isolated. function Compiler:closureEnvironment(scope, inheritedScope, inheritedVar) - scope:addReferenceToHigherScope(self.scope, self.getfenvVar, 2); - scope:addReferenceToHigherScope(inheritedScope, inheritedVar); return Ast.OrExpression(Ast.AndExpression( Ast.VariableExpression(self.scope, self.getfenvVar), Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.getfenvVar), {Ast.NumberExpression(1)}) @@ -427,11 +407,7 @@ function Compiler:closureEnvironment(scope, inheritedScope, inheritedVar) end function Compiler:inheritClosureEnvironment(scope, funcVar, envVar) - scope:addReferenceToHigherScope(self.scope, self.setfenvVar); local bodyScope = Scope:new(scope); - bodyScope:addReferenceToHigherScope(self.scope, self.setfenvVar); - bodyScope:addReferenceToHigherScope(scope, funcVar); - bodyScope:addReferenceToHigherScope(scope, envVar); return Ast.IfStatement(Ast.VariableExpression(self.scope, self.setfenvVar), Ast.Block({ Ast.FunctionCallStatement(Ast.VariableExpression(self.scope, self.setfenvVar), { Ast.VariableExpression(scope, funcVar), @@ -452,11 +428,6 @@ function Compiler:getCreateClosureVar(argCount) local createClosureProxyObject = createClosureScope:addVariable(); local createClosureFuncVar = createClosureScope:addVariable(); - createClosureSubScope:addReferenceToHigherScope(self.scope, self.containerFuncVar); - createClosureSubScope:addReferenceToHigherScope(createClosureScope, createClosurePosArg) - createClosureSubScope:addReferenceToHigherScope(createClosureScope, createClosureUpvalsArg, 1) - createClosureScope:addReferenceToHigherScope(self.scope, self.upvaluesProxyFunctionVar) - createClosureSubScope:addReferenceToHigherScope(createClosureScope, createClosureProxyObject); local argsTb, argsTb2 = {}, {}; for i = 1, argCount do diff --git a/src/prometheus/cff/emit.lua b/src/prometheus/cff/emit.lua index b1469b1d..d5d73459 100644 --- a/src/prometheus/cff/emit.lua +++ b/src/prometheus/cff/emit.lua @@ -267,7 +267,6 @@ return function(Compiler) -- For single block if len == 1 then - tb[l].block.scope:setParent(pScope); return tb[l].block; end @@ -277,13 +276,11 @@ return function(Compiler) local elseifs = {}; -- First block uses the first midpoint threshold - tb[l].block.scope:setParent(ifScope); local firstCondition = buildBlockThresholdCondition(ifScope, tb[l].id, tb[l + 1].id, false); local firstBlock = tb[l].block; -- Middle blocks use their upper midpoint threshold for i = l + 1, r - 1 do - tb[i].block.scope:setParent(ifScope); local condition = buildBlockThresholdCondition(ifScope, tb[i].id, tb[i + 1].id, false); table.insert(elseifs, { condition = condition, @@ -292,7 +289,6 @@ return function(Compiler) end -- Last block becomes else - tb[r].block.scope:setParent(ifScope); local elseBlock = tb[r].block; return Ast.Block({ @@ -339,13 +335,9 @@ return function(Compiler) local whileBody = buildElseifChain(blocks, 1, #blocks, self.containerFuncScope); if self.whileScope then -- Ensure whileScope is properly connected - self.whileScope:setParent(self.containerFuncScope); end - self.whileScope:addReferenceToHigherScope(self.containerFuncScope, self.returnVar, 1); - self.whileScope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); - self.containerFuncScope:addReferenceToHigherScope(self.scope, self.unpackVar); local declarations = { self.returnVar, diff --git a/src/prometheus/cff/expressions.lua b/src/prometheus/cff/expressions.lua index 82207854..2084cd98 100644 --- a/src/prometheus/cff/expressions.lua +++ b/src/prometheus/cff/expressions.lua @@ -17,6 +17,7 @@ handlers[AstKind.NumberExpression] = requireExpression("number"); handlers[AstKind.BooleanExpression] = requireExpression("boolean"); handlers[AstKind.NilExpression] = requireExpression("nil"); handlers[AstKind.VariableExpression] = requireExpression("variable"); +handlers[AstKind.GlobalVariableExpression] = requireExpression("variable"); handlers[AstKind.FunctionCallExpression] = requireExpression("function_call"); handlers[AstKind.PassSelfFunctionCallExpression] = requireExpression("pass_self_function_call"); handlers[AstKind.IndexExpression] = requireExpression("index"); diff --git a/src/prometheus/cff/expressions/variable.lua b/src/prometheus/cff/expressions/variable.lua index 502dc86d..5a023be4 100644 --- a/src/prometheus/cff/expressions/variable.lua +++ b/src/prometheus/cff/expressions/variable.lua @@ -30,7 +30,6 @@ return function(self, expression, funcDepth, numReturns) else local reg = self:allocRegister(false); local upvalId = self:getUpvalueId(expression.scope, expression.id); - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar); self:addStatement(self:setRegister(scope, reg, self:getUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)))), {reg}, {}, true); regs[i] = reg; end diff --git a/src/prometheus/cff/native.lua b/src/prometheus/cff/native.lua index cab95386..5cec0c9c 100644 --- a/src/prometheus/cff/native.lua +++ b/src/prometheus/cff/native.lua @@ -18,7 +18,8 @@ local function captures(expression, ast) end end) walk(expression, ast, function(node) - if (node.kind == Kind.VariableExpression or node.kind == Kind.AssignmentVariable + if (node.kind == Kind.VariableExpression or node.kind == Kind.GlobalVariableExpression + or node.kind == Kind.AssignmentVariable or node.kind == Kind.AssignmentGlobalVariable or node.kind == Kind.FunctionDeclaration) and not node.scope.isGlobal and not owned[node.scope] then seen[node.scope] = seen[node.scope] or {} @@ -55,7 +56,6 @@ return function(Compiler) reads[#reads + 1] = register else local index = self:getUpvalueId(variable.scope, variable.id) - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar) id = Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(index)) end @@ -95,8 +95,8 @@ return function(Compiler) return Ast.IndexExpression(base, index) end walk(expression, ast, nil, function(node) - if node.kind == Kind.VariableExpression then return access(node, false) end - if node.kind == Kind.AssignmentVariable then return access(node, true) end + if node.kind == Kind.VariableExpression or node.kind == Kind.GlobalVariableExpression then return access(node, false) end + if node.kind == Kind.AssignmentVariable or node.kind == Kind.AssignmentGlobalVariable then return access(node, true) end if node.kind == Kind.FunctionDeclaration and not owned[node.scope] then local target = access(node, false) if target then @@ -120,22 +120,17 @@ return function(Compiler) .. names.result .. '=' .. source .. ';' .. finish .. 'return ' .. names.result .. ' end') local factory = parsed.body.statements[1].args[1] self.nativeResults[expression] = factory.body.statements[1].expressions[1] - factory.body.scope:setParent(scope) require('prometheus.visitast')(parsed, nil, function(node, data) - if node.kind == Kind.VariableExpression and node.scope.isGlobal then + if (node.kind == Kind.VariableExpression or node.kind == Kind.GlobalVariableExpression) and node.scope.isGlobal then assert(node.scope:getVariableName(node.id) == names.setenv, 'Unexpected native global') - data.scope:removeReferenceToHigherScope(node.scope, node.id) - data.scope:addReferenceToHigherScope(self.scope, self.setfenvVar) return Ast.VariableExpression(self.scope, self.setfenvVar) end end) local proxy = Ast.NilExpression() if #entries > 0 then - scope:addReferenceToHigherScope(self.scope, self.upvaluesProxyFunctionVar) proxy = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.upvaluesProxyFunctionVar), {Ast.TableConstructorExpression(entries)}) end - scope:addReferenceToHigherScope(self.scope, self.upvaluesTable) local value = Ast.FunctionCallExpression(factory, {proxy, self:env(scope), Ast.VariableExpression(self.scope, self.upvaluesTable)}) local result = self:allocRegister(false) diff --git a/src/prometheus/cff/register.lua b/src/prometheus/cff/register.lua index f7fba7dc..f168bcb4 100644 --- a/src/prometheus/cff/register.lua +++ b/src/prometheus/cff/register.lua @@ -105,12 +105,10 @@ return function(Compiler) if id < MAX_REGS then local vid = self:getRegisterVarId(id); - scope:addReferenceToHigherScope(self.containerFuncScope, vid); return Ast.VariableExpression(self.containerFuncScope, vid); end local vid = self:getRegisterVarId(MAX_REGS); - scope:addReferenceToHigherScope(self.containerFuncScope, vid); return Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, vid), Ast.NumberExpression((id - MAX_REGS) + 1)); end @@ -132,12 +130,10 @@ return function(Compiler) if id < MAX_REGS then local vid = self:getRegisterVarId(id); - scope:addReferenceToHigherScope(self.containerFuncScope, vid); return Ast.AssignmentVariable(self.containerFuncScope, vid); end local vid = self:getRegisterVarId(MAX_REGS); - scope:addReferenceToHigherScope(self.containerFuncScope, vid); return Ast.AssignmentIndexing(Ast.VariableExpression(self.containerFuncScope, vid), Ast.NumberExpression((id - MAX_REGS) + 1)); end @@ -182,73 +178,59 @@ return function(Compiler) end function Compiler:pos(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); return Ast.VariableExpression(self.containerFuncScope, self.posVar); end function Compiler:posAssignment(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); return Ast.AssignmentVariable(self.containerFuncScope, self.posVar); end function Compiler:args(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.argsVar); return Ast.VariableExpression(self.containerFuncScope, self.argsVar); end function Compiler:unpack(scope) - scope:addReferenceToHigherScope(self.scope, self.unpackPackedVar); return Ast.VariableExpression(self.scope, self.unpackPackedVar); end function Compiler:pack(scope, values) - scope:addReferenceToHigherScope(self.scope, self.packVar); return Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.packVar), values); end function Compiler:env(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentEnvVar); return Ast.VariableExpression(self.containerFuncScope, self.currentEnvVar); end function Compiler:functionCall(scope, reg, args) - scope:addReferenceToHigherScope(self.scope, self.callFunctionVar); local callArgs = {self:register(scope, reg), self:env(scope)}; for _, arg in ipairs(args) do table.insert(callArgs, arg); end return Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.callFunctionVar), callArgs); end function Compiler:jmp(scope, to) - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.posVar)},{to}); end function Compiler:setPos(scope, val) if not val then - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.posVar)}, {Ast.NilExpression()}); end - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.posVar)}, {Ast.NumberExpression(val) or Ast.NilExpression()}); end function Compiler:setReturn(scope, val) - scope:addReferenceToHigherScope(self.containerFuncScope, self.returnVar); return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.returnVar)}, {val}); end function Compiler:getReturn(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.returnVar); return Ast.VariableExpression(self.containerFuncScope, self.returnVar); end function Compiler:returnAssignment(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.returnVar); return Ast.AssignmentVariable(self.containerFuncScope, self.returnVar); end function Compiler:setUpvalueMember(scope, idExpr, valExpr, compoundConstructor) - scope:addReferenceToHigherScope(self.scope, self.upvaluesTable); if compoundConstructor then return compoundConstructor(Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesTable), idExpr), valExpr); end @@ -256,7 +238,6 @@ return function(Compiler) end function Compiler:getUpvalueMember(scope, idExpr) - scope:addReferenceToHigherScope(self.scope, self.upvaluesTable); return Ast.IndexExpression(Ast.VariableExpression(self.scope, self.upvaluesTable), idExpr); end end diff --git a/src/prometheus/cff/statements/assignment.lua b/src/prometheus/cff/statements/assignment.lua index fba6b458..f74cf187 100644 --- a/src/prometheus/cff/statements/assignment.lua +++ b/src/prometheus/cff/statements/assignment.lua @@ -71,7 +71,7 @@ return function(self, statement, funcDepth) for i = #statement.lhs, 1, -1 do local primaryExpr = statement.lhs[i]; - if primaryExpr.kind == AstKind.AssignmentVariable then + if primaryExpr.kind == AstKind.AssignmentVariable or primaryExpr.kind == AstKind.AssignmentGlobalVariable then if primaryExpr.scope.isGlobal then local tmpReg = self:allocRegister(false); self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(primaryExpr.scope:getVariableName(primaryExpr.id))), {tmpReg}, {}, false); @@ -91,7 +91,6 @@ return function(self, statement, funcDepth) end else local upvalId = self:getUpvalueId(primaryExpr.scope, primaryExpr.id); - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar); self:addStatement(self:setUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(scope, exprregs[i])), {}, {exprregs[i]}, true); end end diff --git a/src/prometheus/cff/statements/break_statement.lua b/src/prometheus/cff/statements/break_statement.lua index 87416ec5..b552d7ba 100644 --- a/src/prometheus/cff/statements/break_statement.lua +++ b/src/prometheus/cff/statements/break_statement.lua @@ -24,7 +24,6 @@ return function(self, statement, funcDepth) local varScope, id = var.scope, var.id; local varReg = self:getVarRegister(varScope, id, nil, nil); if self:isUpvalue(varScope, id) then - scope:addReferenceToHigherScope(self.scope, self.freeUpvalueFunc); self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.freeUpvalueFunc), { self:register(scope, varReg) })), {varReg}, {varReg}, false); diff --git a/src/prometheus/cff/statements/compound.lua b/src/prometheus/cff/statements/compound.lua index 059a1305..c6a65cf2 100644 --- a/src/prometheus/cff/statements/compound.lua +++ b/src/prometheus/cff/statements/compound.lua @@ -53,7 +53,6 @@ return function(self, statement, funcDepth) end else local upvalId = self:getUpvalueId(primaryExpr.scope, primaryExpr.id); - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar); self:addStatement(self:setUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(scope, valueReg), compoundConstructor), {}, {valueReg}, true); end self:freeRegister(valueReg, false); diff --git a/src/prometheus/cff/statements/continue_statement.lua b/src/prometheus/cff/statements/continue_statement.lua index b2c7173c..d75b8ba3 100644 --- a/src/prometheus/cff/statements/continue_statement.lua +++ b/src/prometheus/cff/statements/continue_statement.lua @@ -24,7 +24,6 @@ return function(self, statement, funcDepth) local varScope, id = var.scope, var.id; local varReg = self:getVarRegister(varScope, id, nil, nil); if self:isUpvalue(varScope, id) then - scope:addReferenceToHigherScope(self.scope, self.freeUpvalueFunc); self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.freeUpvalueFunc), { self:register(scope, varReg) })), {varReg}, {varReg}, false); diff --git a/src/prometheus/cff/statements/for_in_statement.lua b/src/prometheus/cff/statements/for_in_statement.lua index 3a489321..d69d6290 100644 --- a/src/prometheus/cff/statements/for_in_statement.lua +++ b/src/prometheus/cff/statements/for_in_statement.lua @@ -86,7 +86,6 @@ return function(self, statement, funcDepth) if(self:isUpvalue(statement.scope, id)) then local varreg = varRegs[i]; local tmpReg = self:allocRegister(false); - scope:addReferenceToHigherScope(self.scope, self.allocUpvalFunction); self:addStatement(self:setRegister(scope, tmpReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {tmpReg}, {}, false); self:addStatement(self:setUpvalueMember(scope, self:register(scope, tmpReg), self:register(scope, varreg)), {}, {tmpReg, varreg}, true); self:addStatement(self:copyRegisters(scope, {varreg}, {tmpReg}), {varreg}, {tmpReg}, false); diff --git a/src/prometheus/cff/statements/for_statement.lua b/src/prometheus/cff/statements/for_statement.lua index 597fd9d2..c2c9045a 100644 --- a/src/prometheus/cff/statements/for_statement.lua +++ b/src/prometheus/cff/statements/for_statement.lua @@ -36,8 +36,6 @@ return function(self, statement, funcDepth) -- Lua evaluates all three expressions before coercing and validating them. -- Use captured builtins so replacing globals cannot change loop semantics. - scope:addReferenceToHigherScope(self.scope, self.tonumberVar, 3); - scope:addReferenceToHigherScope(self.scope, self.errorVar, 3); for _, input in ipairs({ {initialReg, "initial value"}, {finalReg, "limit"}, @@ -111,7 +109,6 @@ return function(self, statement, funcDepth) local varReg = self:getVarRegister(statement.scope, statement.id, funcDepth, nil); if(self:isUpvalue(statement.scope, statement.id)) then - scope:addReferenceToHigherScope(self.scope, self.allocUpvalFunction); self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {varReg}, {}, false); self:addStatement(self:setUpvalueMember(scope, self:register(scope, varReg), self:register(scope, currentReg)), {}, {varReg, currentReg}, true); else diff --git a/src/prometheus/cff/statements/function_declaration.lua b/src/prometheus/cff/statements/function_declaration.lua index cf0f22ff..a6bf6c57 100644 --- a/src/prometheus/cff/statements/function_declaration.lua +++ b/src/prometheus/cff/statements/function_declaration.lua @@ -27,7 +27,6 @@ return function(self, statement, funcDepth) else tblReg = self:allocRegister(false); local upvalId = self:getUpvalueId(statement.scope, statement.id); - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar); self:addStatement(self:setRegister(scope, tblReg, self:getUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)))), {tblReg}, {}, true); end end @@ -74,7 +73,6 @@ return function(self, statement, funcDepth) end else local upvalId = self:getUpvalueId(statement.scope, statement.id); - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar); self:addStatement(self:setUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(scope, retReg)), {}, {retReg}, true); end end diff --git a/src/prometheus/cff/statements/local_function_declaration.lua b/src/prometheus/cff/statements/local_function_declaration.lua index d377afa6..4339ad67 100644 --- a/src/prometheus/cff/statements/local_function_declaration.lua +++ b/src/prometheus/cff/statements/local_function_declaration.lua @@ -11,7 +11,6 @@ return function(self, statement, funcDepth) if(self:isUpvalue(statement.scope, statement.id)) then local varReg = self:getVarRegister(statement.scope, statement.id, funcDepth, nil); - scope:addReferenceToHigherScope(self.scope, self.allocUpvalFunction); self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {varReg}, {}, false); local retReg = self:compileFunction(statement, funcDepth); self:addStatement(self:setUpvalueMember(scope, self:register(scope, varReg), self:register(scope, retReg)), {}, {varReg, retReg}, true); diff --git a/src/prometheus/cff/statements/local_variable_declaration.lua b/src/prometheus/cff/statements/local_variable_declaration.lua index 56ece645..168acded 100644 --- a/src/prometheus/cff/statements/local_variable_declaration.lua +++ b/src/prometheus/cff/statements/local_variable_declaration.lua @@ -48,7 +48,6 @@ return function(self, statement, funcDepth) if(self:isUpvalue(statement.scope, id)) then local varreg = self:getVarRegister(statement.scope, id, funcDepth); local varReg = self:getVarRegister(statement.scope, id, funcDepth, nil); - scope:addReferenceToHigherScope(self.scope, self.allocUpvalFunction); self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {varReg}, {}, false); self:addStatement(self:setUpvalueMember(scope, self:register(scope, varReg), self:register(scope, exprregs[i])), {}, {varReg, exprregs[i]}, true); self:freeRegister(exprregs[i], false); diff --git a/src/prometheus/cff/statements/repeat_statement.lua b/src/prometheus/cff/statements/repeat_statement.lua index 4da955e6..30313e05 100644 --- a/src/prometheus/cff/statements/repeat_statement.lua +++ b/src/prometheus/cff/statements/repeat_statement.lua @@ -28,7 +28,6 @@ return function(self, statement, funcDepth) for id, _ in ipairs(statement.body.scope.variables) do local varReg = self:getVarRegister(statement.body.scope, id, funcDepth, nil); if self:isUpvalue(statement.body.scope, id) then - scope:addReferenceToHigherScope(self.scope, self.freeUpvalueFunc); self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.freeUpvalueFunc), { self:register(scope, varReg) })), { varReg }, { varReg }, false); else self:addStatement(self:setRegister(scope, varReg, Ast.NilExpression()), { varReg }, {}, false); diff --git a/src/prometheus/cff/strings.lua b/src/prometheus/cff/strings.lua index ebdda11d..bc66c445 100644 --- a/src/prometheus/cff/strings.lua +++ b/src/prometheus/cff/strings.lua @@ -34,7 +34,6 @@ return function(ast) elseif node.kind == Kind.StringExpression then local index = indices[node.value] if index then - scopes[#scopes]:addReferenceToHigherScope(scope, pool) return Ast.IndexExpression(Ast.VariableExpression(scope, pool), Ast.NumberExpression(index)) end end diff --git a/src/prometheus/cff/upvalue.lua b/src/prometheus/cff/upvalue.lua index 14bf1260..43930415 100644 --- a/src/prometheus/cff/upvalue.lua +++ b/src/prometheus/cff/upvalue.lua @@ -19,13 +19,8 @@ return function(Compiler) local valueVar = scope:addVariable(); local whileScope = Scope:new(scope); - whileScope:addReferenceToHigherScope(self.scope, self.upvaluesReferenceCountsTable, 3); - whileScope:addReferenceToHigherScope(scope, valueVar, 3); - whileScope:addReferenceToHigherScope(scope, iteratorVar, 3); local ifScope = Scope:new(whileScope); - ifScope:addReferenceToHigherScope(self.scope, self.upvaluesReferenceCountsTable, 1); - ifScope:addReferenceToHigherScope(self.scope, self.upvaluesTable, 1); return Ast.FunctionLiteralExpression({Ast.VariableExpression(scope, selfVar)}, Ast.Block({ Ast.LocalVariableDeclaration(scope, {iteratorVar, valueVar}, {Ast.NumberExpression(1), Ast.IndexExpression(Ast.VariableExpression(scope, selfVar), Ast.NumberExpression(1))}), @@ -59,8 +54,6 @@ return function(Compiler) local scope = Scope:new(self.scope); local argVar = scope:addVariable(); local ifScope = Scope:new(scope); - ifScope:addReferenceToHigherScope(scope, argVar, 3); - scope:addReferenceToHigherScope(self.scope, self.upvaluesReferenceCountsTable, 2); return Ast.FunctionLiteralExpression({Ast.VariableExpression(scope, argVar)}, Ast.Block({ Ast.AssignmentStatement({ Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, argVar)) @@ -81,7 +74,6 @@ return function(Compiler) function Compiler:createUpvaluesProxyFunc() local scope = Scope:new(self.scope); - scope:addReferenceToHigherScope(self.scope, self.newproxyVar); local entriesVar = scope:addVariable(); @@ -89,18 +81,9 @@ return function(Compiler) local proxyVar = ifScope:addVariable(); local metatableVar = ifScope:addVariable(); local elseScope = Scope:new(scope); - ifScope:addReferenceToHigherScope(self.scope, self.newproxyVar); - ifScope:addReferenceToHigherScope(self.scope, self.getmetatableVar); - ifScope:addReferenceToHigherScope(self.scope, self.upvaluesGcFunctionVar); - ifScope:addReferenceToHigherScope(scope, entriesVar); - elseScope:addReferenceToHigherScope(self.scope, self.setmetatableVar); - elseScope:addReferenceToHigherScope(scope, entriesVar); - elseScope:addReferenceToHigherScope(self.scope, self.upvaluesGcFunctionVar); local forScope = Scope:new(scope); local forArg = forScope:addVariable(); - forScope:addReferenceToHigherScope(self.scope, self.upvaluesReferenceCountsTable, 2); - forScope:addReferenceToHigherScope(scope, entriesVar, 2); return Ast.FunctionLiteralExpression({Ast.VariableExpression(scope, entriesVar)}, Ast.Block({ Ast.ForStatement(forScope, forArg, Ast.NumberExpression(1), Ast.LenExpression(Ast.VariableExpression(scope, entriesVar)), Ast.NumberExpression(1), Ast.Block({ @@ -155,8 +138,6 @@ return function(Compiler) function Compiler:createAllocUpvalFunction() local scope = Scope:new(self.scope); - scope:addReferenceToHigherScope(self.scope, self.currentUpvalId, 4); - scope:addReferenceToHigherScope(self.scope, self.upvaluesReferenceCountsTable, 1); return Ast.FunctionLiteralExpression({}, Ast.Block({ Ast.AssignmentStatement({ diff --git a/src/prometheus/parser.lua b/src/prometheus/parser.lua index f863e9e4..df9c0a02 100644 --- a/src/prometheus/parser.lua +++ b/src/prometheus/parser.lua @@ -16,6 +16,7 @@ local Tokenizer = require("prometheus.tokenizer"); local Enums = require("prometheus.enums"); local util = require("prometheus.util"); local Ast = require("prometheus.ast"); +local analyzeBindings = require("prometheus.bindinganalysis"); local Scope = require("prometheus.scope"); local logger = require("logger"); @@ -37,6 +38,7 @@ local ASSIGNMENT_NO_WARN_LOOKUP = lookupify{ local CALLABLE_PREFIX_EXPRESSION_LOOKUP = lookupify{ AstKind.VariableExpression, + AstKind.GlobalVariableExpression, AstKind.IndexExpression, AstKind.FunctionCallExpression, AstKind.PassSelfFunctionCallExpression @@ -163,6 +165,8 @@ function Parser:parse(code) self.length = 0; logger:debug("Cleanup Done") + -- Keep direct Parser consumers on the same analyzed AST path as the pipeline. + analyzeBindings(ast); return ast; end @@ -402,12 +406,14 @@ function Parser:statement(scope, currentLoop) end -- Variable Assignment - if(expr.kind == AstKind.IndexExpression or expr.kind == AstKind.VariableExpression) then + if(expr.kind == AstKind.IndexExpression or expr.kind == AstKind.VariableExpression or expr.kind == AstKind.GlobalVariableExpression) then if(expr.kind == AstKind.IndexExpression) then expr.kind = AstKind.AssignmentIndexing end if(expr.kind == AstKind.VariableExpression) then expr.kind = AstKind.AssignmentVariable + elseif(expr.kind == AstKind.GlobalVariableExpression) then + expr.kind = AstKind.AssignmentGlobalVariable end if(self.luaVersion == LuaVersion.LuaU) then @@ -460,12 +466,14 @@ function Parser:statement(scope, currentLoop) logger:error(generateError(self, string.format("expected a valid assignment statement lhs part but got nil"))); end - if(expr.kind == AstKind.IndexExpression or expr.kind == AstKind.VariableExpression) then + if(expr.kind == AstKind.IndexExpression or expr.kind == AstKind.VariableExpression or expr.kind == AstKind.GlobalVariableExpression) then if(expr.kind == AstKind.IndexExpression) then expr.kind = AstKind.AssignmentIndexing end if(expr.kind == AstKind.VariableExpression) then expr.kind = AstKind.AssignmentVariable + elseif(expr.kind == AstKind.GlobalVariableExpression) then + expr.kind = AstKind.AssignmentGlobalVariable end table.insert(lhs, expr); else diff --git a/src/prometheus/pipeline.lua b/src/prometheus/pipeline.lua index fe21f853..ab4d174c 100644 --- a/src/prometheus/pipeline.lua +++ b/src/prometheus/pipeline.lua @@ -8,6 +8,7 @@ local Enums = require("prometheus.enums"); local util = require("prometheus.util"); local Parser = require("prometheus.parser"); +local analyzeBindings = require("prometheus.bindinganalysis"); local Unparser = require("prometheus.unparser"); local logger = require("logger"); @@ -209,6 +210,7 @@ function Pipeline:apply(code, filename) if type(newAst) == "table" then ast = newAst; end + analyzeBindings(ast); logger:info(string.format("Step \"%s\" Done in %.2f seconds", step.Name or "Unnamed", gettime() - stepStartTime)); end @@ -238,6 +240,7 @@ function Pipeline:unparse(ast) end function Pipeline:renameVariables(ast) + analyzeBindings(ast); local startTime = gettime(); logger:info("Renaming Variables ..."); diff --git a/src/prometheus/scope.lua b/src/prometheus/scope.lua index 3f01b953..4eda4742 100644 --- a/src/prometheus/scope.lua +++ b/src/prometheus/scope.lua @@ -2,7 +2,8 @@ -- -- scope.lua -- --- This Script provides a class for the Scope of a Lua Script +-- Construction-time name resolution and derived lexical metadata. +-- Binding analysis owns scope ancestry, reference counts, and capture metadata. local logger = require("logger"); local config = require("config"); @@ -25,18 +26,18 @@ function Scope:new(parentScope, name) isGlobal = false, parentScope = parentScope, variables = {}, + symbols = {}, referenceCounts = {}; variablesLookup = {}, variablesFromHigherScopes = {}, skipIdLookup = {}; name = name or nextName(), children = {}, - level = parentScope.level and (parentScope.level + 1) or 1; + level = parentScope and parentScope.level and (parentScope.level + 1) or 1; } setmetatable(scope, self); self.__index = self; - parentScope:addChild(scope); return scope; end @@ -46,6 +47,7 @@ function Scope:newGlobal() isGlobal = true, parentScope = nil, variables = {}, + symbols = {}, variablesLookup = {}; referenceCounts = {}; skipIdLookup = {}; @@ -65,13 +67,6 @@ function Scope:getParent(parentScope) return self.parentScope; end -function Scope:setParent(parentScope) - self.parentScope:removeChild(self); - parentScope:addChild(self); - self.parentScope = parentScope; - self.level = parentScope.level + 1; -end - local next_name_i = 1; -- Adds a Variable to the scope and returns the variable id, if no name is passed then a name is generated function Scope:addVariable(name, token) @@ -148,22 +143,10 @@ function Scope:getVariables() return self.variables; end -function Scope:resetReferences(id) - self.referenceCounts[id] = 0; -end - function Scope:getReferences(id) return self.referenceCounts[id] or 0; end -function Scope:removeReference(id) - self.referenceCounts[id] = (self.referenceCounts[id] or 0) - 1; -end - -function Scope:addReference(id) - self.referenceCounts[id] = (self.referenceCounts[id] or 0) + 1; -end - -- Resolve the scope of a variable by name function Scope:resolve(name) if(self:hasVariable(name)) then @@ -171,7 +154,6 @@ function Scope:resolve(name) end assert(self.parentScope, "No Global Variable Scope was Created! This should not be Possible!"); local scope, id = self.parentScope:resolve(name); - self:addReferenceToHigherScope(scope, id, nil, true); return scope, id; end @@ -181,10 +163,20 @@ function Scope:resolveGlobal(name) end assert(self.parentScope, "No Global Variable Scope was Created! This should not be Possible!"); local scope, id = self.parentScope:resolveGlobal(name); - self:addReferenceToHigherScope(scope, id, nil, true); return scope, id; end +-- Construction adapter: stable identities survive scope reconstruction. +function Scope:getSymbol(id) + assert(not self.isGlobal, "Globals do not have local symbols"); + assert(self.variables[id], "Unknown local variable id: " .. tostring(id)); + if not self.symbols[id] then + self.symbols[id] = require("prometheus.ast").LocalSymbol(self.variables[id]); + self.symbols[id].scope, self.symbols[id].id = self, id; + end + return self.symbols[id]; +end + -- Returns the name of an Variable by id - this is used for unparsing function Scope:getVariableName(id) return self.variables[id]; @@ -198,93 +190,10 @@ function Scope:removeVariable(id) self.skipIdLookup[id] = true; end --- Add a Children Scope -function Scope:addChild(scope) - -- This will add all References from that Scope to higher Scopes. Note that the higher scopes may only be global - for scope, ids in pairs(scope.variablesFromHigherScopes) do - for id, count in pairs(ids) do - if count and count > 0 then - self:addReferenceToHigherScope(scope, id, count); - end - end - end - table.insert(self.children, scope); -end - -function Scope:clearReferences() - self.referenceCounts = {}; - self.variablesFromHigherScopes = {}; -end - -function Scope:removeChild(child) - for i, v in ipairs(self.children) do - if(v == child) then - -- This will add all References from that Scope to higher Scopes. Note that the higher scopes may only be global - for scope, ids in pairs(v.variablesFromHigherScopes) do - for id, count in pairs(ids) do - if count and count > 0 then - self:removeReferenceToHigherScope(scope, id, count); - end - end - end - return table.remove(self.children, i); - end - end -end - function Scope:getMaxId() return #self.variables; end -function Scope:addReferenceToHigherScope(scope, id, n, b) - n = n or 1; - if self.isGlobal then - if not scope.isGlobal then - logger:error(string.format("Could not resolve Scope \"%s\"", scope.name)) - end - return - end - if scope == self then - self.referenceCounts[id] = (self.referenceCounts[id] or 0) + n; - return - end - if not self.variablesFromHigherScopes[scope] then - self.variablesFromHigherScopes[scope] = {}; - end - local scopeReferences = self.variablesFromHigherScopes[scope]; - if scopeReferences[id] then - scopeReferences[id] = scopeReferences[id] + n; - else - scopeReferences[id] = n; - end - if not b then - self.parentScope:addReferenceToHigherScope(scope, id, n); - end -end - -function Scope:removeReferenceToHigherScope(scope, id, n, b) - n = n or 1; - if self.isGlobal then - return - end - if scope == self then - self.referenceCounts[id] = (self.referenceCounts[id] or 0) - n; - return - end - if not self.variablesFromHigherScopes[scope] then - self.variablesFromHigherScopes[scope] = {}; - end - local scopeReferences = self.variablesFromHigherScopes[scope]; - if scopeReferences[id] then - scopeReferences[id] = scopeReferences[id] - n; - else - scopeReferences[id] = 0; - end - if not b then - self.parentScope:removeReferenceToHigherScope(scope, id, n); - end -end - -- Rename Variables from that scope downwards -- this function needs a settings object with the following properties -- Keywords => forbidden Variable Names @@ -321,6 +230,8 @@ function Scope:renameVariables(settings) until not forbiddenNamesLookup[name]; self.variables[id] = name; + if self.symbols[id] then self.symbols[id].name = name; end + forbiddenNamesLookup[name] = true; self.variablesLookup[name] = id; end end diff --git a/src/prometheus/steps/AntiTamper.lua b/src/prometheus/steps/AntiTamper.lua index 428fba41..5da2e780 100644 --- a/src/prometheus/steps/AntiTamper.lua +++ b/src/prometheus/steps/AntiTamper.lua @@ -235,7 +235,6 @@ end local parsed = Parser:new({LuaVersion = Enums.LuaVersion.Lua51}):parse(code); local doStat = parsed.body.statements[1]; - doStat.body.scope:setParent(ast.body.scope); table.insert(ast.body.statements, 1, doStat); return ast; diff --git a/src/prometheus/steps/ConstantArray.lua b/src/prometheus/steps/ConstantArray.lua index 0d7c2dc2..200f8ddd 100644 --- a/src/prometheus/steps/ConstantArray.lua +++ b/src/prometheus/steps/ConstantArray.lua @@ -141,13 +141,11 @@ function ConstantArray:indexing(index, data) end end - data.scope:addReferenceToHigherScope(wrappers.scope, wrappers.id); return Ast.FunctionCallExpression(Ast.IndexExpression( Ast.VariableExpression(wrappers.scope, wrappers.id), Ast.StringExpression(wrapper.index) ), args); else - data.scope:addReferenceToHigherScope(self.rootScope, self.wrapperId); return Ast.FunctionCallExpression(Ast.VariableExpression(self.rootScope, self.wrapperId), { Ast.NumberExpression(index - self.wrapperOffset); }); @@ -203,14 +201,10 @@ function ConstantArray:addRotateCode(ast, shift) local newAst = parser:parse(string.gsub(string.gsub(rotateCode, "SHIFT", tostring(shift)), "LEN", tostring(#self.constants))); local forStat = newAst.body.statements[1]; - forStat.body.scope:setParent(ast.body.scope); visitast(newAst, nil, function(node, data) - if(node.kind == AstKind.VariableExpression) then + if(node.kind == AstKind.VariableExpression or node.kind == AstKind.GlobalVariableExpression) then if(node.scope:getVariableName(node.id) == "ARR") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); - data.scope:addReferenceToHigherScope(self.rootScope, self.arrId); - node.scope = self.rootScope; - node.id = self.arrId; + return Ast.VariableExpression(self.rootScope, self.arrId); end end end) @@ -275,19 +269,14 @@ function ConstantArray:addDecodeCode(ast) local newAst = parser:parse(base64DecodeCode); local forStat = newAst.body.statements[1]; - forStat.body.scope:setParent(ast.body.scope); visitast(newAst, nil, function(node, data) - if(node.kind == AstKind.VariableExpression) then + if(node.kind == AstKind.VariableExpression or node.kind == AstKind.GlobalVariableExpression) then if(node.scope:getVariableName(node.id) == "ARR") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); - data.scope:addReferenceToHigherScope(self.rootScope, self.arrId); - node.scope = self.rootScope; - node.id = self.arrId; + return Ast.VariableExpression(self.rootScope, self.arrId); end if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); return self:createBase64Lookup(); end end @@ -364,19 +353,14 @@ function ConstantArray:addDecodeCode(ast) local newAst = parser:parse(base85DecodeCode); local forStat = newAst.body.statements[1]; - forStat.body.scope:setParent(ast.body.scope); visitast(newAst, nil, function(node, data) - if(node.kind == AstKind.VariableExpression) then + if(node.kind == AstKind.VariableExpression or node.kind == AstKind.GlobalVariableExpression) then if(node.scope:getVariableName(node.id) == "ARR") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); - data.scope:addReferenceToHigherScope(self.rootScope, self.arrId); - node.scope = self.rootScope; - node.id = self.arrId; + return Ast.VariableExpression(self.rootScope, self.arrId); end if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); return self:createBase85Lookup(); end end @@ -489,24 +473,18 @@ function ConstantArray:addDecodeCode(ast) local newAst = parser:parse(mixedDecodeCode); local forStat = newAst.body.statements[1]; - forStat.body.scope:setParent(ast.body.scope); visitast(newAst, nil, function(node, data) - if(node.kind == AstKind.VariableExpression) then + if(node.kind == AstKind.VariableExpression or node.kind == AstKind.GlobalVariableExpression) then if(node.scope:getVariableName(node.id) == "ARR") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); - data.scope:addReferenceToHigherScope(self.rootScope, self.arrId); - node.scope = self.rootScope; - node.id = self.arrId; + return Ast.VariableExpression(self.rootScope, self.arrId); end if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE_64") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); return self:createBase64Lookup(); end if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE_85") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); return self:createBase85Lookup(); end end @@ -749,7 +727,6 @@ function ConstantArray:apply(ast, pipeline) addSubArg = Ast.AddExpression(Ast.VariableExpression(funcScope, arg), Ast.NumberExpression(offset)); end - funcScope:addReferenceToHigherScope(self.rootScope, self.wrapperId); local callArg = Ast.FunctionCallExpression(Ast.VariableExpression(self.rootScope, self.wrapperId), { addSubArg }); @@ -782,8 +759,6 @@ function ConstantArray:apply(ast, pipeline) -- Add Wrapper Function Code function() local funcScope = Scope:new(self.rootScope); - -- Add Reference to Array - funcScope:addReferenceToHigherScope(self.rootScope, self.arrId); local arg = funcScope:addVariable(); local addSubArg; diff --git a/src/prometheus/steps/EncryptStrings.lua b/src/prometheus/steps/EncryptStrings.lua index 7a40c21f..9e446e21 100644 --- a/src/prometheus/steps/EncryptStrings.lua +++ b/src/prometheus/steps/EncryptStrings.lua @@ -201,31 +201,28 @@ function EncryptStrings:apply(ast, _) local decryptVar = scope:addVariable(); local stringsVar = scope:addVariable(); - doStat.body.scope:setParent(ast.body.scope); visitast(newAst, nil, function(node, data) if(node.kind == AstKind.FunctionDeclaration) then if(node.scope:getVariableName(node.id) == "DECRYPT") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); - data.scope:addReferenceToHigherScope(scope, decryptVar); node.scope = scope; node.id = decryptVar; + node.symbol = scope:getSymbol(decryptVar); + node.name = nil; end end - if(node.kind == AstKind.AssignmentVariable or node.kind == AstKind.VariableExpression) then + if(node.kind == AstKind.AssignmentVariable or node.kind == AstKind.VariableExpression or node.kind == AstKind.AssignmentGlobalVariable or node.kind == AstKind.GlobalVariableExpression) then if(node.scope:getVariableName(node.id) == "STRINGS") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); - data.scope:addReferenceToHigherScope(scope, stringsVar); - node.scope = scope; - node.id = stringsVar; + if node.kind == AstKind.AssignmentVariable or node.kind == AstKind.AssignmentGlobalVariable then + return Ast.AssignmentVariable(scope, stringsVar); + end + return Ast.VariableExpression(scope, stringsVar); end end end) visitast(ast, nil, function(node, data) if(node.kind == AstKind.StringExpression) then - data.scope:addReferenceToHigherScope(scope, stringsVar); - data.scope:addReferenceToHigherScope(scope, decryptVar); local encrypted, seed = Encryptor.encrypt(node.value); return Ast.IndexExpression(Ast.VariableExpression(scope, stringsVar), Ast.FunctionCallExpression(Ast.VariableExpression(scope, decryptVar), { Ast.StringExpression(encrypted), Ast.NumberExpression(seed), diff --git a/src/prometheus/steps/ProxifyLocals.lua b/src/prometheus/steps/ProxifyLocals.lua index 138908bb..07d4a98e 100644 --- a/src/prometheus/steps/ProxifyLocals.lua +++ b/src/prometheus/steps/ProxifyLocals.lua @@ -150,9 +150,10 @@ function ProxifyLocals:CreateAssignmentExpression(info, expr, parentScope) ); table.insert(metatableVals, Ast.KeyedTableEntry(Ast.StringExpression(info.getValue.key), getvalueFunctionLiteral)); - parentScope:addReferenceToHigherScope(self.setMetatableVarScope, self.setMetatableVarId); + local setMetatableVar = Ast.VariableExpression(self.setMetatableVarScope, self.setMetatableVarId); + setMetatableVar.__ignoreProxifyLocals = true; return Ast.FunctionCallExpression( - Ast.VariableExpression(self.setMetatableVarScope, self.setMetatableVarId), + setMetatableVar, { Ast.TableConstructorExpression({ Ast.KeyedTableEntry(Ast.StringExpression(info.valueName), expr) @@ -205,6 +206,35 @@ function ProxifyLocals:apply(ast, pipeline) visitast(ast, function(node, data) + -- Expand proxified local functions into a declaration followed by an + -- assignment. The declaration must exist before its body is created so + -- recursive references resolve to the same local binding. + if(node.kind == AstKind.Block) then + local i = 1; + while i <= #node.statements do + local statement = node.statements[i]; + if statement.kind == AstKind.LocalFunctionDeclaration then + local info = getLocalMetatableInfo(statement.scope, statement.id); + if info then + local declaration = Ast.LocalVariableDeclaration(statement.scope, {statement.id}, {}); + declaration.__skipProxifyLocals = true; + local literal = Ast.FunctionLiteralExpression(statement.args, statement.body); + local assignmentTarget = Ast.AssignmentVariable(statement.scope, statement.id); + assignmentTarget.__ignoreProxifyLocals = true; + local assignment = Ast.AssignmentStatement( + {assignmentTarget}, + {self:CreateAssignmentExpression(info, literal, statement.scope)} + ); + assignment.__skipProxifyLocals = true; + node.statements[i] = declaration; + table.insert(node.statements, i + 1, assignment); + i = i + 1; + end + end + i = i + 1; + end + end + -- Lock for loop variables if(node.kind == AstKind.ForStatement) then disableMetatableInfo(node.scope, node.id) @@ -226,7 +256,7 @@ function ProxifyLocals:apply(ast, pipeline) -- Assignment Statements may be Obfuscated Differently if(node.kind == AstKind.AssignmentStatement) then - if(#node.lhs == 1 and node.lhs[1].kind == AstKind.AssignmentVariable) then + if not node.__skipProxifyLocals and #node.lhs == 1 and node.lhs[1].kind == AstKind.AssignmentVariable then local variable = node.lhs[1]; local localMetatableInfo = getLocalMetatableInfo(variable.scope, variable.id); if localMetatableInfo then @@ -235,14 +265,13 @@ function ProxifyLocals:apply(ast, pipeline) vexp.__ignoreProxifyLocals = true; args[1] = localMetatableInfo.setValue.constructor(vexp, args[1]); self.emptyFunctionUsed = true; - data.scope:addReferenceToHigherScope(self.emptyFunctionScope, self.emptyFunctionId); return Ast.FunctionCallStatement(Ast.VariableExpression(self.emptyFunctionScope, self.emptyFunctionId), args); end end end end, function(node, data) -- Local Variable Declaration - if(node.kind == AstKind.LocalVariableDeclaration) then + if(node.kind == AstKind.LocalVariableDeclaration and not node.__skipProxifyLocals) then for i, id in ipairs(node.ids) do local expr = node.expressions[i] or Ast.NilExpression(); local localMetatableInfo = getLocalMetatableInfo(node.scope, id); @@ -274,7 +303,7 @@ function ProxifyLocals:apply(ast, pipeline) end -- Assignment Variable for Assignment Statement - if(node.kind == AstKind.AssignmentVariable) then + if(node.kind == AstKind.AssignmentVariable and not node.__ignoreProxifyLocals) then local localMetatableInfo = getLocalMetatableInfo(node.scope, node.id); -- Apply Only to Some Variables if Threshold is non 1 if localMetatableInfo then @@ -282,17 +311,6 @@ function ProxifyLocals:apply(ast, pipeline) end end - -- Local Function Declaration - if(node.kind == AstKind.LocalFunctionDeclaration) then - local localMetatableInfo = getLocalMetatableInfo(node.scope, node.id); - -- Apply Only to Some Variables if Threshold is non 1 - if localMetatableInfo then - local funcLiteral = Ast.FunctionLiteralExpression(node.args, node.body); - local newExpr = self:CreateAssignmentExpression(localMetatableInfo, funcLiteral, node.scope); - return Ast.LocalVariableDeclaration(node.scope, {node.id}, {newExpr}); - end - end - -- Function Declaration if(node.kind == AstKind.FunctionDeclaration) then local localMetatableInfo = getLocalMetatableInfo(node.scope, node.id); @@ -308,4 +326,4 @@ function ProxifyLocals:apply(ast, pipeline) })); end -return ProxifyLocals; \ No newline at end of file +return ProxifyLocals; diff --git a/src/prometheus/steps/SplitStrings.lua b/src/prometheus/steps/SplitStrings.lua index 060101a3..9a61e6f4 100644 --- a/src/prometheus/steps/SplitStrings.lua +++ b/src/prometheus/steps/SplitStrings.lua @@ -82,7 +82,6 @@ local function generateTableConcatNode(chunks, data) table.insert(chunkNodes, Ast.TableEntry(Ast.StringExpression(chunk))); end local tb = Ast.TableConstructorExpression(chunkNodes); - data.scope:addReferenceToHigherScope(data.tableConcatScope, data.tableConcatId); return Ast.FunctionCallExpression(Ast.VariableExpression(data.tableConcatScope, data.tableConcatId), {tb}); end @@ -175,7 +174,6 @@ local function generateCustomFunctionLiteral(parentScope, variant) local funcDeclNode = parser:parse(custom1Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; - funcBody.scope:setParent(parentScope); return Ast.FunctionLiteralExpression(funcArgs, funcBody); -- Custom Function Type 2 @@ -183,7 +181,6 @@ local function generateCustomFunctionLiteral(parentScope, variant) local funcDeclNode = parser:parse(custom2Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; - funcBody.scope:setParent(parentScope); return Ast.FunctionLiteralExpression(funcArgs, funcBody); end end @@ -199,7 +196,6 @@ local function generateGlobalCustomFunctionDeclaration(ast, data) local funcDeclNode = parser:parse(custom1Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; - funcBody.scope:setParent(astScope); return Ast.LocalVariableDeclaration(astScope, {data.customFuncId}, {Ast.FunctionLiteralExpression(funcArgs, funcBody)}); -- Custom Function Type 2 @@ -208,7 +204,6 @@ local function generateGlobalCustomFunctionDeclaration(ast, data) local funcDeclNode = parser:parse(custom2Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; - funcBody.scope:setParent(astScope); return Ast.LocalVariableDeclaration(data.customFuncScope, {data.customFuncId}, {Ast.FunctionLiteralExpression(funcArgs, funcBody)}); end @@ -297,8 +292,6 @@ function SplitStrings:apply(ast, pipeline) elseif self.ConcatenationType == "custom" then if self.CustomFunctionType == "global" then local args = generateCustomNodeArgs(chunks, data, data.customFunctionVariant); - -- Add Reference for Variable Renaming - data.scope:addReferenceToHigherScope(data.customFuncScope, data.customFuncId); node = Ast.FunctionCallExpression(Ast.VariableExpression(data.customFuncScope, data.customFuncId), args); elseif self.CustomFunctionType == "local" then local lfuncs = data.functionData.localFunctions; @@ -306,8 +299,6 @@ function SplitStrings:apply(ast, pipeline) local func = lfuncs[idx]; local args = generateCustomNodeArgs(chunks, data, func.variant); func.used = true; - -- Add Reference for Variable Renaming - data.scope:addReferenceToHigherScope(func.scope, func.id); node = Ast.FunctionCallExpression(Ast.VariableExpression(func.scope, func.id), args); elseif self.CustomFunctionType == "inline" then local variant = self:variant(); @@ -327,7 +318,6 @@ function SplitStrings:apply(ast, pipeline) if(self.ConcatenationType == "table") then local globalScope = data.globalScope; local tableScope, tableId = globalScope:resolve("table") - ast.body.scope:addReferenceToHigherScope(globalScope, tableId); table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration(data.tableConcatScope, {data.tableConcatId}, {Ast.IndexExpression(Ast.VariableExpression(tableScope, tableId), Ast.StringExpression("concat"))})); elseif(self.ConcatenationType == "custom" and self.CustomFunctionType == "global") then diff --git a/src/prometheus/steps/Watermark.lua b/src/prometheus/steps/Watermark.lua index ce92f0a2..64b5997c 100644 --- a/src/prometheus/steps/Watermark.lua +++ b/src/prometheus/steps/Watermark.lua @@ -38,7 +38,6 @@ function Watermark:apply(ast) local watermark = Ast.AssignmentVariable(ast.globalScope, variable); local functionScope = Scope:new(body.scope); - functionScope:addReferenceToHigherScope(ast.globalScope, variable); local arg = functionScope:addVariable(); local statement = Ast.PassSelfFunctionCallStatement(Ast.StringExpression(self.Content), "gsub", { diff --git a/src/prometheus/steps/WrapInFunction.lua b/src/prometheus/steps/WrapInFunction.lua index 07a8a069..961b1e6b 100644 --- a/src/prometheus/steps/WrapInFunction.lua +++ b/src/prometheus/steps/WrapInFunction.lua @@ -30,7 +30,6 @@ function WrapInFunction:apply(ast) local body = ast.body; local scope = Scope:new(ast.globalScope); - body.scope:setParent(scope); ast.body = Ast.Block({ Ast.ReturnStatement({ diff --git a/src/prometheus/unparser.lua b/src/prometheus/unparser.lua index abf06aa8..e5918e23 100644 --- a/src/prometheus/unparser.lua +++ b/src/prometheus/unparser.lua @@ -12,6 +12,7 @@ local config = require("config"); local Ast = require("prometheus.ast"); +local analyzeBindings = require("prometheus.bindinganalysis"); local Enums = require("prometheus.enums"); local util = require("prometheus.util"); local logger = require("logger"); @@ -106,6 +107,19 @@ function Unparser:unparse(ast) if(ast.kind ~= AstKind.TopNode) then logger:error("Unparser:unparse expects a TopNode as first argument") end + analyzeBindings(ast); + -- Preserve symbol identity even for generated trees whose preferred names + -- collide. Keep existing names whenever lexical lookup would be unambiguous. + local attempts = {}; + ast.globalScope:renameVariables({ + Keywords = self.conventions.Keywords, + generateName = function(_, scope, originalName) + attempts[scope] = attempts[scope] or {}; + local count = attempts[scope][originalName] or 0; + attempts[scope][originalName] = count + 1; + return count == 0 and originalName or originalName .. "_" .. count; + end, + }); return self:unparseBlock(ast.body); end @@ -305,7 +319,7 @@ function Unparser:unparseStatement(statement, tabbing) -- Function Call Statement elseif(statement.kind == AstKind.FunctionCallStatement) then - if not (statement.base.kind == AstKind.IndexExpression or statement.base.kind == AstKind.VariableExpression) then + if not (statement.base.kind == AstKind.IndexExpression or statement.base.kind == AstKind.VariableExpression or statement.base.kind == AstKind.GlobalVariableExpression) then push("(", self:unparseExpression(statement.base, tabbing), ")"); else push(self:unparseExpression(statement.base, tabbing)); @@ -321,7 +335,7 @@ function Unparser:unparseStatement(statement, tabbing) -- Pass Self Function Call Statement elseif(statement.kind == AstKind.PassSelfFunctionCallStatement) then - if not (statement.base.kind == AstKind.IndexExpression or statement.base.kind == AstKind.VariableExpression) then + if not (statement.base.kind == AstKind.IndexExpression or statement.base.kind == AstKind.VariableExpression or statement.base.kind == AstKind.GlobalVariableExpression) then push("(", self:unparseExpression(statement.base, tabbing), ")"); else push(self:unparseExpression(statement.base, tabbing)); @@ -431,6 +445,10 @@ function Unparser:unparseExpression(expression, tabbing) return str; end + if(expression.kind == AstKind.GlobalVariableExpression or expression.kind == AstKind.AssignmentGlobalVariable) then + return expression.name; + end + if(expression.kind == AstKind.VariableExpression or expression.kind == AstKind.AssignmentVariable) then return expression.scope:getVariableName(expression.id); end @@ -596,7 +614,7 @@ function Unparser:unparseExpression(expression, tabbing) k = AstKind.FunctionCallExpression; if(expression.kind == k) then - if not (expression.base.kind == AstKind.IndexExpression or expression.base.kind == AstKind.VariableExpression) then + if not (expression.base.kind == AstKind.IndexExpression or expression.base.kind == AstKind.VariableExpression or expression.base.kind == AstKind.GlobalVariableExpression) then push("(", self:unparseExpression(expression.base, tabbing), ")"); else push(self:unparseExpression(expression.base, tabbing)); @@ -614,7 +632,7 @@ function Unparser:unparseExpression(expression, tabbing) k = AstKind.PassSelfFunctionCallExpression; if(expression.kind == k) then - if not (expression.base.kind == AstKind.IndexExpression or expression.base.kind == AstKind.VariableExpression) then + if not (expression.base.kind == AstKind.IndexExpression or expression.base.kind == AstKind.VariableExpression or expression.base.kind == AstKind.GlobalVariableExpression) then push("(", self:unparseExpression(expression.base, tabbing), ")"); else push(self:unparseExpression(expression.base, tabbing)); diff --git a/src/prometheus/visitast.lua b/src/prometheus/visitast.lua index c7e963dd..ff82a8c0 100644 --- a/src/prometheus/visitast.lua +++ b/src/prometheus/visitast.lua @@ -32,7 +32,7 @@ function visitAst(ast, previsit, postvisit, data) end -- Is Function Block because global scope is treated like a Function - visitBlock(ast.body, previsit, postvisit, data, true); + ast.body = visitBlock(ast.body, previsit, postvisit, data, true); if(type(postvisit) == "function") then ast = postvisit(ast, data) or ast; @@ -53,6 +53,7 @@ local compundStats = lookupify{ function visitBlock(block, previsit, postvisit, data, isFunctionBlock) block.isBlock = true; block.isFunctionBlock = isFunctionBlock or false; + local parentScope = data.scope; data.scope = block.scope; local parentBlockData = data.blockData; data.blockData = {}; @@ -61,7 +62,9 @@ function visitBlock(block, previsit, postvisit, data, isFunctionBlock) local node, skip = previsit(block, data); block = node or block; if skip then - data.scope = table.remove(data.scopeStack); + table.remove(data.scopeStack); + data.scope = parentScope; + data.blockData = parentBlockData; return block end end @@ -81,7 +84,8 @@ function visitBlock(block, previsit, postvisit, data, isFunctionBlock) if(type(postvisit) == "function") then block = postvisit(block, data) or block; end - data.scope = table.remove(data.scopeStack); + table.remove(data.scopeStack); + data.scope = parentScope; data.blockData = parentBlockData; return block; end @@ -129,7 +133,10 @@ function visitStatement(statement, previsit, postvisit, data) statement.body = visitBlock(statement.body, previsit, postvisit, data, false); elseif(statement.kind == AstKind.RepeatStatement) then statement.body = visitBlock(statement.body, previsit, postvisit, data); + local parentScope = data.scope; + data.scope = statement.body.scope; statement.condition = visitExpression(statement.condition, previsit, postvisit, data); + data.scope = parentScope; elseif(statement.kind == AstKind.ForStatement) then statement.initialValue = visitExpression(statement.initialValue, previsit, postvisit, data); statement.finalValue = visitExpression(statement.finalValue, previsit, postvisit, data); diff --git a/src/prometheus/vm/analysis.lua b/src/prometheus/vm/analysis.lua index 6cc13922..2561ce7d 100644 --- a/src/prometheus/vm/analysis.lua +++ b/src/prometheus/vm/analysis.lua @@ -1,23 +1,13 @@ --- Identify each lexical scope's function and locals shared with nested closures. -local visitast = require('prometheus.visitast') +-- Adapt the shared lexical analysis to the VM's scope/slot lookup tables. +local analyzeBindings = require('prometheus.bindinganalysis') return function(ast) - local analysis = {owners = {}, captured = {}} - visitast(ast, function(node, data) - if node.kind == 'Block' then - analysis.owners[node.scope] = data.functionData.scope - end - end) - visitast(ast, function(node, data) - local kind = node.kind - if kind == 'VariableExpression' or kind == 'AssignmentVariable' - or kind == 'FunctionDeclaration' then - local scope = node.scope - if not scope.isGlobal and analysis.owners[scope] ~= data.functionData.scope then - analysis.captured[scope] = analysis.captured[scope] or {} - analysis.captured[scope][node.id] = true - end - end - end) + local bindings = analyzeBindings(ast) + local analysis = {owners = bindings.owners, captured = {}} + for symbol in pairs(bindings.captured) do + local scope, id = symbol.scope, symbol.id + analysis.captured[scope] = analysis.captured[scope] or {} + analysis.captured[scope][id] = true + end return analysis end diff --git a/src/prometheus/vm/expressions.lua b/src/prometheus/vm/expressions.lua index efba259a..1bebec9c 100644 --- a/src/prometheus/vm/expressions.lua +++ b/src/prometheus/vm/expressions.lua @@ -81,7 +81,7 @@ local function install(Compiler) return load(self, numberValue(node.value)) elseif kind == Kind.BooleanExpression or kind == Kind.StringExpression then return load(self, node.value) - elseif kind == Kind.VariableExpression then + elseif kind == Kind.VariableExpression or kind == Kind.GlobalVariableExpression then return self:readVariable(node.scope, node.id) elseif kind == Kind.VarargExpression then local tuple = self:alloc(); self:emit("VARARG", tuple) diff --git a/src/prometheus/vm/flatten.lua b/src/prometheus/vm/flatten.lua index d559b106..e719a939 100644 --- a/src/prometheus/vm/flatten.lua +++ b/src/prometheus/vm/flatten.lua @@ -17,17 +17,18 @@ local function externalVariables(ast, runtime) local writes = false visitast(wrapper, function(node) local kind = node.kind - if kind == 'VariableExpression' or kind == 'AssignmentVariable' + if kind == 'VariableExpression' or kind == 'GlobalVariableExpression' + or kind == 'AssignmentVariable' or kind == 'AssignmentGlobalVariable' or kind == 'FunctionDeclaration' then local scope = node.scope if not scope.isGlobal and not owned[scope] then - if kind == 'AssignmentVariable' + if kind == 'AssignmentVariable' or kind == 'AssignmentGlobalVariable' or (kind == 'FunctionDeclaration' and #node.indices == 0) then writes = true end seen[scope] = seen[scope] or {} if not seen[scope][node.id] then - variables[#variables + 1] = scope:getVariableName(node.id) + variables[#variables + 1] = node.symbol seen[scope][node.id] = true end end @@ -44,23 +45,59 @@ return function(ast) if writes then return nil end local parser = Parser:new({LuaVersion = Enums.LuaVersion.Lua51, AllowInternalIdentifiers = true}) local unparser = Unparser:new({LuaVersion = Enums.LuaVersion.Lua51}) + -- Serialize captures through fresh marker names, then reconnect the parsed + -- forwarding references to their original symbols. Textual name resolution + -- would silently rebind them when surrounding locals are renamed. + local used, markers, captures = {}, {}, {} + visitast(ast, function(node) + if node.scope then + for _, name in pairs(node.scope.variables) do used[name] = true end + end + if node.name then used[node.name] = true end + end) + local markerNames = {} + for i, symbol in ipairs(variables) do + local name = '__prometheus_capture_' .. i + while used[name] do name = name .. '_' end + used[name], markers[symbol], captures[name], markerNames[i] = true, name, symbol, name + end + local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})}, + Scope:new(ast.globalScope)), ast.globalScope) + visitast(wrapper, nil, function(node) + local name = markers[node.symbol] + if name then + if node.kind == 'VariableExpression' then return Ast.GlobalVariableExpression(name) end + if node.kind == 'FunctionDeclaration' then + node.symbol, node.name = nil, name + node.scope, node.id = ast.globalScope:resolve(name) + node.baseScope, node.baseId = node.scope, node.id + end + end + end) local runtimeSource = unparser:unparseExpression(runtime, 0) local prefix = '' local arguments = '...' if #variables > 0 then - prefix = 'local ' .. table.concat(variables, ',') .. '=...;' + prefix = 'local ' .. table.concat(markerNames, ',') .. '=...;' arguments = 'select(' .. (#variables + 1) .. ',...)' end local standalone = parser:parse(prefix .. 'return (' .. runtimeSource .. ')(' .. arguments .. ')') local flattened = unparser:unparse(CffCompiler:new():compile(standalone)) - local forwarded = #variables > 0 and table.concat(variables, ',') .. ',...' or '...' + local forwarded = #variables > 0 and table.concat(markerNames, ',') .. ',...' or '...' local replacementSource = 'return function(...) return (function(...) ' .. flattened .. ' end)(' .. forwarded .. ') end' local replacement = parser:parse(replacementSource).body.statements[1].args[1] + local replacementAst = Ast.TopNode(Ast.Block({Ast.ReturnStatement({replacement})}, + Scope:new(ast.globalScope)), ast.globalScope) + visitast(replacementAst, nil, function(node) + if node.kind == 'GlobalVariableExpression' and captures[node.name] then + return Ast.VariableExpression(captures[node.name]) + end + end) visitast(ast, function(node) if node == runtime then return replacement, true end end) - -- Reparsing resolves injected free-variable names against their original outer - -- scopes, including helpers installed between Vmify and Cff by EncryptStrings. - return parser:parse(unparser:unparse(ast)) + ast.vmRuntime = replacement + require('prometheus.bindinganalysis')(ast) + return ast end diff --git a/src/prometheus/vm/statements.lua b/src/prometheus/vm/statements.lua index 80bc4e04..89e10c02 100644 --- a/src/prometheus/vm/statements.lua +++ b/src/prometheus/vm/statements.lua @@ -166,7 +166,7 @@ return function(Compiler) end local function assignmentTarget(self, target) - if target.kind == K.AssignmentVariable then return {scope = target.scope, id = target.id} end + if target.kind == K.AssignmentVariable or target.kind == K.AssignmentGlobalVariable then return {scope = target.scope, id = target.id} end if target.kind == K.AssignmentIndexing then local base = self:expression(target.base, 1)[1] local key = self:expression(target.index, 1)[1] diff --git a/src/prometheus/vm/strings.lua b/src/prometheus/vm/strings.lua index edeab7c1..675c3b1e 100644 --- a/src/prometheus/vm/strings.lua +++ b/src/prometheus/vm/strings.lua @@ -21,14 +21,11 @@ return function(ast) variables[id] = cacheScope:addVariable() values[id] = Ast.IndexExpression(Ast.VariableExpression(cacheScope, cache), Ast.NumberExpression(id)) - cacheScope:addReferenceToHigherScope(cacheScope, cache) end - context.scope:addReferenceToHigherScope(cacheScope, variables[id]) return Ast.VariableExpression(cacheScope, variables[id]) end end) if not data then return end - cacheScope:addReferenceToHigherScope(scope, data) table.insert(runtime.body.statements, 1, Ast.LocalVariableDeclaration(cacheScope, {cache}, {Ast.VariableExpression(scope, data)})) table.insert(runtime.body.statements, 2, diff --git a/tests/harness/ast-bindings.lua b/tests/harness/ast-bindings.lua new file mode 100644 index 00000000..24847131 --- /dev/null +++ b/tests/harness/ast-bindings.lua @@ -0,0 +1,153 @@ +local Prometheus = require('src.prometheus') +Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error +local Ast = require('prometheus.ast') +local analyze = require('prometheus.bindinganalysis') +local Parser = require('prometheus.parser') +local K = Ast.AstKind +local function parse(source) return Parser:new({LuaVersion = 'Lua51'}):parse(source) end +local function tree(statements) return Ast.TopNode(Ast.Block(statements)) end +local function rejects(ast, message) + local ok, err = pcall(analyze, ast) + assert(not ok and tostring(err):find(message, 1, true), tostring(err)) +end +local function run(ast) + local pipeline = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, Steps = {}}) + pipeline:renameVariables(ast) + return assert(loadstring(pipeline:unparse(ast)))() +end + +-- Binding identity does not depend on source spelling or construction scopes. +local x, y = Ast.LocalSymbol('x'), Ast.LocalSymbol('x') +local read = Ast.VariableExpression(x) +local ast = tree({ + Ast.LocalVariableDeclaration({x}, {Ast.NumberExpression(41)}), + Ast.LocalVariableDeclaration({y}, {Ast.AddExpression(read, Ast.NumberExpression(1))}), + Ast.ReturnStatement({Ast.VariableExpression(x), Ast.VariableExpression(y)}), +}) +analyze(ast) +local plain = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, Steps = {}}):unparse(ast) +local plainA, plainB = assert(loadstring(plain))() +assert(plainA == 41 and plainB == 42, 'Direct emission must preserve symbol identity despite name collisions') +local a, b = run(ast) +assert(a == 41 and b == 42) +assert(read.symbol == x and x ~= y) + +-- Reanalysis ignores stale metadata and counts actual occurrences, not constructors. +local unused = Ast.VariableExpression(x) +local count = x.scope.referenceCounts[x.id] +analyze(ast) +assert(x.scope.referenceCounts[x.id] == count) +local inner = Ast.Block({Ast.ReturnStatement({Ast.VariableExpression(x)})}) +ast.body.statements[#ast.body.statements] = Ast.DoStatement(inner) +analyze(ast) +assert(inner.scope.parentScope == ast.body.scope) +assert(inner.scope.variablesFromHigherScopes[x.scope][x.id] == 1) +assert(run(ast) == 41) + +local missing = Ast.LocalSymbol('missing') +rejects(tree({Ast.ReturnStatement({Ast.VariableExpression(missing)})}), 'not accessible') +rejects(tree({Ast.ReturnStatement({Ast.VariableExpression(missing)}), + Ast.LocalVariableDeclaration({missing}, {})}), 'not accessible') +rejects(tree({Ast.LocalVariableDeclaration({missing}, {Ast.VariableExpression(missing)})}), 'not accessible') +rejects(tree({Ast.DoStatement(Ast.Block({Ast.LocalVariableDeclaration({missing}, {})})), + Ast.ReturnStatement({Ast.VariableExpression(missing)})}), 'not accessible') +rejects(tree({Ast.LocalVariableDeclaration({missing}, {}), + Ast.LocalVariableDeclaration({missing}, {})}), 'multiple declarations') + +-- Removed declarations and moved definitions are checked against the new tree. +local relocated = Ast.LocalSymbol('relocated') +local declaration = Ast.LocalVariableDeclaration({relocated}, {Ast.NumberExpression(23)}) +local original = Ast.Block({declaration}) +ast = tree({Ast.DoStatement(original)}) +analyze(ast) +original.statements = {} +ast.body.statements[2] = declaration +ast.body.statements[3] = Ast.ReturnStatement({Ast.VariableExpression(relocated)}) +analyze(ast) +assert(relocated.scope == ast.body.scope and run(ast) == 23) +table.remove(ast.body.statements, 2) +rejects(ast, 'not accessible') +local sharedBlock = Ast.Block({}) +rejects(tree({Ast.DoStatement(sharedBlock), Ast.DoStatement(sharedBlock)}), 'multiple times') +rejects(tree({Ast.BreakStatement()}), 'outside a loop') +local invalidIndex = Ast.LocalSymbol('index') +rejects(tree({Ast.ForStatement(invalidIndex, Ast.VariableExpression(invalidIndex), + Ast.NumberExpression(1), Ast.NumberExpression(1), Ast.Block({}))}), 'not accessible') + +-- Reads, writes, and closure capture are derived from actual reference positions. +local shared = Ast.LocalSymbol('shared') +local write = Ast.AssignmentVariable(shared) +local closure = Ast.FunctionLiteralExpression({}, Ast.Block({ + Ast.AssignmentStatement({write}, {Ast.NumberExpression(42)}), + Ast.ReturnStatement({Ast.VariableExpression(shared)}), +})) +ast = tree({Ast.LocalVariableDeclaration({shared}, {Ast.NumberExpression(0)}), + Ast.ReturnStatement({closure})}) +local info = analyze(ast) +assert(info.captured[shared] and #info.reads[shared] == 1 and #info.writes[shared] == 1) +assert(info.writes[shared][1] == write and run(ast)() == 42) + +-- Global accesses have distinct kinds and never acquire local symbols. +ast = parse('result = print; return result') +assert(ast.body.statements[1].lhs[1].kind == K.AssignmentGlobalVariable) +assert(ast.body.statements[1].rhs[1].kind == K.GlobalVariableExpression) +assert(not ast.body.statements[1].rhs[1].symbol) +assert(run(tree({Ast.ReturnStatement({Ast.GlobalVariableExpression('_VERSION')})})) == _VERSION) +-- A generated local must not shadow a global used anywhere inside its scope. +assert(run(parse('local a = 1; return type(a)')) == 'number') + +-- Lua declaration points, recursive functions, loop headers and repeat conditions. +for _, source in ipairs({ + 'local x = 7; do local x = x; assert(x == 7) end; return 1', + 'local function f(n) if n == 0 then return 1 end return f(n - 1) end; return f(3)', + 'local i = 3; local sum = 0; for i = i, i do sum = sum + i end; return sum', + 'local t = {5}; for t, v in ipairs(t) do assert(v == 5) end; return 1', + 'repeat local x = 9 until x == 9; return 1', + 'local x = 1; return (function() return function() return x end end)()()', +}) do ast = parse(source); analyze(ast); assert(run(ast)) end + +-- Symbols are also the construction API for parameters, functions, and loops. +local f, parameter = Ast.LocalSymbol('f'), Ast.LocalSymbol('parameter') +ast = tree({Ast.LocalFunctionDeclaration(f, {parameter}, Ast.Block({ + Ast.ReturnStatement({Ast.VariableExpression(parameter)}), +})), Ast.ReturnStatement({Ast.FunctionCallExpression(Ast.VariableExpression(f), {Ast.NumberExpression(17)})})}) +assert(run(ast) == 17) +local index = Ast.LocalSymbol('index') +assert(run(tree({Ast.ForStatement(index, Ast.NumberExpression(1), Ast.NumberExpression(1), + Ast.NumberExpression(1), Ast.Block({Ast.ReturnStatement({Ast.VariableExpression(index)})}))})) == 1) + +-- Function assignment targets can be local bindings or distinct global nodes. +f, parameter = Ast.LocalSymbol('f'), Ast.LocalSymbol('argument') +ast = tree({Ast.LocalVariableDeclaration({f}, {}), + Ast.FunctionDeclaration(Ast.AssignmentVariable(f), {}, {parameter}, Ast.Block({ + Ast.ReturnStatement({Ast.VariableExpression(parameter)}), + })), Ast.ReturnStatement({Ast.FunctionCallExpression(Ast.VariableExpression(f), {Ast.NumberExpression(19)})})}) +assert(run(ast) == 19) +ast = tree({Ast.FunctionDeclaration(Ast.AssignmentGlobalVariable('example'), {}, {}, + Ast.Block({Ast.ReturnStatement({Ast.NumberExpression(29)})})), + Ast.ReturnStatement({Ast.FunctionCallExpression(Ast.GlobalVariableExpression('example'), {})})}) +local output = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, Steps = {}}):unparse(ast) +local chunk = assert(loadstring(output)) +setfenv(chunk, {}) +assert(chunk() == 29) +local key, value = Ast.LocalSymbol('key'), Ast.LocalSymbol('value') +ast = tree({Ast.ForInStatement({key, value}, {Ast.FunctionCallExpression(Ast.GlobalVariableExpression('ipairs'), { + Ast.TableConstructorExpression({Ast.TableEntry(Ast.NumberExpression(31))}), +})}, Ast.Block({Ast.ReturnStatement({Ast.VariableExpression(value)})}))}) +assert(run(ast) == 31) + +-- Nested declarations must not capture an explicitly retained outer reference. +local outer, shadow = Ast.LocalSymbol('value'), Ast.LocalSymbol('value') +ast = tree({Ast.LocalVariableDeclaration({outer}, {Ast.NumberExpression(1)}), + Ast.DoStatement(Ast.Block({Ast.LocalVariableDeclaration({shadow}, {Ast.NumberExpression(2)}), + Ast.ReturnStatement({Ast.VariableExpression(outer)})}))}) +plain = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, Steps = {}}):unparse(ast) +assert(assert(loadstring(plain))() == 1) + +-- Lowering a recursive local function must preserve its declaration point. +local proxy = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, + Steps = {{Name = 'ProxifyLocals'}}}):apply( + 'local function f(n) if n == 0 then return 1 end return f(n - 1) end return f(3)') +assert(assert(loadstring(proxy))() == 1) + +print('AST binding identity, visibility, globals and reconstruction checks passed') From 7ebd233a1ff71958cc2e69827097ac4e01a91a70 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Mon, 2 Nov 2026 13:48:40 +0100 Subject: [PATCH 2/5] Migrate AST construction to symbols and remove legacy scopes --- doc/advanced/ast-bindings.md | 106 +++- doc/advanced/register-vm.md | 19 +- src/prometheus/ast.lua | 196 ++------ src/prometheus/bindinganalysis.lua | 18 +- src/prometheus/cff/block.lua | 5 - src/prometheus/cff/compile_core.lua | 5 +- src/prometheus/cff/compile_top.lua | 55 +- src/prometheus/cff/compiler.lua | 387 +++++++------- src/prometheus/cff/emit.lua | 54 +- src/prometheus/cff/expressions/and.lua | 20 +- src/prometheus/cff/expressions/binary.lua | 7 +- src/prometheus/cff/expressions/boolean.lua | 5 +- .../cff/expressions/function_call.lua | 15 +- .../cff/expressions/function_literal.lua | 3 +- src/prometheus/cff/expressions/if_else.lua | 31 +- src/prometheus/cff/expressions/index.lua | 5 +- src/prometheus/cff/expressions/len.lua | 5 +- src/prometheus/cff/expressions/negate.lua | 5 +- src/prometheus/cff/expressions/nil.lua | 3 +- src/prometheus/cff/expressions/not.lua | 5 +- src/prometheus/cff/expressions/number.lua | 9 +- src/prometheus/cff/expressions/or.lua | 20 +- .../expressions/pass_self_function_call.lua | 25 +- src/prometheus/cff/expressions/string.lua | 5 +- .../cff/expressions/table_constructor.lua | 13 +- src/prometheus/cff/expressions/vararg.lua | 3 +- src/prometheus/cff/expressions/variable.lua | 13 +- src/prometheus/cff/native.lua | 59 +-- src/prometheus/cff/register.lua | 107 ++-- src/prometheus/cff/statements/assignment.lua | 25 +- .../cff/statements/break_statement.lua | 9 +- src/prometheus/cff/statements/compound.lua | 17 +- .../cff/statements/continue_statement.lua | 9 +- .../cff/statements/for_in_statement.lua | 33 +- .../cff/statements/for_statement.lua | 53 +- .../cff/statements/function_call.lua | 9 +- .../cff/statements/function_declaration.lua | 31 +- .../cff/statements/if_statement.lua | 15 +- .../statements/local_function_declaration.lua | 8 +- .../statements/local_variable_declaration.lua | 7 +- .../statements/pass_self_function_call.lua | 15 +- .../cff/statements/repeat_statement.lua | 11 +- src/prometheus/cff/statements/return.lua | 11 +- .../cff/statements/while_statement.lua | 9 +- src/prometheus/cff/strings.lua | 6 +- src/prometheus/cff/upvalue.lua | 138 +++-- src/prometheus/names.lua | 30 ++ src/prometheus/parser.lua | 130 +++-- src/prometheus/pipeline.lua | 4 +- src/prometheus/scope.lua | 245 --------- src/prometheus/steps/ConstantArray.lua | 96 ++-- src/prometheus/steps/EncryptStrings.lua | 21 +- src/prometheus/steps/ProxifyLocals.lua | 474 +++++++----------- src/prometheus/steps/SplitStrings.lua | 48 +- src/prometheus/steps/Watermark.lua | 17 +- src/prometheus/steps/WatermarkCheck.lua | 8 +- src/prometheus/steps/WrapInFunction.lua | 7 +- src/prometheus/unparser.lua | 25 +- src/prometheus/vm/compiler.lua | 4 +- src/prometheus/vm/flatten.lua | 21 +- src/prometheus/vm/statements.lua | 2 +- src/prometheus/vm/strings.lua | 21 +- tests/harness/ast-bindings.lua | 50 ++ tests/harness/cff-native.lua | 17 +- tests/harness/cff-strings.lua | 21 +- tests/harness/vm-data.lua | 7 +- 66 files changed, 1193 insertions(+), 1664 deletions(-) create mode 100644 src/prometheus/names.lua delete mode 100644 src/prometheus/scope.lua diff --git a/doc/advanced/ast-bindings.md b/doc/advanced/ast-bindings.md index f2036b58..4f2fe481 100644 --- a/doc/advanced/ast-bindings.md +++ b/doc/advanced/ast-bindings.md @@ -1,48 +1,104 @@ # AST Bindings -The AST represents each local binding with a stable `LocalSymbol`. A symbol is the identity of one declaration; its name can change during transformations, and binding analysis derives its lexical scope and slot. Local reference nodes point to the same symbol, so transforms do not need to keep references synchronized with scope tables. +The AST identifies each local binding with a stable `LocalSymbol`. A symbol is +the identity of one declaration, independent of its source spelling, lexical +owner, or eventual register slot. Declarations and references point to the same +symbol through the canonical `symbol` or `symbols` field. Transforms should +preserve those identities when moving nodes and create fresh symbols when they +introduce new bindings. ## Constructing local bindings -Create a symbol for each local declaration, then use that same object for every reference to the binding: +Create one symbol for each local declaration and reuse it for every read and +write of that binding: ```lua local Ast = require("prometheus.ast") -local symbol = Ast.LocalSymbol("value") +local value = Ast.LocalSymbol("value") -local declaration = Ast.LocalVariableDeclaration({symbol}, { +local declaration = Ast.LocalVariableDeclaration({value}, { Ast.NumberExpression(10), }) -local reference = Ast.VariableExpression(symbol) -local write = Ast.AssignmentVariable(symbol) +local read = Ast.VariableExpression(value) +local write = Ast.AssignmentVariable(value) ``` -`Ast.LocalFunctionDeclaration(symbol, args, body)` and `Ast.FunctionDeclaration(globalReference, indices, args, body)` also accept symbol-based inputs. `Ast.ForStatement(symbol, initial, final, increment, body)` and `Ast.ForInStatement(symbols, expressions, body)` define loop bindings. `Ast.Block(statements)` and `Ast.TopNode(body)` need no scope arguments. Function arguments are local symbols; use `Ast.VarargExpression()` for a vararg argument. The AST constructors that accept an existing scope and numeric ID remain available as compatibility construction adapters for existing generators. +The AST constructors are symbol-only. `Ast.LocalFunctionDeclaration(symbol, +args, body)` declares a local function. `Ast.FunctionDeclaration(target, +indices, args, body)` accepts a global reference or a local reference as its +target. `Ast.ForStatement(symbol, initial, final, increment, body)` and +`Ast.ForInStatement(symbols, expressions, body)` define loop bindings. +`Ast.LocalVariableDeclaration(symbols, expressions)` takes symbols in its +first argument. `Ast.Block(statements)` and `Ast.TopNode(body)` take no scope +argument. Function arguments are local reference nodes (or symbols, which the +function constructors convert to reference nodes); use +`Ast.VarargExpression()` for a vararg argument. -## Globals - -Globals are names, not local bindings. Use `Ast.GlobalVariableExpression(name)` for a read and `Ast.AssignmentGlobalVariable(name)` for a write. Analysis fills their compatibility `scope` and `id` fields, while `.name` remains the source-level global name. `Ast.VariableExpression(scope, id)` and `Ast.AssignmentVariable(scope, id)` still act as construction adapters when the supplied scope is global. - -## Running binding analysis - -`require("prometheus.bindinganalysis")(ast)` rebuilds lexical metadata for a `TopNode` and returns an analysis record. `Parser:parse` runs it before returning, and `Unparser:unparse` runs it before output, so callers using those APIs directly get analyzed ASTs. AST transforms that need metadata before either entry point can invoke the analysis module themselves after making their changes. +Old scope-plus-numeric-ID construction signatures have been removed. Do not +pass scopes or IDs to AST constructors. -Analysis derives the legacy `scope`, `id`, declaration `ids`, and scope reference tables from symbols and tree structure. Treat those fields as compatibility data: do not use them as the identity of a local or manually adjust reference counts, upvalue counts, parent links, or scope levels. For a local reference, `.symbol` is authoritative. Global references use `.name` as their identity. - -The returned record exposes `declarations[symbol]`, `references[symbol]`, `reads[symbol]`, `writes[symbol]`, `captured[symbol]`, and `owners[scope]`. Read/write lists describe reference occurrences; declaration initialization is represented by the declaration itself. The pipeline refreshes analysis after each transformation and before renaming, and compiler entry points analyze their inputs. +## Globals -The unparser preserves existing local names where possible and changes colliding names as needed to preserve binding identity, even when obfuscating renaming has not been requested. +Globals are identified by their names and have no local symbol. Use +`Ast.GlobalVariableExpression(name)` for a read and +`Ast.AssignmentGlobalVariable(name)` for a write. Their canonical identity is +the node's `name` field. + +## Parsing, analysis, and naming + +The parser uses temporary `Bindings` environments while it reads source. Those +environments track declaration visibility, disabled names, and lexical lookup +during parsing. They are distinct from the fresh plain scope records later +derived by binding analysis, and are not AST binding identities. + +`require("prometheus.bindinganalysis")(ast)` rebuilds derived lexical metadata +from declarations, references, and tree structure. It returns an analysis +record with `declarations[symbol]`, `references[symbol]`, `reads[symbol]`, +`writes[symbol]`, `captured[symbol]`, and `owners[scope]`. It also attaches fresh +plain scope and slot metadata for consumers that need lexical ownership or +lowering locations. That metadata is derived on each analysis pass; it is not +the identity of a binding. The canonical AST identity remains +`node.symbol`/`node.symbols` for locals and `node.name` for globals. + +`Parser:parse` analyzes its result before returning. The pipeline refreshes +analysis after transformations, and `Unparser:unparse` analyzes before output. +Transforms that need derived ownership information at another point can call +the analyzer after changing the tree. + +`prometheus.names` allocates local names from analyzed metadata. It updates +symbol names while preserving symbol identity and avoids names that would +capture references from nested functions. Name allocation requires a fresh +binding analysis first. ## Visibility and captures -The tree order determines visibility. A local declaration's initializer expressions are analyzed before that declaration becomes active, matching Lua's rule that a local is not visible in its own initializer. Local functions are active while their bodies are analyzed, allowing recursion. Function arguments and loop variables are active in their function or loop body. A reference is valid only while its symbol is active in the enclosing lexical path. References from a nested function are recorded as captures. +Analysis follows Lua declaration order. A local declaration's initializer is +analyzed before its symbol becomes active, so the local is not visible in its +own initializer. Local functions are active while their bodies are analyzed, +which allows recursion. Function arguments and loop variables are active in +their function or loop body. A reference is valid only while its symbol is +active on the enclosing lexical path. References from nested functions are +recorded as captures. -Binding analysis does not widen a scope to make a reference valid. If a transform moves a reference outside the symbol's lexical region, it must also restructure the declarations and references so the binding is valid; analysis will report an inaccessible local symbol otherwise. +Analysis does not widen a lexical region to make a reference valid. If a +transform moves a reference outside its symbol's declaration region, it must +also restructure the declarations and references. Otherwise analysis reports +an inaccessible local symbol. ## Moving and cloning nodes -Moving a complete subtree is safe when it remains within the lexical region of every symbol it uses and the destination preserves the intended declaration order. Re-run binding analysis after structural changes that affect declarations, references, or scope ownership. - -Do not place one declaration symbol in two declaration nodes or share the same block object at multiple tree positions. Analysis rejects duplicate symbol declarations and repeated blocks. When cloning code that introduces a new local binding, create a fresh `Ast.LocalSymbol` for the clone and point its cloned references to that new symbol. Reusing the original symbol would make two declarations claim one binding identity. Global nodes can be cloned by name because globals are identified by their string names. - -Older transforms may still construct nodes using `scope` and numeric IDs. These adapters are supported for compatibility, but new transforms should create symbols and let binding analysis rebuild derived scope metadata instead of manually wiring references. The old scope-parent and reference mutation methods have been removed. Numeric IDs may change after every analysis; do not retain them across transformations. +Moving a complete subtree is safe when it remains within the lexical region of +every symbol it uses and the destination preserves declaration order. Re-run +binding analysis after structural changes that affect declarations, +references, or lexical ownership. + +Do not place one declaration symbol in two declaration nodes or share the same +block object at multiple tree positions. Analysis rejects duplicate symbol +declarations and repeated blocks. When cloning code that introduces a new +local binding, create a fresh `Ast.LocalSymbol` for the clone and point its +cloned references to that symbol. Reusing the original symbol would make two +declarations claim one identity. Global nodes can be cloned by name. + +Lowering backends may attach private slot or scope metadata for their own +implementation. Such fields are derived and backend-specific; AST transforms +must use symbols and must not rely on those fields for binding identity. diff --git a/doc/advanced/register-vm.md b/doc/advanced/register-vm.md index 9d7057e6..1e5a9550 100644 --- a/doc/advanced/register-vm.md +++ b/doc/advanced/register-vm.md @@ -1,12 +1,23 @@ # Register VM architecture -`Vmify` compiles the scoped Prometheus AST directly into instructions executed -by a generated Lua interpreter. It does not use `string.dump`, an external Lua -compiler, or `loadstring` to execute the original source. +`Vmify` compiles the symbol-bound Prometheus AST directly into instructions +executed by a generated Lua interpreter. It does not use `string.dump`, an +external Lua compiler, or `loadstring` to execute the original source. + +Local declarations and references in the input AST share stable `LocalSymbol` +identities through their `symbols` and `symbol` fields. The VM's binding +analysis derives lexical ownership, captures, and private register slots from +those identities before lowering. Parser `Bindings` environments are temporary +parse-time lookup state; scope and slot records used by lowering are derived +metadata, not AST binding identities. VM lowering may keep its own private +scope and slot metadata, while AST transformations must continue to use +symbols. The former scope-plus-numeric-ID AST construction adapters are no +longer supported. ## Modules -- `bindinganalysis.lua`: shared lexical ownership, visibility validation, and captured-local analysis. +- `bindinganalysis.lua`: fresh derived lexical ownership, visibility validation, + and captured-local analysis from AST symbols. - `vm/analysis.lua`: adapts binding analysis to VM scope and slot lookups. - `vm/compiler.lua`: function prototypes, constants, registers, and captures. - `vm/expressions.lua` and `vm/expressions_tables.lua`: expression lowering, diff --git a/src/prometheus/ast.lua b/src/prometheus/ast.lua index d03cad1b..5746ee13 100644 --- a/src/prometheus/ast.lua +++ b/src/prometheus/ast.lua @@ -163,13 +163,8 @@ function Ast.IfElseExpression(condition, true_value, elseifs, false_value) end -- Create Ast Top Node -function Ast.TopNode(body, globalScope) - return { - kind = AstKind.TopNode, - body = body, - globalScope = globalScope, - - } +function Ast.TopNode(body) + return {kind = AstKind.TopNode, body = body}; end function Ast.TableEntry(value) @@ -197,31 +192,16 @@ function Ast.TableConstructorExpression(entries) end -- Create Statement Block -function Ast.Block(statements, scope) - scope = scope or require("prometheus.scope"):new(); - return { - kind = AstKind.Block, - statements = statements, - scope = scope, - } +function Ast.Block(statements) + return {kind = AstKind.Block, statements = statements}; end --- Create Break Statement -function Ast.BreakStatement(loop, scope) - return { - kind = AstKind.BreakStatement, - loop = loop, - scope = scope, - } +function Ast.BreakStatement(loop) + return {kind = AstKind.BreakStatement, loop = loop}; end --- Create Continue Statement -function Ast.ContinueStatement(loop, scope) - return { - kind = AstKind.ContinueStatement, - loop = loop, - scope = scope, - } +function Ast.ContinueStatement(loop) + return {kind = AstKind.ContinueStatement, loop = loop}; end function Ast.PassSelfFunctionCallStatement(base, passSelfFunctionName, args) @@ -323,61 +303,21 @@ function Ast.DoStatement(body) } end -function Ast.WhileStatement(body, condition, parentScope) - return { - kind = AstKind.WhileStatement, - body = body, - condition = condition, - parentScope = parentScope, - } +function Ast.WhileStatement(body, condition) + return {kind = AstKind.WhileStatement, body = body, condition = condition}; end -function Ast.ForInStatement(scope, vars, expressions, body, parentScope) - local symbols = {}; - if not scope.getSymbol then - symbols, expressions, body = scope, vars, expressions; - scope, vars = nil, {}; - else - for i, id in ipairs(vars) do symbols[i] = scope:getSymbol(id); end - end - return { - symbols = symbols, - kind = AstKind.ForInStatement, - scope = scope, - ids = vars, - vars = vars, - expressions = expressions, - body = body, - parentScope = parentScope, - } +function Ast.ForInStatement(symbols, expressions, body) + return {kind = AstKind.ForInStatement, symbols = symbols, expressions = expressions, body = body}; end -function Ast.ForStatement(scope, id, initialValue, finalValue, incrementBy, body, parentScope) - local symbol; - if scope.kind == "LocalSymbol" then - symbol, initialValue, finalValue, incrementBy, body = scope, id, initialValue, finalValue, incrementBy; - scope, id = nil, nil; - else symbol = scope:getSymbol(id); end - return { - symbol = symbol, - kind = AstKind.ForStatement, - scope = scope, - id = id, - initialValue = initialValue, - finalValue = finalValue, - incrementBy = incrementBy, - body = body, - parentScope = parentScope, - } +function Ast.ForStatement(symbol, initialValue, finalValue, incrementBy, body) + return {kind = AstKind.ForStatement, symbol = symbol, initialValue = initialValue, + finalValue = finalValue, incrementBy = incrementBy, body = body}; end -function Ast.RepeatStatement(condition, body, parentScope) - return { - kind = AstKind.RepeatStatement, - body = body, - condition = condition, - parentScope = parentScope, - } +function Ast.RepeatStatement(condition, body) + return {kind = AstKind.RepeatStatement, condition = condition, body = body}; end function Ast.IfStatement(condition, body, elseifs, elsebody) @@ -390,72 +330,25 @@ function Ast.IfStatement(condition, body, elseifs, elsebody) } end -function Ast.FunctionDeclaration(scope, id, indices, args, body) - local symbol, name; - if not scope.getSymbol then - local target = scope; - indices, args, body = id, indices, args; - symbol, name, scope, id = target.symbol, target.name, target.scope, target.id; - else - symbol = not scope.isGlobal and scope:getSymbol(id) or nil; - name = scope.isGlobal and scope:getVariableName(id) or nil; - end +function Ast.FunctionDeclaration(target, indices, args, body) for i, arg in ipairs(args) do if arg.kind == "LocalSymbol" then args[i] = Ast.VariableExpression(arg); end end - return { - symbol = symbol, - name = name, - kind = AstKind.FunctionDeclaration, - scope = scope, - baseScope = scope, - id = id, - baseId = id, - indices = indices, - args = args, - body = body, - getName = function(self) - return self.symbol and self.symbol.name or self.name; - end, - } + return {kind = AstKind.FunctionDeclaration, symbol = target.symbol, name = target.name, + indices = indices, args = args, body = body, + getName = function(self) return self.symbol and self.symbol.name or self.name end}; end -function Ast.LocalFunctionDeclaration(scope, id, args, body) - local symbol; - if scope.kind == "LocalSymbol" then - symbol, args, body, scope, id = scope, id, args, nil, nil; - else symbol = scope:getSymbol(id); end +function Ast.LocalFunctionDeclaration(symbol, args, body) for i, arg in ipairs(args) do if arg.kind == "LocalSymbol" then args[i] = Ast.VariableExpression(arg); end end - return { - symbol = symbol, - kind = AstKind.LocalFunctionDeclaration, - scope = scope, - id = id, - args = args, - body = body, - getName = function(self) - return self.symbol and self.symbol.name or self.name; - end, - } + return {kind = AstKind.LocalFunctionDeclaration, symbol = symbol, args = args, body = body, + getName = function(self) return self.symbol.name end}; end -function Ast.LocalVariableDeclaration(scope, ids, expressions) - local symbols = {}; - if not scope.getSymbol then - symbols, expressions, scope = scope, ids, nil; - ids = {}; - else - for i, id in ipairs(ids) do symbols[i] = scope:getSymbol(id); end - end - return { - symbols = symbols, - kind = AstKind.LocalVariableDeclaration, - scope = scope, - ids = ids, - expressions = expressions, - } +function Ast.LocalVariableDeclaration(symbols, expressions) + return {kind = AstKind.LocalVariableDeclaration, symbols = symbols, expressions = expressions or {}}; end function Ast.VarargExpression() @@ -819,21 +712,15 @@ function Ast.FunctionCallExpression(base, args) end -- A symbol identifies a binding, independently of its name or lexical owner. +local nextSymbol = 0; function Ast.LocalSymbol(name) - return { kind = "LocalSymbol", name = name }; + nextSymbol = nextSymbol + 1; + return {kind = "LocalSymbol", name = name or ("__prometheus_local_" .. nextSymbol)}; end -local function localReference(kind, scope, id) - local symbol; - if scope.kind == "LocalSymbol" then - symbol, scope, id = scope, scope.scope, scope.id; - else - symbol = scope:getSymbol(id); - end - return { - kind = kind, symbol = symbol, scope = scope, id = id, - getName = function(self) return self.symbol.name end, - }; +local function localReference(kind, symbol) + assert(symbol and symbol.kind == "LocalSymbol", "Local references require a LocalSymbol"); + return {kind = kind, symbol = symbol, getName = function(self) return self.symbol.name end}; end function Ast.GlobalVariableExpression(name) @@ -844,23 +731,12 @@ function Ast.AssignmentGlobalVariable(name) return {kind = AstKind.AssignmentGlobalVariable, name = name}; end --- The scope/id forms remain construction adapters for existing generators. -function Ast.VariableExpression(scope, id) - if scope.isGlobal then - local node = Ast.GlobalVariableExpression(scope:getVariableName(id)); - node.scope, node.id = scope, id; - return node; - end - return localReference(AstKind.VariableExpression, scope, id); +function Ast.VariableExpression(symbol) + return localReference(AstKind.VariableExpression, symbol); end -function Ast.AssignmentVariable(scope, id) - if scope.isGlobal then - local node = Ast.AssignmentGlobalVariable(scope:getVariableName(id)); - node.scope, node.id = scope, id; - return node; - end - return localReference(AstKind.AssignmentVariable, scope, id); +function Ast.AssignmentVariable(symbol) + return localReference(AstKind.AssignmentVariable, symbol); end Ast.LocalVariableExpression = Ast.VariableExpression; diff --git a/src/prometheus/bindinganalysis.lua b/src/prometheus/bindinganalysis.lua index c4ea4f31..ea3d6a62 100644 --- a/src/prometheus/bindinganalysis.lua +++ b/src/prometheus/bindinganalysis.lua @@ -1,7 +1,6 @@ --- Rebuild lexical metadata from declarations and uses. Scope/id fields are --- derived adapters for the lowering backends; symbols are binding identities. +-- Derive lexical metadata from declarations and uses. Symbols are binding +-- identities; scope records and register slots are private lowering metadata. local Ast = require('prometheus.ast') -local Scope = require('prometheus.scope') local K = Ast.AstKind local binary = {} @@ -16,19 +15,16 @@ end return function(ast) assert(ast.kind == K.TopNode, 'Binding analysis expects a TopNode') ast.bindingAnalysis = nil - local global = ast.globalScope or Scope:newGlobal() - assert(global.isGlobal, 'TopNode requires a global environment scope') + local global = {isGlobal = true} ast.globalScope = global local analysis = {owners = {}, captured = {}, declarations = {}, references = {}, reads = {}, writes = {}} - local initialized, blocks, active, loops = {}, {}, {}, {} + local blocks, active, loops = {}, {}, {} local expression, statement, block, functionBody local function initialize(scope, parent, owner) - assert(not initialized[scope], 'A lexical scope is shared by multiple blocks') - initialized[scope] = true scope.parentScope, scope.level = parent, parent and parent.level + 1 or 0 scope.children, scope.referenceCounts, scope.variablesFromHigherScopes = {}, {}, {} - scope.variables, scope.variablesLookup, scope.symbols, scope.skipIdLookup = {}, {}, {}, {} + scope.variables, scope.variablesLookup, scope.symbols = {}, {}, {} if parent then parent.children[#parent.children + 1] = scope end analysis.owners[scope] = owner or scope end @@ -72,7 +68,6 @@ return function(ast) if analysis.owners[target] ~= owner then analysis.captured[symbol] = true end end node.scope, node.id = target, id - if node.kind == K.FunctionDeclaration then node.baseScope, node.baseId = target, id end target.referenceCounts[id] = (target.referenceCounts[id] or 0) + 1 local current = scope while current and current ~= target do @@ -138,7 +133,7 @@ return function(ast) assert(node and node.kind == K.Block, 'Expected a block') assert(not blocks[node], 'A block occurs multiple times in the AST') blocks[node] = true - local scope = node.scope or Scope:new() + local scope = {isGlobal = false} node.scope = scope initialize(scope, parent, owner) owner = owner or scope @@ -190,7 +185,6 @@ return function(ast) elseif kind == K.ForInStatement then node.scope, node.ids = inner, {} for i, symbol in ipairs(node.symbols) do node.ids[i] = bind(symbol, node, inner, locals) end - node.vars = node.ids end end, function(inner) if kind == K.RepeatStatement then expression(node.condition, inner, owner) end diff --git a/src/prometheus/cff/block.lua b/src/prometheus/cff/block.lua index a4cddcc6..efca1c6c 100644 --- a/src/prometheus/cff/block.lua +++ b/src/prometheus/cff/block.lua @@ -3,8 +3,6 @@ -- block.lua -- -- Block management for the compiler - -local Scope = require("prometheus.scope"); local util = require("prometheus.util"); local lookupify = util.lookupify; @@ -16,12 +14,9 @@ return function(Compiler) id = math.random(0, 2^24) until not self.usedBlockIds[id]; self.usedBlockIds[id] = true; - - local scope = Scope:new(self.containerFuncScope); local block = { id = id; statements = {}; - scope = scope; advanceToNextBlock = true; }; table.insert(self.blocks, block); diff --git a/src/prometheus/cff/compile_core.lua b/src/prometheus/cff/compile_core.lua index ef779ab7..c580c70d 100644 --- a/src/prometheus/cff/compile_core.lua +++ b/src/prometheus/cff/compile_core.lua @@ -34,16 +34,15 @@ return function(Compiler) (numReturns == self.RETURN_ALL or numReturns > 1) then -- Parentheses collapse multiple values to one, even when it is nil. local regs = handler(self, expression, funcDepth, 1); - local scope = self.activeBlock.scope; if numReturns == self.RETURN_ALL then local packed = self:allocRegister(false); - self:addStatement(self:setRegister(scope, packed, self:pack(scope, {self:register(scope, regs[1])})), {packed}, {regs[1]}, false); + self:addStatement(self:setRegister(packed, self:pack({self:register(regs[1])})), {packed}, {regs[1]}, false); self:freeRegister(regs[1], false); return {packed}; end for i = 2, numReturns do regs[i] = self:allocRegister(false); - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end return regs; end diff --git a/src/prometheus/cff/compile_top.lua b/src/prometheus/cff/compile_top.lua index eb518078..64cbc47c 100644 --- a/src/prometheus/cff/compile_top.lua +++ b/src/prometheus/cff/compile_top.lua @@ -15,7 +15,6 @@ return function(Compiler) function Compiler:compileTopNode(node) if self.nativeExpressions then self:prepareNativeExpressions(node); end local startBlock = self:createBlock(); - local scope = startBlock.scope; self.startBlockId = startBlock.id; self:setActiveBlock(startBlock); @@ -51,12 +50,12 @@ return function(Compiler) end, nil, nil) self.varargReg = self:allocRegister(true); - self:addStatement(self:setRegister(scope, self.varargReg, Ast.VariableExpression(self.containerFuncScope, self.argsVar)), {self.varargReg}, {}, false); + self:addStatement(self:setRegister(self.varargReg, Ast.VariableExpression(self.argsVar)), {self.varargReg}, {}, false); self:compileBlock(node.body, 0); if(self.activeBlock.advanceToNextBlock) then - self:addStatement(self:setPos(self.activeBlock.scope, nil), {self.POS_REGISTER}, {}, false); - self:addStatement(self:setReturn(self.activeBlock.scope, self:pack(self.activeBlock.scope, {})), {self.RETURN_REGISTER}, {}, false) + self:addStatement(self:setPos(nil), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setReturn(self:pack({})), {self.RETURN_REGISTER}, {}, false) self.activeBlock.advanceToNextBlock = false; end @@ -85,14 +84,14 @@ return function(Compiler) local scopeFuncDepth = self.scopeFunctionDepths[scope]; local expression; if(scopeFuncDepth == funcDepth) then - expression = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {}); + expression = Ast.FunctionCallExpression(Ast.VariableExpression(self.allocUpvalFunction), {}); elseif(scopeFuncDepth == funcDepth - 1) then local varReg = self:getVarRegister(scope, id, scopeFuncDepth, nil); - expression = self:register(oldActiveBlock.scope, varReg); + expression = self:register(varReg); table.insert(usedRegs, varReg); else local higherId = oldGetUpvalueId(self, scope, id); - expression = Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(higherId)); + expression = Ast.IndexExpression(Ast.VariableExpression(self.currentUpvaluesVar), Ast.NumberExpression(higherId)); end table.insert(upvalueExpressions, Ast.TableEntry(expression)); local uid = #upvalueExpressions; @@ -102,25 +101,24 @@ return function(Compiler) local block = self:createBlock(); self:setActiveBlock(block); - local scope = self.activeBlock.scope; self:pushRegisterUsageInfo(); for i, arg in ipairs(node.args) do if(arg.kind == AstKind.VariableExpression or arg.kind == AstKind.GlobalVariableExpression) then if(self:isUpvalue(arg.scope, arg.id)) then local argReg = self:getVarRegister(arg.scope, arg.id, funcDepth, nil); - self:addStatement(self:setRegister(scope, argReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {argReg}, {}, false); - self:addStatement(self:setUpvalueMember(scope, self:register(scope, argReg), Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.argsVar), Ast.NumberExpression(i))), {}, {argReg}, true); + self:addStatement(self:setRegister(argReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.allocUpvalFunction), {})), {argReg}, {}, false); + self:addStatement(self:setUpvalueMember(self:register(argReg), Ast.IndexExpression(Ast.VariableExpression(self.argsVar), Ast.NumberExpression(i))), {}, {argReg}, true); else local argReg = self:getVarRegister(arg.scope, arg.id, funcDepth, nil); - self:addStatement(self:setRegister(scope, argReg, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.argsVar), Ast.NumberExpression(i))), {argReg}, {}, false); + self:addStatement(self:setRegister(argReg, Ast.IndexExpression(Ast.VariableExpression(self.argsVar), Ast.NumberExpression(i))), {argReg}, {}, false); end else self.varargReg = self:allocRegister(true); - self:addStatement(self:setRegister(scope, self.varargReg, self:pack(scope, { - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.selectVar), { + self:addStatement(self:setRegister(self.varargReg, self:pack({ + Ast.FunctionCallExpression(Ast.VariableExpression(self.selectVar), { Ast.NumberExpression(i); - Ast.FunctionCallExpression(self:unpack(scope), { - Ast.VariableExpression(self.containerFuncScope, self.argsVar), + Ast.FunctionCallExpression(self:unpack(), { + Ast.VariableExpression(self.argsVar), }); }), })), {self.varargReg}, {}, false); @@ -129,8 +127,8 @@ return function(Compiler) self:compileBlock(node.body, funcDepth); if(self.activeBlock.advanceToNextBlock) then - self:addStatement(self:setPos(self.activeBlock.scope, nil), {self.POS_REGISTER}, {}, false); - self:addStatement(self:setReturn(self.activeBlock.scope, self:pack(self.activeBlock.scope, {})), {self.RETURN_REGISTER}, {}, false); + self:addStatement(self:setPos(nil), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setReturn(self:pack({})), {self.RETURN_REGISTER}, {}, false); self.activeBlock.advanceToNextBlock = false; end @@ -142,30 +140,27 @@ return function(Compiler) self:popRegisterUsageInfo(); self:setActiveBlock(oldActiveBlock); - - local scope = self.activeBlock.scope; - local retReg = self:allocRegister(false); local isVarargFunction = #node.args > 0 and node.args[#node.args].kind == AstKind.VarargExpression; local retrieveExpression if isVarargFunction then - retrieveExpression = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.createVarargClosureVar), { + retrieveExpression = Ast.FunctionCallExpression(Ast.VariableExpression(self.createVarargClosureVar), { Ast.NumberExpression(block.id), Ast.TableConstructorExpression(upvalueExpressions), - self:env(scope) + self:env() }); else - local varScope, var = self:getCreateClosureVar(#node.args + math.random(0, 5)); - retrieveExpression = Ast.FunctionCallExpression(Ast.VariableExpression(varScope, var), { + local var = self:getCreateClosureVar(#node.args + math.random(0, 5)); + retrieveExpression = Ast.FunctionCallExpression(Ast.VariableExpression(var), { Ast.NumberExpression(block.id), Ast.TableConstructorExpression(upvalueExpressions), - self:env(scope) + self:env() }); end - self:addStatement(self:setRegister(scope, retReg, retrieveExpression), {retReg}, usedRegs, false); + self:addStatement(self:setRegister(retReg, retrieveExpression), {retReg}, usedRegs, false); return retReg; end @@ -173,16 +168,14 @@ return function(Compiler) for i, stat in ipairs(block.statements) do self:compileStatement(stat, funcDepth); end - - local scope = self.activeBlock.scope; for id, name in ipairs(block.scope.variables) do local varReg = self:getVarRegister(block.scope, id, funcDepth, nil); if self:isUpvalue(block.scope, id) then - self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.freeUpvalueFunc), { - self:register(scope, varReg) + self:addStatement(self:setRegister(varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.freeUpvalueFunc), { + self:register(varReg) })), {varReg}, {varReg}, false); else - self:addStatement(self:setRegister(scope, varReg, Ast.NilExpression()), {varReg}, {}, false); + self:addStatement(self:setRegister(varReg, Ast.NilExpression()), {varReg}, {}, false); end self:freeRegister(varReg, true); end diff --git a/src/prometheus/cff/compiler.lua b/src/prometheus/cff/compiler.lua index 240cd444..e8aca3c7 100644 --- a/src/prometheus/cff/compiler.lua +++ b/src/prometheus/cff/compiler.lua @@ -6,7 +6,6 @@ local Ast = require("prometheus.ast"); local analyzeBindings = require("prometheus.bindinganalysis"); -local Scope = require("prometheus.scope"); local util = require("prometheus.util"); local lookupify = util.lookupify; @@ -100,69 +99,52 @@ function Compiler:compile(ast) self.upvalsProxyLenReturn = math.random(-2^22, 2^22); - local newGlobalScope = Scope:newGlobal(); - local psc = Scope:new(newGlobalScope, nil); - - local _, getfenvVar = newGlobalScope:resolve("getfenv"); - local _, setfenvVar = newGlobalScope:resolve("setfenv"); - local _, tableVar = newGlobalScope:resolve("table"); - local _, unpackVar = newGlobalScope:resolve("unpack"); - local _, envVar = newGlobalScope:resolve("_ENV"); - local _, newproxyVar = newGlobalScope:resolve("newproxy"); - local _, setmetatableVar = newGlobalScope:resolve("setmetatable"); - local _, getmetatableVar = newGlobalScope:resolve("getmetatable"); - local _, selectVar = newGlobalScope:resolve("select"); - local _, tonumberVar = newGlobalScope:resolve("tonumber"); - local _, errorVar = newGlobalScope:resolve("error"); - - - self.scope = Scope:new(psc); - self.envVar = self.scope:addVariable(); - self.getfenvVar = self.scope:addVariable(); - self.setfenvVar = self.scope:addVariable(); - self.callFunctionVar = self.scope:addVariable(); - self.containerFuncVar = self.scope:addVariable(); - self.unpackVar = self.scope:addVariable(); - self.packVar = self.scope:addVariable(); - self.unpackPackedVar = self.scope:addVariable(); - self.newproxyVar = self.scope:addVariable(); - self.setmetatableVar = self.scope:addVariable(); - self.getmetatableVar = self.scope:addVariable(); - self.selectVar = self.scope:addVariable(); - self.tonumberVar = self.scope:addVariable(); - self.errorVar = self.scope:addVariable(); - - local argVar = self.scope:addVariable(); - - self.containerFuncScope = Scope:new(self.scope); - self.whileScope = Scope:new(self.containerFuncScope); - - self.posVar = self.containerFuncScope:addVariable(); - self.argsVar = self.containerFuncScope:addVariable(); - self.currentUpvaluesVar = self.containerFuncScope:addVariable(); - self.detectGcCollectVar = self.containerFuncScope:addVariable(); - self.currentEnvVar = self.containerFuncScope:addVariable(); - self.returnVar = self.containerFuncScope:addVariable(); - - self.upvaluesTable = self.scope:addVariable(); - self.upvaluesReferenceCountsTable = self.scope:addVariable(); - self.allocUpvalFunction = self.scope:addVariable(); - self.currentUpvalId = self.scope:addVariable(); - - self.upvaluesProxyFunctionVar = self.scope:addVariable(); - self.upvaluesGcFunctionVar = self.scope:addVariable(); - self.freeUpvalueFunc = self.scope:addVariable(); + local getfenvVar, setfenvVar = "getfenv", "setfenv"; + local tableVar, unpackVar, envVar = "table", "unpack", "_ENV"; + local newproxyVar, setmetatableVar, getmetatableVar = "newproxy", "setmetatable", "getmetatable"; + local selectVar, tonumberVar, errorVar = "select", "tonumber", "error"; + self.envVar = Ast.LocalSymbol(); + self.getfenvVar = Ast.LocalSymbol(); + self.setfenvVar = Ast.LocalSymbol(); + self.callFunctionVar = Ast.LocalSymbol(); + self.containerFuncVar = Ast.LocalSymbol(); + self.unpackVar = Ast.LocalSymbol(); + self.packVar = Ast.LocalSymbol(); + self.unpackPackedVar = Ast.LocalSymbol(); + self.newproxyVar = Ast.LocalSymbol(); + self.setmetatableVar = Ast.LocalSymbol(); + self.getmetatableVar = Ast.LocalSymbol(); + self.selectVar = Ast.LocalSymbol(); + self.tonumberVar = Ast.LocalSymbol(); + self.errorVar = Ast.LocalSymbol(); + + local argVar = Ast.LocalSymbol(); + + + self.posVar = Ast.LocalSymbol(); + self.argsVar = Ast.LocalSymbol(); + self.currentUpvaluesVar = Ast.LocalSymbol(); + self.detectGcCollectVar = Ast.LocalSymbol(); + self.currentEnvVar = Ast.LocalSymbol(); + self.returnVar = Ast.LocalSymbol(); + + self.upvaluesTable = Ast.LocalSymbol(); + self.upvaluesReferenceCountsTable = Ast.LocalSymbol(); + self.allocUpvalFunction = Ast.LocalSymbol(); + self.currentUpvalId = Ast.LocalSymbol(); + + self.upvaluesProxyFunctionVar = Ast.LocalSymbol(); + self.upvaluesGcFunctionVar = Ast.LocalSymbol(); + self.freeUpvalueFunc = Ast.LocalSymbol(); self.createClosureVars = {}; - self.createVarargClosureVar = self.scope:addVariable(); - local createClosureScope = Scope:new(self.scope); - local createClosurePosArg = createClosureScope:addVariable(); - local createClosureUpvalsArg = createClosureScope:addVariable(); - local createClosureEnvArg = createClosureScope:addVariable(); - local createClosureProxyObject = createClosureScope:addVariable(); - local createClosureFuncVar = createClosureScope:addVariable(); + self.createVarargClosureVar = Ast.LocalSymbol(); - local createClosureSubScope = Scope:new(createClosureScope); + local createClosurePosArg = Ast.LocalSymbol(); + local createClosureUpvalsArg = Ast.LocalSymbol(); + local createClosureEnvArg = Ast.LocalSymbol(); + local createClosureProxyObject = Ast.LocalSymbol(); + local createClosureFuncVar = Ast.LocalSymbol(); local upvalEntries = {}; local upvalueIds = {}; @@ -173,7 +155,7 @@ function Compiler:compile(ast) if upvalueIds[id] then return upvalueIds[id]; end - expression = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {}); + expression = Ast.FunctionCallExpression(Ast.VariableExpression(self.allocUpvalFunction), {}); else require("logger"):error("Unresolved Upvalue, this error should not occur!"); end @@ -187,146 +169,145 @@ function Compiler:compile(ast) self:compileTopNode(ast); -- VM value lists carry an explicit count: table length loses trailing nils. - local packScope = Scope:new(self.scope); - local unpackScope = Scope:new(self.scope); - local unpackValuesVar = unpackScope:addVariable(); + + + local unpackValuesVar = Ast.LocalSymbol(); -- Keep query handling shared: a per-call closure would capture the frame's -- environment and delay its collection when another Cff pass wraps it. -- Ordinary functions receive exactly their original arguments via a tail call. - local callScope = Scope:new(self.scope); - local callFuncVar, callEnvVar = callScope:addVariable(), callScope:addVariable(); - local getterScope = Scope:new(callScope); - local levelVar = getterScope:addVariable(); - local currentScope = Scope:new(getterScope); + + local callFuncVar, callEnvVar = Ast.LocalSymbol(), Ast.LocalSymbol(); + + local levelVar = Ast.LocalSymbol(); local functionNodeAssignments = { { - var = Ast.AssignmentVariable(self.scope, self.callFunctionVar), + var = Ast.AssignmentVariable(self.callFunctionVar), val = Ast.FunctionLiteralExpression({ - Ast.VariableExpression(callScope, callFuncVar), - Ast.VariableExpression(callScope, callEnvVar), + Ast.VariableExpression(callFuncVar), + Ast.VariableExpression(callEnvVar), Ast.VarargExpression(), }, Ast.Block({ Ast.IfStatement(Ast.AndExpression( - Ast.VariableExpression(self.scope, self.getfenvVar), - Ast.EqualsExpression(Ast.VariableExpression(callScope, callFuncVar), Ast.VariableExpression(self.scope, self.getfenvVar)) + Ast.VariableExpression(self.getfenvVar), + Ast.EqualsExpression(Ast.VariableExpression(callFuncVar), Ast.VariableExpression(self.getfenvVar)) ), Ast.Block({ - Ast.LocalVariableDeclaration(getterScope, {levelVar}, {Ast.VarargExpression()}), + Ast.LocalVariableDeclaration({levelVar}, {Ast.VarargExpression()}), Ast.IfStatement(Ast.OrExpression( - Ast.EqualsExpression(Ast.VariableExpression(getterScope, levelVar), Ast.NilExpression()), - Ast.EqualsExpression(Ast.VariableExpression(getterScope, levelVar), Ast.NumberExpression(1)) - ), Ast.Block({Ast.ReturnStatement{Ast.VariableExpression(callScope, callEnvVar)}}, currentScope), {}), - }, getterScope), {}), - Ast.ReturnStatement{Ast.FunctionCallExpression(Ast.VariableExpression(callScope, callFuncVar), {Ast.VarargExpression()})}, - }, callScope)), + Ast.EqualsExpression(Ast.VariableExpression(levelVar), Ast.NilExpression()), + Ast.EqualsExpression(Ast.VariableExpression(levelVar), Ast.NumberExpression(1)) + ), Ast.Block({Ast.ReturnStatement{Ast.VariableExpression(callEnvVar)}}), {}), + }), {}), + Ast.ReturnStatement{Ast.FunctionCallExpression(Ast.VariableExpression(callFuncVar), {Ast.VarargExpression()})}, + })), }, { - var = Ast.AssignmentVariable(self.scope, self.packVar), + var = Ast.AssignmentVariable(self.packVar), val = Ast.FunctionLiteralExpression({Ast.VarargExpression()}, Ast.Block({ Ast.ReturnStatement{Ast.TableConstructorExpression({ Ast.KeyedTableEntry(Ast.StringExpression("n"), Ast.FunctionCallExpression( - Ast.VariableExpression(self.scope, self.selectVar), + Ast.VariableExpression(self.selectVar), {Ast.StringExpression("#"), Ast.VarargExpression()})), Ast.TableEntry(Ast.VarargExpression()), })}, - }, packScope)), + })), }, { - var = Ast.AssignmentVariable(self.scope, self.unpackPackedVar), - val = Ast.FunctionLiteralExpression({Ast.VariableExpression(unpackScope, unpackValuesVar)}, Ast.Block({ - Ast.ReturnStatement{Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.unpackVar), { - Ast.VariableExpression(unpackScope, unpackValuesVar), + var = Ast.AssignmentVariable(self.unpackPackedVar), + val = Ast.FunctionLiteralExpression({Ast.VariableExpression(unpackValuesVar)}, Ast.Block({ + Ast.ReturnStatement{Ast.FunctionCallExpression(Ast.VariableExpression(self.unpackVar), { + Ast.VariableExpression(unpackValuesVar), Ast.NumberExpression(1), - Ast.IndexExpression(Ast.VariableExpression(unpackScope, unpackValuesVar), Ast.StringExpression("n")), + Ast.IndexExpression(Ast.VariableExpression(unpackValuesVar), Ast.StringExpression("n")), })}, - }, unpackScope)), + })), }, { - var = Ast.AssignmentVariable(self.scope, self.containerFuncVar), + var = Ast.AssignmentVariable(self.containerFuncVar), val = Ast.FunctionLiteralExpression({ - Ast.VariableExpression(self.containerFuncScope, self.posVar), - Ast.VariableExpression(self.containerFuncScope, self.argsVar), - Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), - Ast.VariableExpression(self.containerFuncScope, self.detectGcCollectVar), - Ast.VariableExpression(self.containerFuncScope, self.currentEnvVar) + Ast.VariableExpression(self.posVar), + Ast.VariableExpression(self.argsVar), + Ast.VariableExpression(self.currentUpvaluesVar), + Ast.VariableExpression(self.detectGcCollectVar), + Ast.VariableExpression(self.currentEnvVar) }, self:emitContainerFuncBody()); }, { - var = Ast.AssignmentVariable(self.scope, self.createVarargClosureVar), + var = Ast.AssignmentVariable(self.createVarargClosureVar), val = Ast.FunctionLiteralExpression({ - Ast.VariableExpression(createClosureScope, createClosurePosArg), - Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), - Ast.VariableExpression(createClosureScope, createClosureEnvArg), + Ast.VariableExpression(createClosurePosArg), + Ast.VariableExpression(createClosureUpvalsArg), + Ast.VariableExpression(createClosureEnvArg), }, Ast.Block({ - Ast.LocalVariableDeclaration(createClosureScope, { + Ast.LocalVariableDeclaration({ createClosureProxyObject }, { - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.upvaluesProxyFunctionVar), { - Ast.VariableExpression(createClosureScope, createClosureUpvalsArg) + Ast.FunctionCallExpression(Ast.VariableExpression(self.upvaluesProxyFunctionVar), { + Ast.VariableExpression(createClosureUpvalsArg) }) }), - Ast.LocalVariableDeclaration(createClosureScope, {createClosureFuncVar},{ + Ast.LocalVariableDeclaration({createClosureFuncVar},{ Ast.FunctionLiteralExpression({ Ast.VarargExpression(); }, Ast.Block({ Ast.ReturnStatement{ - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.containerFuncVar), { - Ast.VariableExpression(createClosureScope, createClosurePosArg), - self:pack(createClosureSubScope, {Ast.VarargExpression()}), - Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), - Ast.VariableExpression(createClosureScope, createClosureProxyObject), - self:closureEnvironment(createClosureSubScope, createClosureScope, createClosureEnvArg) + Ast.FunctionCallExpression(Ast.VariableExpression(self.containerFuncVar), { + Ast.VariableExpression(createClosurePosArg), + self:pack({Ast.VarargExpression()}), + Ast.VariableExpression(createClosureUpvalsArg), + Ast.VariableExpression(createClosureProxyObject), + self:closureEnvironment(createClosureEnvArg) }) } - }, createClosureSubScope) + }) ); }); - self:inheritClosureEnvironment(createClosureScope, createClosureFuncVar, createClosureEnvArg); - Ast.ReturnStatement{Ast.VariableExpression(createClosureScope, createClosureFuncVar)}; - }, createClosureScope) + self:inheritClosureEnvironment(createClosureFuncVar, createClosureEnvArg); + Ast.ReturnStatement{Ast.VariableExpression(createClosureFuncVar)}; + }) ); }, { - var = Ast.AssignmentVariable(self.scope, self.upvaluesTable), + var = Ast.AssignmentVariable(self.upvaluesTable), val = Ast.TableConstructorExpression({}), }, { - var = Ast.AssignmentVariable(self.scope, self.upvaluesReferenceCountsTable), + var = Ast.AssignmentVariable(self.upvaluesReferenceCountsTable), val = Ast.TableConstructorExpression({}), }, { - var = Ast.AssignmentVariable(self.scope, self.allocUpvalFunction), + var = Ast.AssignmentVariable(self.allocUpvalFunction), val = self:createAllocUpvalFunction(), }, { - var = Ast.AssignmentVariable(self.scope, self.currentUpvalId), + var = Ast.AssignmentVariable(self.currentUpvalId), val = Ast.NumberExpression(0), }, { - var = Ast.AssignmentVariable(self.scope, self.upvaluesProxyFunctionVar), + var = Ast.AssignmentVariable(self.upvaluesProxyFunctionVar), val = self:createUpvaluesProxyFunc(), }, { - var = Ast.AssignmentVariable(self.scope, self.upvaluesGcFunctionVar), + var = Ast.AssignmentVariable(self.upvaluesGcFunctionVar), val = self:createUpvaluesGcFunc(), }, { - var = Ast.AssignmentVariable(self.scope, self.freeUpvalueFunc), + var = Ast.AssignmentVariable(self.freeUpvalueFunc), val = self:createFreeUpvalueFunc(), }, } local tbl = { - Ast.VariableExpression(self.scope, self.callFunctionVar), - Ast.VariableExpression(self.scope, self.packVar), - Ast.VariableExpression(self.scope, self.unpackPackedVar), - Ast.VariableExpression(self.scope, self.containerFuncVar), - Ast.VariableExpression(self.scope, self.createVarargClosureVar), - Ast.VariableExpression(self.scope, self.upvaluesTable), - Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), - Ast.VariableExpression(self.scope, self.allocUpvalFunction), - Ast.VariableExpression(self.scope, self.currentUpvalId), - Ast.VariableExpression(self.scope, self.upvaluesProxyFunctionVar), - Ast.VariableExpression(self.scope, self.upvaluesGcFunctionVar), - Ast.VariableExpression(self.scope, self.freeUpvalueFunc), + Ast.VariableExpression(self.callFunctionVar), + Ast.VariableExpression(self.packVar), + Ast.VariableExpression(self.unpackPackedVar), + Ast.VariableExpression(self.containerFuncVar), + Ast.VariableExpression(self.createVarargClosureVar), + Ast.VariableExpression(self.upvaluesTable), + Ast.VariableExpression(self.upvaluesReferenceCountsTable), + Ast.VariableExpression(self.allocUpvalFunction), + Ast.VariableExpression(self.currentUpvalId), + Ast.VariableExpression(self.upvaluesProxyFunctionVar), + Ast.VariableExpression(self.upvaluesGcFunctionVar), + Ast.VariableExpression(self.freeUpvalueFunc), }; for i, entry in pairs(self.createClosureVars) do table.insert(functionNodeAssignments, entry); - table.insert(tbl, Ast.VariableExpression(entry.var.scope, entry.var.id)); + table.insert(tbl, Ast.VariableExpression(entry.symbol)); end util.shuffle(functionNodeAssignments); @@ -341,35 +322,35 @@ function Compiler:compile(ast) local ids = util.shuffle({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}); local items = { - Ast.VariableExpression(self.scope, self.envVar), - Ast.VariableExpression(self.scope, self.unpackVar), - Ast.VariableExpression(self.scope, self.newproxyVar), - Ast.VariableExpression(self.scope, self.setmetatableVar), - Ast.VariableExpression(self.scope, self.getmetatableVar), - Ast.VariableExpression(self.scope, self.selectVar), - Ast.VariableExpression(self.scope, argVar), - Ast.VariableExpression(self.scope, self.tonumberVar), - Ast.VariableExpression(self.scope, self.errorVar), - Ast.VariableExpression(self.scope, self.getfenvVar), - Ast.VariableExpression(self.scope, self.setfenvVar), + Ast.VariableExpression(self.envVar), + Ast.VariableExpression(self.unpackVar), + Ast.VariableExpression(self.newproxyVar), + Ast.VariableExpression(self.setmetatableVar), + Ast.VariableExpression(self.getmetatableVar), + Ast.VariableExpression(self.selectVar), + Ast.VariableExpression(argVar), + Ast.VariableExpression(self.tonumberVar), + Ast.VariableExpression(self.errorVar), + Ast.VariableExpression(self.getfenvVar), + Ast.VariableExpression(self.setfenvVar), } local astItems = { - Ast.OrExpression(Ast.AndExpression(Ast.VariableExpression(newGlobalScope, getfenvVar), Ast.FunctionCallExpression(Ast.VariableExpression(newGlobalScope, getfenvVar), {})), Ast.VariableExpression(newGlobalScope, envVar)); - Ast.OrExpression(Ast.VariableExpression(newGlobalScope, unpackVar), Ast.IndexExpression(Ast.VariableExpression(newGlobalScope, tableVar), Ast.StringExpression("unpack"))); - Ast.VariableExpression(newGlobalScope, newproxyVar); - Ast.VariableExpression(newGlobalScope, setmetatableVar); - Ast.VariableExpression(newGlobalScope, getmetatableVar); - Ast.VariableExpression(newGlobalScope, selectVar); + Ast.OrExpression(Ast.AndExpression(Ast.GlobalVariableExpression(getfenvVar), Ast.FunctionCallExpression(Ast.GlobalVariableExpression(getfenvVar), {})), Ast.GlobalVariableExpression(envVar)); + Ast.OrExpression(Ast.GlobalVariableExpression(unpackVar), Ast.IndexExpression(Ast.GlobalVariableExpression(tableVar), Ast.StringExpression("unpack"))); + Ast.GlobalVariableExpression(newproxyVar); + Ast.GlobalVariableExpression(setmetatableVar); + Ast.GlobalVariableExpression(getmetatableVar); + Ast.GlobalVariableExpression(selectVar); Ast.TableConstructorExpression({ Ast.KeyedTableEntry(Ast.StringExpression("n"), Ast.FunctionCallExpression( - Ast.VariableExpression(newGlobalScope, selectVar), {Ast.StringExpression("#"), Ast.VarargExpression()})), + Ast.GlobalVariableExpression(selectVar), {Ast.StringExpression("#"), Ast.VarargExpression()})), Ast.TableEntry(Ast.VarargExpression()); }); - Ast.VariableExpression(newGlobalScope, tonumberVar); - Ast.VariableExpression(newGlobalScope, errorVar); - Ast.VariableExpression(newGlobalScope, getfenvVar); - Ast.VariableExpression(newGlobalScope, setfenvVar); + Ast.GlobalVariableExpression(tonumberVar); + Ast.GlobalVariableExpression(errorVar); + Ast.GlobalVariableExpression(getfenvVar); + Ast.GlobalVariableExpression(setfenvVar); } local functionNode = Ast.FunctionLiteralExpression({ @@ -380,13 +361,13 @@ function Compiler:compile(ast) }, Ast.Block({ Ast.AssignmentStatement(assignmentStatLhs, assignmentStatRhs); Ast.ReturnStatement{ - Ast.FunctionCallExpression(Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.createVarargClosureVar), { + Ast.FunctionCallExpression(Ast.FunctionCallExpression(Ast.VariableExpression(self.createVarargClosureVar), { Ast.NumberExpression(self.startBlockId); Ast.TableConstructorExpression(upvalEntries); - Ast.VariableExpression(self.scope, self.envVar); - }), {Ast.FunctionCallExpression(self:unpack(self.scope), {Ast.VariableExpression(self.scope, argVar)})}); + Ast.VariableExpression(self.envVar); + }), {Ast.FunctionCallExpression(self:unpack(), {Ast.VariableExpression(argVar)})}); } - }, self.scope)); + })); return Ast.TopNode(Ast.Block({ Ast.ReturnStatement{Ast.FunctionCallExpression(functionNode, { @@ -394,89 +375,89 @@ function Compiler:compile(ast) astItems[ids[5]], astItems[ids[6]], astItems[ids[7]], astItems[ids[8]], astItems[ids[9]], astItems[ids[10]], astItems[ids[11]], })}; - }, psc), newGlobalScope); + })); end -- Each wrapper reads its own environment at entry, before tail-calling the VM. -- The VM keeps this table in its call frame so recursion and yields are isolated. -function Compiler:closureEnvironment(scope, inheritedScope, inheritedVar) +function Compiler:closureEnvironment(inheritedVar) return Ast.OrExpression(Ast.AndExpression( - Ast.VariableExpression(self.scope, self.getfenvVar), - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.getfenvVar), {Ast.NumberExpression(1)}) - ), Ast.VariableExpression(inheritedScope, inheritedVar)); + Ast.VariableExpression(self.getfenvVar), + Ast.FunctionCallExpression(Ast.VariableExpression(self.getfenvVar), {Ast.NumberExpression(1)}) + ), Ast.VariableExpression(inheritedVar)); end -function Compiler:inheritClosureEnvironment(scope, funcVar, envVar) - local bodyScope = Scope:new(scope); - return Ast.IfStatement(Ast.VariableExpression(self.scope, self.setfenvVar), Ast.Block({ - Ast.FunctionCallStatement(Ast.VariableExpression(self.scope, self.setfenvVar), { - Ast.VariableExpression(scope, funcVar), - Ast.VariableExpression(scope, envVar), +function Compiler:inheritClosureEnvironment(funcVar, envVar) + + return Ast.IfStatement(Ast.VariableExpression(self.setfenvVar), Ast.Block({ + Ast.FunctionCallStatement(Ast.VariableExpression(self.setfenvVar), { + Ast.VariableExpression(funcVar), + Ast.VariableExpression(envVar), }), - }, bodyScope), {}); + }), {}); end function Compiler:getCreateClosureVar(argCount) if not self.createClosureVars[argCount] then - local var = Ast.AssignmentVariable(self.scope, self.scope:addVariable()); - local createClosureScope = Scope:new(self.scope); - local createClosureSubScope = Scope:new(createClosureScope); + local var = Ast.LocalSymbol(); - local createClosurePosArg = createClosureScope:addVariable(); - local createClosureUpvalsArg = createClosureScope:addVariable(); - local createClosureEnvArg = createClosureScope:addVariable(); - local createClosureProxyObject = createClosureScope:addVariable(); - local createClosureFuncVar = createClosureScope:addVariable(); + + local createClosurePosArg = Ast.LocalSymbol(); + local createClosureUpvalsArg = Ast.LocalSymbol(); + local createClosureEnvArg = Ast.LocalSymbol(); + local createClosureProxyObject = Ast.LocalSymbol(); + local createClosureFuncVar = Ast.LocalSymbol(); local argsTb, argsTb2 = {}, {}; for i = 1, argCount do - local arg = createClosureSubScope:addVariable() - argsTb[i] = Ast.VariableExpression(createClosureSubScope, arg); - argsTb2[i] = Ast.TableEntry(Ast.VariableExpression(createClosureSubScope, arg)); + local arg = Ast.LocalSymbol() + argsTb[i] = Ast.VariableExpression(arg); + argsTb2[i] = Ast.TableEntry(Ast.VariableExpression(arg)); end table.insert(argsTb2, Ast.KeyedTableEntry(Ast.StringExpression("n"), Ast.NumberExpression(argCount))); local val = Ast.FunctionLiteralExpression({ - Ast.VariableExpression(createClosureScope, createClosurePosArg), - Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), - Ast.VariableExpression(createClosureScope, createClosureEnvArg), + Ast.VariableExpression(createClosurePosArg), + Ast.VariableExpression(createClosureUpvalsArg), + Ast.VariableExpression(createClosureEnvArg), }, Ast.Block({ - Ast.LocalVariableDeclaration(createClosureScope, { + Ast.LocalVariableDeclaration({ createClosureProxyObject }, { - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.upvaluesProxyFunctionVar), { - Ast.VariableExpression(createClosureScope, createClosureUpvalsArg) + Ast.FunctionCallExpression(Ast.VariableExpression(self.upvaluesProxyFunctionVar), { + Ast.VariableExpression(createClosureUpvalsArg) }) }), - Ast.LocalVariableDeclaration(createClosureScope, {createClosureFuncVar},{ + Ast.LocalVariableDeclaration({createClosureFuncVar},{ Ast.FunctionLiteralExpression(argsTb, Ast.Block({ Ast.ReturnStatement{ - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.containerFuncVar), { - Ast.VariableExpression(createClosureScope, createClosurePosArg), + Ast.FunctionCallExpression(Ast.VariableExpression(self.containerFuncVar), { + Ast.VariableExpression(createClosurePosArg), Ast.TableConstructorExpression(argsTb2), - Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), - Ast.VariableExpression(createClosureScope, createClosureProxyObject), - self:closureEnvironment(createClosureSubScope, createClosureScope, createClosureEnvArg) + Ast.VariableExpression(createClosureUpvalsArg), + Ast.VariableExpression(createClosureProxyObject), + self:closureEnvironment(createClosureEnvArg) }) } - }, createClosureSubScope) + }) ); }); - self:inheritClosureEnvironment(createClosureScope, createClosureFuncVar, createClosureEnvArg); - Ast.ReturnStatement{Ast.VariableExpression(createClosureScope, createClosureFuncVar)} - }, createClosureScope) + self:inheritClosureEnvironment(createClosureFuncVar, createClosureEnvArg); + Ast.ReturnStatement{Ast.VariableExpression(createClosureFuncVar)} + }) ); self.createClosureVars[argCount] = { - var = var, + symbol = var, + var = Ast.AssignmentVariable(var), val = val, } end - local var = self.createClosureVars[argCount].var; - return var.scope, var.id; + local var = self.createClosureVars[argCount].symbol; + return var; end return Compiler; diff --git a/src/prometheus/cff/emit.lua b/src/prometheus/cff/emit.lua index d5d73459..55f1f316 100644 --- a/src/prometheus/cff/emit.lua +++ b/src/prometheus/cff/emit.lua @@ -5,7 +5,6 @@ -- This Script contains the container function body emission for the compiler. local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); local util = require("prometheus.util"); local constants = require("prometheus.cff.constants"); local AstKind = Ast.AstKind; @@ -227,7 +226,7 @@ return function(Compiler) table.insert(blockstats, stat.statement); end - local block = { id = id, index = i, block = Ast.Block(blockstats, block.scope) } + local block = { id = id, index = i, block = Ast.Block(blockstats) } -- Block IDs can overlap list indices; keep this table an ordered list. table.insert(blocks, block); end @@ -236,10 +235,10 @@ return function(Compiler) -- Build a strict threshold condition between adjacent block IDs. -- Using a midpoint avoids exact-id comparisons while preserving dispatch. - local function buildBlockThresholdCondition(scope, leftId, rightId, useAndOr) + local function buildBlockThresholdCondition(leftId, rightId, useAndOr) -- Keep the midpoint strictly between the IDs, even when consecutive. local bound = (leftId + rightId) / 2; - local posExpr = self:pos(scope); + local posExpr = self:pos(); local boundExpr = Ast.NumberExpression(bound); if useAndOr then @@ -256,11 +255,10 @@ return function(Compiler) end -- Build an elseif chain for a range of blocks - local function buildElseifChain(tb, l, r, pScope) + local function buildElseifChain(tb, l, r) -- Handle invalid range by returning an empty block if r < l then - local emptyScope = Scope:new(pScope); - return Ast.Block({}, emptyScope); + return Ast.Block({}); end local len = r - l + 1; @@ -272,16 +270,15 @@ return function(Compiler) -- For small ranges, use elseif chain if len <= 4 then - local ifScope = Scope:new(pScope); local elseifs = {}; -- First block uses the first midpoint threshold - local firstCondition = buildBlockThresholdCondition(ifScope, tb[l].id, tb[l + 1].id, false); + local firstCondition = buildBlockThresholdCondition(tb[l].id, tb[l + 1].id, false); local firstBlock = tb[l].block; -- Middle blocks use their upper midpoint threshold for i = l + 1, r - 1 do - local condition = buildBlockThresholdCondition(ifScope, tb[i].id, tb[i + 1].id, false); + local condition = buildBlockThresholdCondition(tb[i].id, tb[i + 1].id, false); table.insert(elseifs, { condition = condition, body = tb[i].block @@ -293,7 +290,7 @@ return function(Compiler) return Ast.Block({ Ast.IfStatement(firstCondition, firstBlock, elseifs, elseBlock); - }, ifScope); + }); end -- For larger ranges, use binary split with and/or chaining @@ -303,10 +300,8 @@ return function(Compiler) -- IDs are 24-bit integers, so half-integer midpoints are exact. -- Rounding would make consecutive IDs share a dispatch branch. local bound = (leftMaxId + rightMinId) / 2; - local ifScope = Scope:new(pScope); - - local lBlock = buildElseifChain(tb, l, mid - 1, ifScope); - local rBlock = buildElseifChain(tb, mid, r, ifScope); + local lBlock = buildElseifChain(tb, l, mid - 1); + local rBlock = buildElseifChain(tb, mid, r); -- Randomly choose between different condition styles local condStyle = math.random(1, 3); @@ -315,27 +310,24 @@ return function(Compiler) if condStyle == 1 then -- pos < bound - condition = Ast.LessThanExpression(self:pos(ifScope), Ast.NumberExpression(bound)); + condition = Ast.LessThanExpression(self:pos(), Ast.NumberExpression(bound)); trueBlock, falseBlock = lBlock, rBlock; elseif condStyle == 2 then -- bound > pos - condition = Ast.GreaterThanExpression(Ast.NumberExpression(bound), self:pos(ifScope)); + condition = Ast.GreaterThanExpression(Ast.NumberExpression(bound), self:pos()); trueBlock, falseBlock = lBlock, rBlock; else -- Equivalent split using strict > with branches reversed. - condition = Ast.GreaterThanExpression(self:pos(ifScope), Ast.NumberExpression(bound)); + condition = Ast.GreaterThanExpression(self:pos(), Ast.NumberExpression(bound)); trueBlock, falseBlock = rBlock, lBlock; end return Ast.Block({ Ast.IfStatement(condition, trueBlock, {}, falseBlock); - }, ifScope); + }); end - local whileBody = buildElseifChain(blocks, 1, #blocks, self.containerFuncScope); - if self.whileScope then - -- Ensure whileScope is properly connected - end + local whileBody = buildElseifChain(blocks, 1, #blocks); @@ -352,26 +344,26 @@ return function(Compiler) local stats = {} if self.maxUsedRegister >= MAX_REGS then - table.insert(stats, Ast.LocalVariableDeclaration(self.containerFuncScope, {self.registerVars[MAX_REGS]}, {Ast.TableConstructorExpression({})})); + table.insert(stats, Ast.LocalVariableDeclaration({self.registerVars[MAX_REGS]}, {Ast.TableConstructorExpression({})})); end - table.insert(stats, Ast.LocalVariableDeclaration(self.containerFuncScope, util.shuffle(declarations), {})); + table.insert(stats, Ast.LocalVariableDeclaration(util.shuffle(declarations), {})); - table.insert(stats, Ast.WhileStatement(whileBody, Ast.VariableExpression(self.containerFuncScope, self.posVar))); + table.insert(stats, Ast.WhileStatement(whileBody, Ast.VariableExpression(self.posVar))); table.insert(stats, Ast.AssignmentStatement({ - Ast.AssignmentVariable(self.containerFuncScope, self.posVar) + Ast.AssignmentVariable(self.posVar) }, { - Ast.LenExpression(Ast.VariableExpression(self.containerFuncScope, self.detectGcCollectVar)) + Ast.LenExpression(Ast.VariableExpression(self.detectGcCollectVar)) })); table.insert(stats, Ast.ReturnStatement{ - Ast.FunctionCallExpression(self:unpack(self.containerFuncScope), { - Ast.VariableExpression(self.containerFuncScope, self.returnVar) + Ast.FunctionCallExpression(self:unpack(), { + Ast.VariableExpression(self.returnVar) }); }); - return Ast.Block(stats, self.containerFuncScope); + return Ast.Block(stats); end end diff --git a/src/prometheus/cff/expressions/and.lua b/src/prometheus/cff/expressions/and.lua index 89b30260..21333e61 100644 --- a/src/prometheus/cff/expressions/and.lua +++ b/src/prometheus/cff/expressions/and.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local posState = self.registers[self.POS_REGISTER]; self.registers[self.POS_REGISTER] = self.VAR_REGISTER; @@ -15,7 +14,7 @@ return function(self, expression, funcDepth, numReturns) for i = 1, numReturns do regs[i] = self:allocRegister(); if i ~= 1 then - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end @@ -24,13 +23,13 @@ return function(self, expression, funcDepth, numReturns) if posState then tmpReg = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {tmpReg}, {self.POS_REGISTER}), {tmpReg}, {self.POS_REGISTER}, false); + self:addStatement(self:copyRegisters({tmpReg}, {self.POS_REGISTER}), {tmpReg}, {self.POS_REGISTER}, false); end local lhsReg = self:compileExpression(expression.lhs, funcDepth, 1)[1]; if expression.rhs.isConstant then local rhsReg = self:compileExpression(expression.rhs, funcDepth, 1)[1]; - self:addStatement(self:setRegister(scope, resReg, Ast.AndExpression(self:register(scope, lhsReg), self:register(scope, rhsReg))), {resReg}, {lhsReg, rhsReg}, false); + self:addStatement(self:setRegister(resReg, Ast.AndExpression(self:register(lhsReg), self:register(rhsReg))), {resReg}, {lhsReg, rhsReg}, false); if tmpReg then self:freeRegister(tmpReg, false); end @@ -40,26 +39,23 @@ return function(self, expression, funcDepth, numReturns) end local block1, block2 = self:createBlock(), self:createBlock(); - self:addStatement(self:copyRegisters(scope, {resReg}, {lhsReg}), {resReg}, {lhsReg}, false); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(scope, lhsReg), Ast.NumberExpression(block1.id)), Ast.NumberExpression(block2.id))), {self.POS_REGISTER}, {lhsReg}, false); + self:addStatement(self:copyRegisters({resReg}, {lhsReg}), {resReg}, {lhsReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(lhsReg), Ast.NumberExpression(block1.id)), Ast.NumberExpression(block2.id))), {self.POS_REGISTER}, {lhsReg}, false); self:freeRegister(lhsReg, false); do self:setActiveBlock(block1); - scope = block1.scope; local rhsReg = self:compileExpression(expression.rhs, funcDepth, 1)[1]; - self:addStatement(self:copyRegisters(scope, {resReg}, {rhsReg}), {resReg}, {rhsReg}, false); + self:addStatement(self:copyRegisters({resReg}, {rhsReg}), {resReg}, {rhsReg}, false); self:freeRegister(rhsReg, false); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(block2.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(block2.id)), {self.POS_REGISTER}, {}, false); end self.registers[self.POS_REGISTER] = posState; self:setActiveBlock(block2); - scope = block2.scope; - if tmpReg then - self:addStatement(self:copyRegisters(scope, {self.POS_REGISTER}, {tmpReg}), {self.POS_REGISTER}, {tmpReg}, false); + self:addStatement(self:copyRegisters({self.POS_REGISTER}, {tmpReg}), {self.POS_REGISTER}, {tmpReg}, false); self:freeRegister(tmpReg, false); end diff --git a/src/prometheus/cff/expressions/binary.lua b/src/prometheus/cff/expressions/binary.lua index 380d689f..12b1ddcb 100644 --- a/src/prometheus/cff/expressions/binary.lua +++ b/src/prometheus/cff/expressions/binary.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); @@ -15,12 +14,12 @@ return function(self, expression, funcDepth, numReturns) local lhsReg = self:compileExpression(expression.lhs, funcDepth, 1)[1]; local rhsReg = self:compileExpression(expression.rhs, funcDepth, 1)[1]; - local binaryExpr = Ast[expression.kind](self:register(scope, lhsReg), self:register(scope, rhsReg)); - self:addStatement(self:setRegister(scope, regs[i], binaryExpr), {regs[i]}, {lhsReg, rhsReg}, true); + local binaryExpr = Ast[expression.kind](self:register(lhsReg), self:register(rhsReg)); + self:addStatement(self:setRegister(regs[i], binaryExpr), {regs[i]}, {lhsReg, rhsReg}, true); self:freeRegister(rhsReg, false); self:freeRegister(lhsReg, false); else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/boolean.lua b/src/prometheus/cff/expressions/boolean.lua index 1b1ddace..d9720abc 100644 --- a/src/prometheus/cff/expressions/boolean.lua +++ b/src/prometheus/cff/expressions/boolean.lua @@ -45,14 +45,13 @@ local function createRandomASTCFlowExpression(resultBool) end return function(self, expression, _, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); if i == 1 then - self:addStatement(self:setRegister(scope, regs[i], createRandomASTCFlowExpression(expression.value)), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], createRandomASTCFlowExpression(expression.value)), {regs[i]}, {}, false); else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/function_call.lua b/src/prometheus/cff/expressions/function_call.lua index 30b9e722..f2965f55 100644 --- a/src/prometheus/cff/expressions/function_call.lua +++ b/src/prometheus/cff/expressions/function_call.lua @@ -8,7 +8,6 @@ local Ast = require("prometheus.ast"); local AstKind = Ast.AstKind; return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local baseReg = self:compileExpression(expression.base, funcDepth, 1)[1]; local retRegs = {}; @@ -27,32 +26,32 @@ return function(self, expression, funcDepth, numReturns) if i == #expression.args and (expr.kind == AstKind.FunctionCallExpression or expr.kind == AstKind.PassSelfFunctionCallExpression or expr.kind == AstKind.VarargExpression) then local reg = self:compileExpression(expr, funcDepth, self.RETURN_ALL)[1]; table.insert(args, Ast.FunctionCallExpression( - self:unpack(scope), - {self:register(scope, reg)})); + self:unpack(), + {self:register(reg)})); table.insert(regs, reg); else local reg = self:compileExpression(expr, funcDepth, 1)[1]; - table.insert(args, self:register(scope, reg)); + table.insert(args, self:register(reg)); table.insert(regs, reg); end end if returnAll then - self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {self:functionCall(scope, baseReg, args)})), {retRegs[1]}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(retRegs[1], self:pack({self:functionCall(baseReg, args)})), {retRegs[1]}, {baseReg, unpack(regs)}, true); else if numReturns > 1 then local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(self:functionCall(scope, baseReg, args))}), {tmpReg}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(self:functionCall(baseReg, args))}), {tmpReg}, {baseReg, unpack(regs)}, true); for i, reg in ipairs(retRegs) do - self:addStatement(self:setRegister(scope, reg, Ast.IndexExpression(self:register(scope, tmpReg), Ast.NumberExpression(i))), {reg}, {tmpReg}, false); + self:addStatement(self:setRegister(reg, Ast.IndexExpression(self:register(tmpReg), Ast.NumberExpression(i))), {reg}, {tmpReg}, false); end self:freeRegister(tmpReg, false); else - self:addStatement(self:setRegister(scope, retRegs[1], self:functionCall(scope, baseReg, args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(retRegs[1], self:functionCall(baseReg, args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); end end diff --git a/src/prometheus/cff/expressions/function_literal.lua b/src/prometheus/cff/expressions/function_literal.lua index 0a4e06eb..3d9fcfb9 100644 --- a/src/prometheus/cff/expressions/function_literal.lua +++ b/src/prometheus/cff/expressions/function_literal.lua @@ -7,14 +7,13 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do if i == 1 then regs[i] = self:compileFunction(expression, funcDepth); else regs[i] = self:allocRegister(); - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/if_else.lua b/src/prometheus/cff/expressions/if_else.lua index 317b0c68..8398fd88 100644 --- a/src/prometheus/cff/expressions/if_else.lua +++ b/src/prometheus/cff/expressions/if_else.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local posState = self.registers[self.POS_REGISTER]; self.registers[self.POS_REGISTER] = self.VAR_REGISTER; @@ -15,7 +14,7 @@ return function(self, expression, funcDepth, numReturns) for i = 1, numReturns do regs[i] = self:allocRegister(); if i ~= 1 then - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end @@ -24,7 +23,7 @@ return function(self, expression, funcDepth, numReturns) if posState then tmpReg = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {tmpReg}, {self.POS_REGISTER}), {tmpReg}, {self.POS_REGISTER}, false); + self:addStatement(self:copyRegisters({tmpReg}, {self.POS_REGISTER}), {tmpReg}, {self.POS_REGISTER}, false); end local conditionReg = self:compileExpression(expression.condition, funcDepth, 1)[1]; @@ -33,46 +32,38 @@ return function(self, expression, funcDepth, numReturns) local nextBlock = self:createBlock(); local innerBlock = self:createBlock(); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(scope, conditionReg), Ast.NumberExpression(innerBlock.id)), Ast.NumberExpression(nextBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(conditionReg), Ast.NumberExpression(innerBlock.id)), Ast.NumberExpression(nextBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); self:freeRegister(conditionReg, false); self:setActiveBlock(innerBlock); - scope = innerBlock.scope; - local trueReg = self:compileExpression(expression.true_value, funcDepth, 1)[1]; - self:addStatement(self:copyRegisters(scope, {resReg}, {trueReg}), {resReg}, {trueReg}, false); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:copyRegisters({resReg}, {trueReg}), {resReg}, {trueReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); for _, elif in ipairs(expression.elseifs) do self:setActiveBlock(nextBlock); conditionReg = self:compileExpression(elif.condition, funcDepth, 1)[1]; local elifBlock = self:createBlock(); nextBlock = self:createBlock(); - local elifScope = self.activeBlock.scope; - - self:addStatement(self:setRegister(elifScope, self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(elifScope, conditionReg), Ast.NumberExpression(elifBlock.id)), Ast.NumberExpression(nextBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(conditionReg), Ast.NumberExpression(elifBlock.id)), Ast.NumberExpression(nextBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); self:freeRegister(conditionReg, false); self:setActiveBlock(elifBlock); - elifScope = elifBlock.scope; local valueReg = self:compileExpression(elif.value, funcDepth, 1)[1]; - self:addStatement(self:copyRegisters(elifScope, {resReg}, {valueReg}), {resReg}, {valueReg}, false); - self:addStatement(self:setRegister(elifScope, self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:copyRegisters({resReg}, {valueReg}), {resReg}, {valueReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); end self:setActiveBlock(nextBlock); - scope = self.activeBlock.scope; local falseReg = self:compileExpression(expression.false_value, funcDepth, 1)[1]; - self:addStatement(self:copyRegisters(scope, {resReg}, {falseReg}), {resReg}, {falseReg}, false); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:copyRegisters({resReg}, {falseReg}), {resReg}, {falseReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); self.registers[self.POS_REGISTER] = posState; self:setActiveBlock(finalBlock); - scope = finalBlock.scope; - if tmpReg then - self:addStatement(self:copyRegisters(scope, {self.POS_REGISTER}, {tmpReg}), {self.POS_REGISTER}, {tmpReg}, false); + self:addStatement(self:copyRegisters({self.POS_REGISTER}, {tmpReg}), {self.POS_REGISTER}, {tmpReg}, false); self:freeRegister(tmpReg, false); end diff --git a/src/prometheus/cff/expressions/index.lua b/src/prometheus/cff/expressions/index.lua index c3574c7d..4c88e47a 100644 --- a/src/prometheus/cff/expressions/index.lua +++ b/src/prometheus/cff/expressions/index.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); @@ -17,12 +16,12 @@ return function(self, expression, funcDepth, numReturns) - self:addStatement(self:setRegister(scope, regs[i], Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, indexReg))), {regs[i]}, {baseReg, indexReg}, true); + self:addStatement(self:setRegister(regs[i], Ast.IndexExpression(self:register(baseReg), self:register(indexReg))), {regs[i]}, {baseReg, indexReg}, true); self:freeRegister(baseReg, false); self:freeRegister(indexReg, false); else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/len.lua b/src/prometheus/cff/expressions/len.lua index b0cb1c7b..bf0a6c0b 100644 --- a/src/prometheus/cff/expressions/len.lua +++ b/src/prometheus/cff/expressions/len.lua @@ -7,17 +7,16 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); if i == 1 then local rhsReg = self:compileExpression(expression.rhs, funcDepth, 1)[1]; - self:addStatement(self:setRegister(scope, regs[i], Ast.LenExpression(self:register(scope, rhsReg))), {regs[i]}, {rhsReg}, true); + self:addStatement(self:setRegister(regs[i], Ast.LenExpression(self:register(rhsReg))), {regs[i]}, {rhsReg}, true); self:freeRegister(rhsReg, false); else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/negate.lua b/src/prometheus/cff/expressions/negate.lua index b5fdc98a..4755d136 100644 --- a/src/prometheus/cff/expressions/negate.lua +++ b/src/prometheus/cff/expressions/negate.lua @@ -7,17 +7,16 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); if i == 1 then local rhsReg = self:compileExpression(expression.rhs, funcDepth, 1)[1]; - self:addStatement(self:setRegister(scope, regs[i], Ast.NegateExpression(self:register(scope, rhsReg))), {regs[i]}, {rhsReg}, true); + self:addStatement(self:setRegister(regs[i], Ast.NegateExpression(self:register(rhsReg))), {regs[i]}, {rhsReg}, true); self:freeRegister(rhsReg, false); else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/nil.lua b/src/prometheus/cff/expressions/nil.lua index 4e716ea3..4fac8c28 100644 --- a/src/prometheus/cff/expressions/nil.lua +++ b/src/prometheus/cff/expressions/nil.lua @@ -7,11 +7,10 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end return regs; end; diff --git a/src/prometheus/cff/expressions/not.lua b/src/prometheus/cff/expressions/not.lua index 3176c80f..bd7d8acd 100644 --- a/src/prometheus/cff/expressions/not.lua +++ b/src/prometheus/cff/expressions/not.lua @@ -7,17 +7,16 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); if i == 1 then local rhsReg = self:compileExpression(expression.rhs, funcDepth, 1)[1]; - self:addStatement(self:setRegister(scope, regs[i], Ast.NotExpression(self:register(scope, rhsReg))), {regs[i]}, {rhsReg}, false); + self:addStatement(self:setRegister(regs[i], Ast.NotExpression(self:register(rhsReg))), {regs[i]}, {rhsReg}, false); self:freeRegister(rhsReg, false); else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/number.lua b/src/prometheus/cff/expressions/number.lua index 4fe5e3b9..49d09383 100644 --- a/src/prometheus/cff/expressions/number.lua +++ b/src/prometheus/cff/expressions/number.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); @@ -15,13 +14,13 @@ return function(self, expression, funcDepth, numReturns) if type(expression.value) == "number" and expression.value == 0 and 1 / expression.value < 0 then -- Keep negative zero out of native Lua 5.1 numeric constants. -- The register read makes this runtime negation distinct from a -0 literal. - self:addStatement(self:setRegister(scope, regs[i], Ast.NumberExpression(0)), {regs[i]}, {}, false); - self:addStatement(self:setRegister(scope, regs[i], Ast.NegateExpression(self:register(scope, regs[i]))), {regs[i]}, {regs[i]}, false); + self:addStatement(self:setRegister(regs[i], Ast.NumberExpression(0)), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NegateExpression(self:register(regs[i]))), {regs[i]}, {regs[i]}, false); else - self:addStatement(self:setRegister(scope, regs[i], Ast.NumberExpression(expression.value)), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NumberExpression(expression.value)), {regs[i]}, {}, false); end else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/or.lua b/src/prometheus/cff/expressions/or.lua index 92e6ca77..9ec49023 100644 --- a/src/prometheus/cff/expressions/or.lua +++ b/src/prometheus/cff/expressions/or.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local posState = self.registers[self.POS_REGISTER]; self.registers[self.POS_REGISTER] = self.VAR_REGISTER; @@ -15,7 +14,7 @@ return function(self, expression, funcDepth, numReturns) for i = 1, numReturns do regs[i] = self:allocRegister(); if i ~= 1 then - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end @@ -24,13 +23,13 @@ return function(self, expression, funcDepth, numReturns) if posState then tmpReg = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {tmpReg}, {self.POS_REGISTER}), {tmpReg}, {self.POS_REGISTER}, false); + self:addStatement(self:copyRegisters({tmpReg}, {self.POS_REGISTER}), {tmpReg}, {self.POS_REGISTER}, false); end local lhsReg = self:compileExpression(expression.lhs, funcDepth, 1)[1]; if expression.rhs.isConstant then local rhsReg = self:compileExpression(expression.rhs, funcDepth, 1)[1]; - self:addStatement(self:setRegister(scope, resReg, Ast.OrExpression(self:register(scope, lhsReg), self:register(scope, rhsReg))), {resReg}, {lhsReg, rhsReg}, false); + self:addStatement(self:setRegister(resReg, Ast.OrExpression(self:register(lhsReg), self:register(rhsReg))), {resReg}, {lhsReg, rhsReg}, false); if tmpReg then self:freeRegister(tmpReg, false); end @@ -40,28 +39,25 @@ return function(self, expression, funcDepth, numReturns) end local block1, block2 = self:createBlock(), self:createBlock(); - self:addStatement(self:copyRegisters(scope, {resReg}, {lhsReg}), {resReg}, {lhsReg}, false); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(scope, lhsReg), Ast.NumberExpression(block2.id)), Ast.NumberExpression(block1.id))), {self.POS_REGISTER}, {lhsReg}, false); + self:addStatement(self:copyRegisters({resReg}, {lhsReg}), {resReg}, {lhsReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(lhsReg), Ast.NumberExpression(block2.id)), Ast.NumberExpression(block1.id))), {self.POS_REGISTER}, {lhsReg}, false); self:freeRegister(lhsReg, false); do self:setActiveBlock(block1); - local scope = block1.scope; local rhsReg = self:compileExpression(expression.rhs, funcDepth, 1)[1]; - self:addStatement(self:copyRegisters(scope, {resReg}, {rhsReg}), {resReg}, {rhsReg}, false); + self:addStatement(self:copyRegisters({resReg}, {rhsReg}), {resReg}, {rhsReg}, false); self:freeRegister(rhsReg, false); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(block2.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(block2.id)), {self.POS_REGISTER}, {}, false); end self.registers[self.POS_REGISTER] = posState; self:setActiveBlock(block2); - scope = block2.scope; - if tmpReg then - self:addStatement(self:copyRegisters(scope, {self.POS_REGISTER}, {tmpReg}), {self.POS_REGISTER}, {tmpReg}, false); + self:addStatement(self:copyRegisters({self.POS_REGISTER}, {tmpReg}), {self.POS_REGISTER}, {tmpReg}, false); self:freeRegister(tmpReg, false); end diff --git a/src/prometheus/cff/expressions/pass_self_function_call.lua b/src/prometheus/cff/expressions/pass_self_function_call.lua index 30e124b9..366115a8 100644 --- a/src/prometheus/cff/expressions/pass_self_function_call.lua +++ b/src/prometheus/cff/expressions/pass_self_function_call.lua @@ -8,7 +8,6 @@ local Ast = require("prometheus.ast"); local AstKind = Ast.AstKind; return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local baseReg = self:compileExpression(expression.base, funcDepth, 1)[1]; local retRegs = {}; local returnAll = numReturns == self.RETURN_ALL; @@ -20,19 +19,19 @@ return function(self, expression, funcDepth, numReturns) end end - local args = { self:register(scope, baseReg) }; + local args = { self:register(baseReg) }; local regs = { baseReg }; for i, expr in ipairs(expression.args) do if i == #expression.args and (expr.kind == AstKind.FunctionCallExpression or expr.kind == AstKind.PassSelfFunctionCallExpression or expr.kind == AstKind.VarargExpression) then local reg = self:compileExpression(expr, funcDepth, self.RETURN_ALL)[1]; table.insert(args, Ast.FunctionCallExpression( - self:unpack(scope), - {self:register(scope, reg)})); + self:unpack(), + {self:register(reg)})); table.insert(regs, reg); else local reg = self:compileExpression(expr, funcDepth, 1)[1]; - table.insert(args, self:register(scope, reg)); + table.insert(args, self:register(reg)); table.insert(regs, reg); end end @@ -40,16 +39,16 @@ return function(self, expression, funcDepth, numReturns) if returnAll or numReturns > 1 then local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(expression.passSelfFunctionName)), {tmpReg}, {}, false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, true); + self:addStatement(self:setRegister(tmpReg, Ast.StringExpression(expression.passSelfFunctionName)), {tmpReg}, {}, false); + self:addStatement(self:setRegister(tmpReg, Ast.IndexExpression(self:register(baseReg), self:register(tmpReg))), {tmpReg}, {baseReg, tmpReg}, true); if returnAll then - self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {self:functionCall(scope, tmpReg, args)})), {retRegs[1]}, {tmpReg, unpack(regs)}, true); + self:addStatement(self:setRegister(retRegs[1], self:pack({self:functionCall(tmpReg, args)})), {retRegs[1]}, {tmpReg, unpack(regs)}, true); else - self:addStatement(self:setRegister(scope, tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(self:functionCall(scope, tmpReg, args))}), {tmpReg}, {tmpReg, unpack(regs)}, true); + self:addStatement(self:setRegister(tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(self:functionCall(tmpReg, args))}), {tmpReg}, {tmpReg, unpack(regs)}, true); for i, reg in ipairs(retRegs) do - self:addStatement(self:setRegister(scope, reg, Ast.IndexExpression(self:register(scope, tmpReg), Ast.NumberExpression(i))), {reg}, {tmpReg}, false); + self:addStatement(self:setRegister(reg, Ast.IndexExpression(self:register(tmpReg), Ast.NumberExpression(i))), {reg}, {tmpReg}, false); end end @@ -57,9 +56,9 @@ return function(self, expression, funcDepth, numReturns) else local tmpReg = retRegs[1] or self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(expression.passSelfFunctionName)), {tmpReg}, {}, false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, true); - self:addStatement(self:setRegister(scope, retRegs[1], self:functionCall(scope, tmpReg, args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(tmpReg, Ast.StringExpression(expression.passSelfFunctionName)), {tmpReg}, {}, false); + self:addStatement(self:setRegister(tmpReg, Ast.IndexExpression(self:register(baseReg), self:register(tmpReg))), {tmpReg}, {baseReg, tmpReg}, true); + self:addStatement(self:setRegister(retRegs[1], self:functionCall(tmpReg, args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); end for i, reg in ipairs(regs) do diff --git a/src/prometheus/cff/expressions/string.lua b/src/prometheus/cff/expressions/string.lua index c3b8dbd7..2edd7a7a 100644 --- a/src/prometheus/cff/expressions/string.lua +++ b/src/prometheus/cff/expressions/string.lua @@ -7,14 +7,13 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns, 1 do regs[i] = self:allocRegister(); if i == 1 then - self:addStatement(self:setRegister(scope, regs[i], Ast.StringExpression(expression.value)), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.StringExpression(expression.value)), {regs[i]}, {}, false); else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/table_constructor.lua b/src/prometheus/cff/expressions/table_constructor.lua index f9ce495a..090a8c82 100644 --- a/src/prometheus/cff/expressions/table_constructor.lua +++ b/src/prometheus/cff/expressions/table_constructor.lua @@ -8,7 +8,6 @@ local Ast = require("prometheus.ast"); local AstKind = Ast.AstKind; return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(); @@ -21,28 +20,28 @@ return function(self, expression, funcDepth, numReturns) if i == #expression.entries and (value.kind == AstKind.FunctionCallExpression or value.kind == AstKind.PassSelfFunctionCallExpression or value.kind == AstKind.VarargExpression) then local reg = self:compileExpression(entry.value, funcDepth, self.RETURN_ALL)[1]; table.insert(entries, Ast.TableEntry(Ast.FunctionCallExpression( - self:unpack(scope), - {self:register(scope, reg)}))); + self:unpack(), + {self:register(reg)}))); table.insert(entryRegs, reg); else local reg = self:compileExpression(entry.value, funcDepth, 1)[1]; - table.insert(entries, Ast.TableEntry(self:register(scope, reg))); + table.insert(entries, Ast.TableEntry(self:register(reg))); table.insert(entryRegs, reg); end else local keyReg = self:compileExpression(entry.key, funcDepth, 1)[1]; local valReg = self:compileExpression(entry.value, funcDepth, 1)[1]; - table.insert(entries, Ast.KeyedTableEntry(self:register(scope, keyReg), self:register(scope, valReg))); + table.insert(entries, Ast.KeyedTableEntry(self:register(keyReg), self:register(valReg))); table.insert(entryRegs, valReg); table.insert(entryRegs, keyReg); end end - self:addStatement(self:setRegister(scope, regs[i], Ast.TableConstructorExpression(entries)), {regs[i]}, entryRegs, false); + self:addStatement(self:setRegister(regs[i], Ast.TableConstructorExpression(entries)), {regs[i]}, entryRegs, false); for i, reg in ipairs(entryRegs) do self:freeRegister(reg, false); end else - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/expressions/vararg.lua b/src/prometheus/cff/expressions/vararg.lua index 82e84c82..9664fb19 100644 --- a/src/prometheus/cff/expressions/vararg.lua +++ b/src/prometheus/cff/expressions/vararg.lua @@ -7,14 +7,13 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; if numReturns == self.RETURN_ALL then return {self.varargReg}; end local regs = {}; for i = 1, numReturns do regs[i] = self:allocRegister(false); - self:addStatement(self:setRegister(scope, regs[i], Ast.IndexExpression(self:register(scope, self.varargReg), Ast.NumberExpression(i))), {regs[i]}, {self.varargReg}, false); + self:addStatement(self:setRegister(regs[i], Ast.IndexExpression(self:register(self.varargReg), Ast.NumberExpression(i))), {regs[i]}, {self.varargReg}, false); end return regs; end; diff --git a/src/prometheus/cff/expressions/variable.lua b/src/prometheus/cff/expressions/variable.lua index 5a023be4..2cb11f96 100644 --- a/src/prometheus/cff/expressions/variable.lua +++ b/src/prometheus/cff/expressions/variable.lua @@ -7,22 +7,21 @@ local Ast = require("prometheus.ast"); return function(self, expression, funcDepth, numReturns) - local scope = self.activeBlock.scope; local regs = {}; for i = 1, numReturns do if i == 1 then - if expression.scope.isGlobal then + if expression.kind == Ast.AstKind.GlobalVariableExpression then regs[i] = self:allocRegister(false); local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(expression.scope:getVariableName(expression.id))), {tmpReg}, {}, false); - self:addStatement(self:setRegister(scope, regs[i], Ast.IndexExpression(self:env(scope), self:register(scope, tmpReg))), {regs[i]}, {tmpReg}, true); + self:addStatement(self:setRegister(tmpReg, Ast.StringExpression(expression.name)), {tmpReg}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.IndexExpression(self:env(), self:register(tmpReg))), {regs[i]}, {tmpReg}, true); self:freeRegister(tmpReg, false); else if self.scopeFunctionDepths[expression.scope] == funcDepth then if self:isUpvalue(expression.scope, expression.id) then local reg = self:allocRegister(false); local varReg = self:getVarRegister(expression.scope, expression.id, funcDepth, nil); - self:addStatement(self:setRegister(scope, reg, self:getUpvalueMember(scope, self:register(scope, varReg))), {reg}, {varReg}, true); + self:addStatement(self:setRegister(reg, self:getUpvalueMember(self:register(varReg))), {reg}, {varReg}, true); regs[i] = reg; else regs[i] = self:getVarRegister(expression.scope, expression.id, funcDepth, nil); @@ -30,13 +29,13 @@ return function(self, expression, funcDepth, numReturns) else local reg = self:allocRegister(false); local upvalId = self:getUpvalueId(expression.scope, expression.id); - self:addStatement(self:setRegister(scope, reg, self:getUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)))), {reg}, {}, true); + self:addStatement(self:setRegister(reg, self:getUpvalueMember(Ast.IndexExpression(Ast.VariableExpression(self.currentUpvaluesVar), Ast.NumberExpression(upvalId)))), {reg}, {}, true); regs[i] = reg; end end else regs[i] = self:allocRegister(); - self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + self:addStatement(self:setRegister(regs[i], Ast.NilExpression()), {regs[i]}, {}, false); end end return regs; diff --git a/src/prometheus/cff/native.lua b/src/prometheus/cff/native.lua index 5cec0c9c..bd12e983 100644 --- a/src/prometheus/cff/native.lua +++ b/src/prometheus/cff/native.lua @@ -7,25 +7,28 @@ local Kind = Ast.AstKind local function walk(expression, ast, previsit, postvisit) return require('prometheus.visitast')(Ast.TopNode(Ast.Block({ Ast.ReturnStatement({expression}), - }, ast.body.scope), ast.globalScope), previsit, postvisit) + })), previsit, postvisit) end local function captures(expression, ast) local owned, variables, seen = {}, {}, {} walk(expression, ast, function(node) - if node.kind == Kind.Block and node.scope ~= ast.body.scope then + if node.kind == Kind.Block and node.scope and node.scope ~= ast.body.scope then owned[node.scope] = true end end) walk(expression, ast, function(node) - if (node.kind == Kind.VariableExpression or node.kind == Kind.GlobalVariableExpression - or node.kind == Kind.AssignmentVariable or node.kind == Kind.AssignmentGlobalVariable - or node.kind == Kind.FunctionDeclaration) and not node.scope.isGlobal - and not owned[node.scope] then - seen[node.scope] = seen[node.scope] or {} - if not seen[node.scope][node.id] then - variables[#variables + 1] = {scope = node.scope, id = node.id} - seen[node.scope][node.id] = #variables + local isGlobal = node.kind == Kind.GlobalVariableExpression + or node.kind == Kind.AssignmentGlobalVariable + or (node.kind == Kind.FunctionDeclaration and not node.symbol) + if (node.kind == Kind.VariableExpression or node.kind == Kind.AssignmentVariable + or node.kind == Kind.FunctionDeclaration) and not isGlobal and node.symbol + and not owned[node.symbol.scope] then + local scope, id = node.symbol.scope, node.symbol.id + seen[scope] = seen[scope] or {} + if not seen[scope][id] then + variables[#variables + 1] = {scope = scope, id = id, symbol = node.symbol} + seen[scope][id] = #variables end end end) @@ -44,7 +47,7 @@ return function(Compiler) end function Compiler:compileNativeExpression(expression, funcDepth, numReturns) - local ast, scope = self.nativeAst, self.activeBlock.scope + local ast = self.nativeAst local variables, lookup, owned = captures(expression, ast) local entries, reads = {}, {} for _, variable in ipairs(variables) do @@ -52,12 +55,11 @@ return function(Compiler) local id if depth == funcDepth then local register = self:getVarRegister(variable.scope, variable.id, funcDepth) - id = self:register(scope, register) + id = self:register(register) reads[#reads + 1] = register else local index = self:getUpvalueId(variable.scope, variable.id) - id = Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, - self.currentUpvaluesVar), Ast.NumberExpression(index)) + id = Ast.IndexExpression(Ast.VariableExpression(self.currentUpvaluesVar), Ast.NumberExpression(index)) end entries[#entries + 1] = Ast.TableEntry(id) end @@ -76,17 +78,19 @@ return function(Compiler) used[name], names[label] = true, name end local function marker(label) - local _, id = ast.globalScope:resolve(names[label]) - return Ast.VariableExpression(ast.globalScope, id) + return Ast.GlobalVariableExpression(names[label]) end local function access(node, assignment) local base, index - if node.scope.isGlobal then - local name = node.scope:getVariableName(node.id) + if node.kind == Kind.GlobalVariableExpression or node.kind == Kind.AssignmentGlobalVariable + or (node.kind == Kind.FunctionDeclaration and not node.symbol) then + local name = node.name if name == '_ENV' and not assignment then return marker('env') end base, index = marker('env'), Ast.StringExpression(name) else - local slot = lookup[node.scope] and lookup[node.scope][node.id] + local symbol = node.symbol + local scope, id = symbol and symbol.scope, symbol and symbol.id + local slot = scope and lookup[scope] and lookup[scope][id] if not slot then return end base = marker('values') index = Ast.IndexExpression(marker('proxy'), Ast.NumberExpression(slot)) @@ -121,24 +125,23 @@ return function(Compiler) local factory = parsed.body.statements[1].args[1] self.nativeResults[expression] = factory.body.statements[1].expressions[1] require('prometheus.visitast')(parsed, nil, function(node, data) - if (node.kind == Kind.VariableExpression or node.kind == Kind.GlobalVariableExpression) and node.scope.isGlobal then - assert(node.scope:getVariableName(node.id) == names.setenv, 'Unexpected native global') - return Ast.VariableExpression(self.scope, self.setfenvVar) + if node.kind == Kind.GlobalVariableExpression then + assert(node.name == names.setenv, 'Unexpected native global') + return Ast.VariableExpression(self.setfenvVar) end end) local proxy = Ast.NilExpression() if #entries > 0 then - proxy = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, - self.upvaluesProxyFunctionVar), {Ast.TableConstructorExpression(entries)}) + proxy = Ast.FunctionCallExpression(Ast.VariableExpression(self.upvaluesProxyFunctionVar), {Ast.TableConstructorExpression(entries)}) end - local value = Ast.FunctionCallExpression(factory, {proxy, self:env(scope), - Ast.VariableExpression(self.scope, self.upvaluesTable)}) + local value = Ast.FunctionCallExpression(factory, {proxy, self:env(), + Ast.VariableExpression(self.upvaluesTable)}) local result = self:allocRegister(false) - self:addStatement(self:setRegister(scope, result, value), {result}, reads, true) + self:addStatement(self:setRegister(result, value), {result}, reads, true) local registers = {result} for i = 2, numReturns do registers[i] = self:allocRegister(false) - self:addStatement(self:setRegister(scope, registers[i], Ast.NilExpression()), + self:addStatement(self:setRegister(registers[i], Ast.NilExpression()), {registers[i]}, {}, false) end return registers diff --git a/src/prometheus/cff/register.lua b/src/prometheus/cff/register.lua index f168bcb4..7b344909 100644 --- a/src/prometheus/cff/register.lua +++ b/src/prometheus/cff/register.lua @@ -15,8 +15,7 @@ return function(Compiler) -- Releasing an allocator slot must also release its runtime reference. -- Control/return slots are reserved and never contain temporaries. if id ~= self.POS_REGISTER and id ~= self.RETURN_REGISTER then - local scope = self.activeBlock.scope; - self:addStatement(self:setRegister(scope, id, Ast.NilExpression()), {id}, {}, false, true); + self:addStatement(self:setRegister(id, Ast.NilExpression()), {id}, {}, false, true); end self.usedRegisters = self.usedRegisters - 1; self.registers[id] = false @@ -88,83 +87,83 @@ return function(Compiler) function Compiler:getRegisterVarId(id) local varId = self.registerVars[id]; if not varId then - varId = self.containerFuncScope:addVariable(); + varId = Ast.LocalSymbol(); self.registerVars[id] = varId; end return varId; end - function Compiler:register(scope, id) + function Compiler:register(id) if id == self.POS_REGISTER then - return self:pos(scope); + return self:pos(); end if id == self.RETURN_REGISTER then - return self:getReturn(scope); + return self:getReturn(); end if id < MAX_REGS then local vid = self:getRegisterVarId(id); - return Ast.VariableExpression(self.containerFuncScope, vid); + return Ast.VariableExpression(vid); end local vid = self:getRegisterVarId(MAX_REGS); - return Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, vid), Ast.NumberExpression((id - MAX_REGS) + 1)); + return Ast.IndexExpression(Ast.VariableExpression(vid), Ast.NumberExpression((id - MAX_REGS) + 1)); end - function Compiler:registerList(scope, ids) + function Compiler:registerList(ids) local l = {}; for i, id in ipairs(ids) do - table.insert(l, self:register(scope, id)); + table.insert(l, self:register(id)); end return l; end - function Compiler:registerAssignment(scope, id) + function Compiler:registerAssignment(id) if id == self.POS_REGISTER then - return self:posAssignment(scope); + return self:posAssignment(); end if id == self.RETURN_REGISTER then - return self:returnAssignment(scope); + return self:returnAssignment(); end if id < MAX_REGS then local vid = self:getRegisterVarId(id); - return Ast.AssignmentVariable(self.containerFuncScope, vid); + return Ast.AssignmentVariable(vid); end local vid = self:getRegisterVarId(MAX_REGS); - return Ast.AssignmentIndexing(Ast.VariableExpression(self.containerFuncScope, vid), Ast.NumberExpression((id - MAX_REGS) + 1)); + return Ast.AssignmentIndexing(Ast.VariableExpression(vid), Ast.NumberExpression((id - MAX_REGS) + 1)); end - function Compiler:setRegister(scope, id, val, compundArg) + function Compiler:setRegister(id, val, compundArg) if(compundArg) then - return compundArg(self:registerAssignment(scope, id), val); + return compundArg(self:registerAssignment(id), val); end return Ast.AssignmentStatement({ - self:registerAssignment(scope, id) + self:registerAssignment(id) }, { val }); end - function Compiler:setRegisters(scope, ids, vals) + function Compiler:setRegisters(ids, vals) local idStats = {}; for i, id in ipairs(ids) do - table.insert(idStats, self:registerAssignment(scope, id)); + table.insert(idStats, self:registerAssignment(id)); end return Ast.AssignmentStatement(idStats, vals); end - function Compiler:copyRegisters(scope, to, from) + function Compiler:copyRegisters(to, from) local idStats = {}; local vals = {}; for i, id in ipairs(to) do local fromId = from[i]; if(fromId ~= id) then - table.insert(idStats, self:registerAssignment(scope, id)); - table.insert(vals, self:register(scope, fromId)); + table.insert(idStats, self:registerAssignment(id)); + table.insert(vals, self:register(fromId)); end end @@ -177,68 +176,68 @@ return function(Compiler) self.registers = {}; end - function Compiler:pos(scope) - return Ast.VariableExpression(self.containerFuncScope, self.posVar); + function Compiler:pos() + return Ast.VariableExpression(self.posVar); end - function Compiler:posAssignment(scope) - return Ast.AssignmentVariable(self.containerFuncScope, self.posVar); + function Compiler:posAssignment() + return Ast.AssignmentVariable(self.posVar); end - function Compiler:args(scope) - return Ast.VariableExpression(self.containerFuncScope, self.argsVar); + function Compiler:args() + return Ast.VariableExpression(self.argsVar); end - function Compiler:unpack(scope) - return Ast.VariableExpression(self.scope, self.unpackPackedVar); + function Compiler:unpack() + return Ast.VariableExpression(self.unpackPackedVar); end - function Compiler:pack(scope, values) - return Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.packVar), values); + function Compiler:pack(values) + return Ast.FunctionCallExpression(Ast.VariableExpression(self.packVar), values); end - function Compiler:env(scope) - return Ast.VariableExpression(self.containerFuncScope, self.currentEnvVar); + function Compiler:env() + return Ast.VariableExpression(self.currentEnvVar); end - function Compiler:functionCall(scope, reg, args) - local callArgs = {self:register(scope, reg), self:env(scope)}; + function Compiler:functionCall(reg, args) + local callArgs = {self:register(reg), self:env()}; for _, arg in ipairs(args) do table.insert(callArgs, arg); end - return Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.callFunctionVar), callArgs); + return Ast.FunctionCallExpression(Ast.VariableExpression(self.callFunctionVar), callArgs); end - function Compiler:jmp(scope, to) - return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.posVar)},{to}); + function Compiler:jmp(to) + return Ast.AssignmentStatement({Ast.AssignmentVariable(self.posVar)},{to}); end - function Compiler:setPos(scope, val) + function Compiler:setPos(val) if not val then - return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.posVar)}, {Ast.NilExpression()}); + return Ast.AssignmentStatement({Ast.AssignmentVariable(self.posVar)}, {Ast.NilExpression()}); end - return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.posVar)}, {Ast.NumberExpression(val) or Ast.NilExpression()}); + return Ast.AssignmentStatement({Ast.AssignmentVariable(self.posVar)}, {Ast.NumberExpression(val) or Ast.NilExpression()}); end - function Compiler:setReturn(scope, val) - return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.returnVar)}, {val}); + function Compiler:setReturn(val) + return Ast.AssignmentStatement({Ast.AssignmentVariable(self.returnVar)}, {val}); end - function Compiler:getReturn(scope) - return Ast.VariableExpression(self.containerFuncScope, self.returnVar); + function Compiler:getReturn() + return Ast.VariableExpression(self.returnVar); end - function Compiler:returnAssignment(scope) - return Ast.AssignmentVariable(self.containerFuncScope, self.returnVar); + function Compiler:returnAssignment() + return Ast.AssignmentVariable(self.returnVar); end - function Compiler:setUpvalueMember(scope, idExpr, valExpr, compoundConstructor) + function Compiler:setUpvalueMember(idExpr, valExpr, compoundConstructor) if compoundConstructor then - return compoundConstructor(Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesTable), idExpr), valExpr); + return compoundConstructor(Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesTable), idExpr), valExpr); end - return Ast.AssignmentStatement({Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesTable), idExpr)}, {valExpr}); + return Ast.AssignmentStatement({Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesTable), idExpr)}, {valExpr}); end - function Compiler:getUpvalueMember(scope, idExpr) - return Ast.IndexExpression(Ast.VariableExpression(self.scope, self.upvaluesTable), idExpr); + function Compiler:getUpvalueMember(idExpr) + return Ast.IndexExpression(Ast.VariableExpression(self.upvaluesTable), idExpr); end end diff --git a/src/prometheus/cff/statements/assignment.lua b/src/prometheus/cff/statements/assignment.lua index f74cf187..f1506ae5 100644 --- a/src/prometheus/cff/statements/assignment.lua +++ b/src/prometheus/cff/statements/assignment.lua @@ -14,14 +14,13 @@ local function isSideEffectFreeLiteral(expression) end return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local exprregs = {}; local assignmentIndexingRegs = {}; local function captureTarget(expression) local reg = self:compileExpression(expression, funcDepth, 1)[1]; if self:isVarRegister(reg) then local snapshot = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {snapshot}, {reg}), {snapshot}, {reg}, false); + self:addStatement(self:copyRegisters({snapshot}, {reg}), {snapshot}, {reg}, false); reg = snapshot; end return reg; @@ -45,7 +44,7 @@ return function(self, statement, funcDepth) if(self:isVarRegister(reg)) then local ro = reg; reg = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {reg}, {ro}), {reg}, {ro}, false); + self:addStatement(self:copyRegisters({reg}, {ro}), {reg}, {ro}, false); end table.insert(exprregs, reg); end @@ -55,7 +54,7 @@ return function(self, statement, funcDepth) if(self:isVarRegister(reg)) then local ro = reg; reg = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {reg}, {ro}), {reg}, {ro}, false); + self:addStatement(self:copyRegisters({reg}, {ro}), {reg}, {ro}, false); end table.insert(exprregs, reg); else @@ -72,35 +71,35 @@ return function(self, statement, funcDepth) for i = #statement.lhs, 1, -1 do local primaryExpr = statement.lhs[i]; if primaryExpr.kind == AstKind.AssignmentVariable or primaryExpr.kind == AstKind.AssignmentGlobalVariable then - if primaryExpr.scope.isGlobal then + if primaryExpr.kind == AstKind.AssignmentGlobalVariable then local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(primaryExpr.scope:getVariableName(primaryExpr.id))), {tmpReg}, {}, false); - self:addStatement(Ast.AssignmentStatement({Ast.AssignmentIndexing(self:env(scope), self:register(scope, tmpReg))}, - {self:register(scope, exprregs[i])}), {}, {tmpReg, exprregs[i]}, true); + self:addStatement(self:setRegister(tmpReg, Ast.StringExpression(primaryExpr.name)), {tmpReg}, {}, false); + self:addStatement(Ast.AssignmentStatement({Ast.AssignmentIndexing(self:env(), self:register(tmpReg))}, + {self:register(exprregs[i])}), {}, {tmpReg, exprregs[i]}, true); self:freeRegister(tmpReg, false); else if self.scopeFunctionDepths[primaryExpr.scope] == funcDepth then if self:isUpvalue(primaryExpr.scope, primaryExpr.id) then local reg = self:getVarRegister(primaryExpr.scope, primaryExpr.id, funcDepth); - self:addStatement(self:setUpvalueMember(scope, self:register(scope, reg), self:register(scope, exprregs[i])), {}, {reg, exprregs[i]}, true); + self:addStatement(self:setUpvalueMember(self:register(reg), self:register(exprregs[i])), {}, {reg, exprregs[i]}, true); else local reg = self:getVarRegister(primaryExpr.scope, primaryExpr.id, funcDepth, exprregs[i]); if reg ~= exprregs[i] then - self:addStatement(self:setRegister(scope, reg, self:register(scope, exprregs[i])), {reg}, {exprregs[i]}, false); + self:addStatement(self:setRegister(reg, self:register(exprregs[i])), {reg}, {exprregs[i]}, false); end end else local upvalId = self:getUpvalueId(primaryExpr.scope, primaryExpr.id); - self:addStatement(self:setUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(scope, exprregs[i])), {}, {exprregs[i]}, true); + self:addStatement(self:setUpvalueMember(Ast.IndexExpression(Ast.VariableExpression(self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(exprregs[i])), {}, {exprregs[i]}, true); end end elseif primaryExpr.kind == AstKind.AssignmentIndexing then local baseReg = assignmentIndexingRegs[i].base; local indexReg = assignmentIndexingRegs[i].index; self:addStatement(Ast.AssignmentStatement({ - Ast.AssignmentIndexing(self:register(scope, baseReg), self:register(scope, indexReg)) + Ast.AssignmentIndexing(self:register(baseReg), self:register(indexReg)) }, { - self:register(scope, exprregs[i]) + self:register(exprregs[i]) }), {}, {exprregs[i], baseReg, indexReg}, true); self:freeRegister(baseReg, false); self:freeRegister(indexReg, false); diff --git a/src/prometheus/cff/statements/break_statement.lua b/src/prometheus/cff/statements/break_statement.lua index b552d7ba..676e4105 100644 --- a/src/prometheus/cff/statements/break_statement.lua +++ b/src/prometheus/cff/statements/break_statement.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local toFreeVars = {}; local statScope; repeat @@ -24,15 +23,15 @@ return function(self, statement, funcDepth) local varScope, id = var.scope, var.id; local varReg = self:getVarRegister(varScope, id, nil, nil); if self:isUpvalue(varScope, id) then - self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.freeUpvalueFunc), { - self:register(scope, varReg) + self:addStatement(self:setRegister(varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.freeUpvalueFunc), { + self:register(varReg) })), {varReg}, {varReg}, false); else - self:addStatement(self:setRegister(scope, varReg, Ast.NilExpression()), {varReg}, {}, false); + self:addStatement(self:setRegister(varReg, Ast.NilExpression()), {varReg}, {}, false); end end - self:addStatement(self:setPos(scope, statement.loop.__final_block.id), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setPos(statement.loop.__final_block.id), {self.POS_REGISTER}, {}, false); self.activeBlock.advanceToNextBlock = false; end; diff --git a/src/prometheus/cff/statements/compound.lua b/src/prometheus/cff/statements/compound.lua index c6a65cf2..51df1653 100644 --- a/src/prometheus/cff/statements/compound.lua +++ b/src/prometheus/cff/statements/compound.lua @@ -18,7 +18,6 @@ local compoundConstructors = { }; return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local compoundConstructor = compoundConstructors[statement.kind]; if statement.lhs.kind == AstKind.AssignmentIndexing then local indexing = statement.lhs; @@ -26,34 +25,34 @@ return function(self, statement, funcDepth) local indexReg = self:compileExpression(indexing.index, funcDepth, 1)[1]; local valueReg = self:compileExpression(statement.rhs, funcDepth, 1)[1]; - self:addStatement(compoundConstructor(Ast.AssignmentIndexing(self:register(scope, baseReg), self:register(scope, indexReg)), self:register(scope, valueReg)), {}, {baseReg, indexReg, valueReg}, true); + self:addStatement(compoundConstructor(Ast.AssignmentIndexing(self:register(baseReg), self:register(indexReg)), self:register(valueReg)), {}, {baseReg, indexReg, valueReg}, true); self:freeRegister(baseReg, false); self:freeRegister(indexReg, false); self:freeRegister(valueReg, false); else local valueReg = self:compileExpression(statement.rhs, funcDepth, 1)[1]; local primaryExpr = statement.lhs; - if primaryExpr.scope.isGlobal then + if primaryExpr.kind == AstKind.AssignmentGlobalVariable then local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(primaryExpr.scope:getVariableName(primaryExpr.id))), {tmpReg}, {}, false); - self:addStatement(compoundConstructor(Ast.AssignmentIndexing(self:env(scope), self:register(scope, tmpReg)), - self:register(scope, valueReg)), {}, {tmpReg, valueReg}, true); + self:addStatement(self:setRegister(tmpReg, Ast.StringExpression(primaryExpr.name)), {tmpReg}, {}, false); + self:addStatement(compoundConstructor(Ast.AssignmentIndexing(self:env(), self:register(tmpReg)), + self:register(valueReg)), {}, {tmpReg, valueReg}, true); self:freeRegister(tmpReg, false); self:freeRegister(valueReg, false); else if self.scopeFunctionDepths[primaryExpr.scope] == funcDepth then if self:isUpvalue(primaryExpr.scope, primaryExpr.id) then local reg = self:getVarRegister(primaryExpr.scope, primaryExpr.id, funcDepth); - self:addStatement(self:setUpvalueMember(scope, self:register(scope, reg), self:register(scope, valueReg), compoundConstructor), {}, {reg, valueReg}, true); + self:addStatement(self:setUpvalueMember(self:register(reg), self:register(valueReg), compoundConstructor), {}, {reg, valueReg}, true); else local reg = self:getVarRegister(primaryExpr.scope, primaryExpr.id, funcDepth, valueReg); if reg ~= valueReg then - self:addStatement(self:setRegister(scope, reg, self:register(scope, valueReg), compoundConstructor), {reg}, {valueReg}, false); + self:addStatement(self:setRegister(reg, self:register(valueReg), compoundConstructor), {reg}, {valueReg}, false); end end else local upvalId = self:getUpvalueId(primaryExpr.scope, primaryExpr.id); - self:addStatement(self:setUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(scope, valueReg), compoundConstructor), {}, {valueReg}, true); + self:addStatement(self:setUpvalueMember(Ast.IndexExpression(Ast.VariableExpression(self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(valueReg), compoundConstructor), {}, {valueReg}, true); end self:freeRegister(valueReg, false); end diff --git a/src/prometheus/cff/statements/continue_statement.lua b/src/prometheus/cff/statements/continue_statement.lua index d75b8ba3..5878946e 100644 --- a/src/prometheus/cff/statements/continue_statement.lua +++ b/src/prometheus/cff/statements/continue_statement.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local toFreeVars = {}; local statScope; repeat @@ -24,15 +23,15 @@ return function(self, statement, funcDepth) local varScope, id = var.scope, var.id; local varReg = self:getVarRegister(varScope, id, nil, nil); if self:isUpvalue(varScope, id) then - self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.freeUpvalueFunc), { - self:register(scope, varReg) + self:addStatement(self:setRegister(varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.freeUpvalueFunc), { + self:register(varReg) })), {varReg}, {varReg}, false); else - self:addStatement(self:setRegister(scope, varReg, Ast.NilExpression()), {varReg}, {}, false); + self:addStatement(self:setRegister(varReg, Ast.NilExpression()), {varReg}, {}, false); end end - self:addStatement(self:setPos(scope, statement.loop.__start_block.id), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setPos(statement.loop.__start_block.id), {self.POS_REGISTER}, {}, false); self.activeBlock.advanceToNextBlock = false; end; diff --git a/src/prometheus/cff/statements/for_in_statement.lua b/src/prometheus/cff/statements/for_in_statement.lua index d69d6290..ff1af1b4 100644 --- a/src/prometheus/cff/statements/for_in_statement.lua +++ b/src/prometheus/cff/statements/for_in_statement.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local expressionsLength = #statement.expressions; local exprregs = {}; for i, expr in ipairs(statement.expressions) do @@ -30,7 +29,7 @@ return function(self, statement, funcDepth) self.registers[reg] = self.VAR_REGISTER; else exprregs[i] = self:allocRegister(true); - self:addStatement(self:copyRegisters(scope, {exprregs[i]}, {reg}), {exprregs[i]}, {reg}, false); + self:addStatement(self:copyRegisters({exprregs[i]}, {reg}), {exprregs[i]}, {reg}, false); end end @@ -41,11 +40,9 @@ return function(self, statement, funcDepth) statement.__start_block = checkBlock; statement.__final_block = finalBlock; - self:addStatement(self:setPos(scope, checkBlock.id), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setPos(checkBlock.id), {self.POS_REGISTER}, {}, false); self:setActiveBlock(checkBlock); - local scope = self.activeBlock.scope; - local varRegs = {}; for i, id in ipairs(statement.ids) do varRegs[i] = self:getVarRegister(statement.scope, id, funcDepth) @@ -56,45 +53,43 @@ return function(self, statement, funcDepth) -- Keep the control result separate from the visible first loop variable. -- Native multiple assignment preserves nils and pads missing results. local resultRegs = {exprregs[3]}; - local resultTargets = {self:registerAssignment(scope, exprregs[3])}; + local resultTargets = {self:registerAssignment(exprregs[3])}; for i = 2, #varRegs do resultRegs[i] = varRegs[i]; - resultTargets[i] = self:registerAssignment(scope, varRegs[i]); + resultTargets[i] = self:registerAssignment(varRegs[i]); end self:addStatement(Ast.AssignmentStatement(resultTargets, { - Ast.FunctionCallExpression(self:register(scope, exprregs[1]), { - self:register(scope, exprregs[2]), - self:register(scope, exprregs[3]), + Ast.FunctionCallExpression(self:register(exprregs[1]), { + self:register(exprregs[2]), + self:register(exprregs[3]), }) }), resultRegs, {exprregs[1], exprregs[2], exprregs[3]}, true); self:addStatement(Ast.AssignmentStatement({ - self:posAssignment(scope) + self:posAssignment() }, { - Ast.OrExpression(Ast.AndExpression(self:register(scope, exprregs[3]), Ast.NumberExpression(bodyBlock.id)), Ast.NumberExpression(finalBlock.id)) + Ast.OrExpression(Ast.AndExpression(self:register(exprregs[3]), Ast.NumberExpression(bodyBlock.id)), Ast.NumberExpression(finalBlock.id)) }), {self.POS_REGISTER}, {exprregs[3]}, false); self:setActiveBlock(bodyBlock); - local scope = self.activeBlock.scope; - - self:addStatement(self:copyRegisters(scope, {varRegs[1]}, {exprregs[3]}), {varRegs[1]}, {exprregs[3]}, false); + self:addStatement(self:copyRegisters({varRegs[1]}, {exprregs[3]}), {varRegs[1]}, {exprregs[3]}, false); for i, id in ipairs(statement.ids) do if(self:isUpvalue(statement.scope, id)) then local varreg = varRegs[i]; local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {tmpReg}, {}, false); - self:addStatement(self:setUpvalueMember(scope, self:register(scope, tmpReg), self:register(scope, varreg)), {}, {tmpReg, varreg}, true); - self:addStatement(self:copyRegisters(scope, {varreg}, {tmpReg}), {varreg}, {tmpReg}, false); + self:addStatement(self:setRegister(tmpReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.allocUpvalFunction), {})), {tmpReg}, {}, false); + self:addStatement(self:setUpvalueMember(self:register(tmpReg), self:register(varreg)), {}, {tmpReg, varreg}, true); + self:addStatement(self:copyRegisters({varreg}, {tmpReg}), {varreg}, {tmpReg}, false); self:freeRegister(tmpReg, false); end end self:compileBlock(statement.body, funcDepth); - self:addStatement(self:setPos(scope, checkBlock.id), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setPos(checkBlock.id), {self.POS_REGISTER}, {}, false); self:setActiveBlock(finalBlock); for i, _ in ipairs(exprregs) do diff --git a/src/prometheus/cff/statements/for_statement.lua b/src/prometheus/cff/statements/for_statement.lua index c2c9045a..53d3d4c4 100644 --- a/src/prometheus/cff/statements/for_statement.lua +++ b/src/prometheus/cff/statements/for_statement.lua @@ -8,7 +8,6 @@ local Ast = require("prometheus.ast"); local util = require("prometheus.util"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local checkBlock = self:createBlock(); local innerBlock = self:createBlock(); local finalBlock = self:createBlock(); @@ -21,17 +20,17 @@ return function(self, statement, funcDepth) local initialExprReg = self:compileExpression(statement.initialValue, funcDepth, 1)[1]; local initialReg = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {initialReg}, {initialExprReg}), {initialReg}, {initialExprReg}, false); + self:addStatement(self:copyRegisters({initialReg}, {initialExprReg}), {initialReg}, {initialExprReg}, false); self:freeRegister(initialExprReg); local finalExprReg = self:compileExpression(statement.finalValue, funcDepth, 1)[1]; local finalReg = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {finalReg}, {finalExprReg}), {finalReg}, {finalExprReg}, false); + self:addStatement(self:copyRegisters({finalReg}, {finalExprReg}), {finalReg}, {finalExprReg}, false); self:freeRegister(finalExprReg); local incrementExprReg = self:compileExpression(statement.incrementBy, funcDepth, 1)[1]; local incrementReg = self:allocRegister(false); - self:addStatement(self:copyRegisters(scope, {incrementReg}, {incrementExprReg}), {incrementReg}, {incrementExprReg}, false); + self:addStatement(self:copyRegisters({incrementReg}, {incrementExprReg}), {incrementReg}, {incrementExprReg}, false); self:freeRegister(incrementExprReg); -- Lua evaluates all three expressions before coercing and validating them. @@ -42,82 +41,78 @@ return function(self, statement, funcDepth) {incrementReg, "step"}, }) do local reg, name = input[1], input[2]; - self:addStatement(self:setRegister(scope, reg, Ast.OrExpression( - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.tonumberVar), { - self:register(scope, reg), + self:addStatement(self:setRegister(reg, Ast.OrExpression( + Ast.FunctionCallExpression(Ast.VariableExpression(self.tonumberVar), { + self:register(reg), }), - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.errorVar), { + Ast.FunctionCallExpression(Ast.VariableExpression(self.errorVar), { Ast.StringExpression("'for' " .. name .. " must be a number"), }) )), {reg}, {reg}, true); end local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.NumberExpression(0)), {tmpReg}, {}, false); + self:addStatement(self:setRegister(tmpReg, Ast.NumberExpression(0)), {tmpReg}, {}, false); local incrementIsNegReg = self:allocRegister(false); local shouldSwap3 = math.random(1, 2) == 2; local shuffledRegs4 = shouldSwap3 and {incrementReg, tmpReg} or {tmpReg, incrementReg}; - self:addStatement(self:setRegister(scope, incrementIsNegReg, Ast[shouldSwap3 and "LessThanExpression" or "GreaterThanExpression"](self:register(scope, shuffledRegs4[1]), self:register(scope, shuffledRegs4[2]))), {incrementIsNegReg}, {shuffledRegs4[1], shuffledRegs4[2]}, false); + self:addStatement(self:setRegister(incrementIsNegReg, Ast[shouldSwap3 and "LessThanExpression" or "GreaterThanExpression"](self:register(shuffledRegs4[1]), self:register(shuffledRegs4[2]))), {incrementIsNegReg}, {shuffledRegs4[1], shuffledRegs4[2]}, false); self:freeRegister(tmpReg); local currentReg = self:allocRegister(true); - self:addStatement(self:setRegister(scope, currentReg, Ast.SubExpression(self:register(scope, initialReg), self:register(scope, incrementReg))), {currentReg}, {initialReg, incrementReg}, false); + self:addStatement(self:setRegister(currentReg, Ast.SubExpression(self:register(initialReg), self:register(incrementReg))), {currentReg}, {initialReg, incrementReg}, false); self:freeRegister(initialReg); - self:addStatement(self:jmp(scope, Ast.NumberExpression(checkBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:jmp(Ast.NumberExpression(checkBlock.id)), {self.POS_REGISTER}, {}, false); self:setActiveBlock(checkBlock); - - scope = checkBlock.scope; - -- x = x + y or x = y + x instead of just x = x + y. --> NEW: In an attempt to thwart deobfuscations, despite this being a simple comparison... Shuffling these causes problems for decompilers. --> NOTE: This isn't unstable code, I've tested it multiple times. local shuffledRegs = util.shuffle({currentReg, incrementReg}); - self:addStatement(self:setRegister(scope, currentReg, Ast.AddExpression(self:register(scope, shuffledRegs[1]), self:register(scope, shuffledRegs[2]))), {currentReg}, {shuffledRegs[1], shuffledRegs[2]}, false); + self:addStatement(self:setRegister(currentReg, Ast.AddExpression(self:register(shuffledRegs[1]), self:register(shuffledRegs[2]))), {currentReg}, {shuffledRegs[1], shuffledRegs[2]}, false); local tmpReg1 = self:allocRegister(false); local tmpReg2 = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg2, Ast.NotExpression(self:register(scope, incrementIsNegReg))), {tmpReg2}, {incrementIsNegReg}, false); + self:addStatement(self:setRegister(tmpReg2, Ast.NotExpression(self:register(incrementIsNegReg))), {tmpReg2}, {incrementIsNegReg}, false); local shouldSwap = math.random(1, 2) == 2; local shuffledRegs2 = shouldSwap and {currentReg, finalReg} or {finalReg, currentReg}; - self:addStatement(self:setRegister(scope, tmpReg1, Ast[shouldSwap and "LessThanOrEqualsExpression" or "GreaterThanOrEqualsExpression"](self:register(scope, shuffledRegs2[1]), self:register(scope, shuffledRegs2[2]))), {tmpReg1}, {shuffledRegs2[1], shuffledRegs2[2]}, false); - self:addStatement(self:setRegister(scope, tmpReg1, Ast.AndExpression(self:register(scope, tmpReg2), self:register(scope, tmpReg1))), {tmpReg1}, {tmpReg1, tmpReg2}, false); + self:addStatement(self:setRegister(tmpReg1, Ast[shouldSwap and "LessThanOrEqualsExpression" or "GreaterThanOrEqualsExpression"](self:register(shuffledRegs2[1]), self:register(shuffledRegs2[2]))), {tmpReg1}, {shuffledRegs2[1], shuffledRegs2[2]}, false); + self:addStatement(self:setRegister(tmpReg1, Ast.AndExpression(self:register(tmpReg2), self:register(tmpReg1))), {tmpReg1}, {tmpReg1, tmpReg2}, false); local shouldSwap2 = math.random(1, 2) == 2; local shuffledRegs3 = shouldSwap2 and {currentReg, finalReg} or {finalReg, currentReg}; - self:addStatement(self:setRegister(scope, tmpReg2, Ast[shouldSwap2 and "GreaterThanOrEqualsExpression" or "LessThanOrEqualsExpression"](self:register(scope, shuffledRegs3[1]), self:register(scope, shuffledRegs3[2]))), {tmpReg2}, {shuffledRegs3[1], shuffledRegs3[2]}, false); + self:addStatement(self:setRegister(tmpReg2, Ast[shouldSwap2 and "GreaterThanOrEqualsExpression" or "LessThanOrEqualsExpression"](self:register(shuffledRegs3[1]), self:register(shuffledRegs3[2]))), {tmpReg2}, {shuffledRegs3[1], shuffledRegs3[2]}, false); - self:addStatement(self:setRegister(scope, tmpReg2, Ast.AndExpression(self:register(scope, incrementIsNegReg), self:register(scope, tmpReg2))), {tmpReg2}, {tmpReg2, incrementIsNegReg}, false); - self:addStatement(self:setRegister(scope, tmpReg1, Ast.OrExpression(self:register(scope, tmpReg2), self:register(scope, tmpReg1))), {tmpReg1}, {tmpReg1, tmpReg2}, false); + self:addStatement(self:setRegister(tmpReg2, Ast.AndExpression(self:register(incrementIsNegReg), self:register(tmpReg2))), {tmpReg2}, {tmpReg2, incrementIsNegReg}, false); + self:addStatement(self:setRegister(tmpReg1, Ast.OrExpression(self:register(tmpReg2), self:register(tmpReg1))), {tmpReg1}, {tmpReg1, tmpReg2}, false); self:freeRegister(tmpReg2); tmpReg2 = self:compileExpression(Ast.NumberExpression(innerBlock.id), funcDepth, 1)[1]; - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.AndExpression(self:register(scope, tmpReg1), self:register(scope, tmpReg2))), {self.POS_REGISTER}, {tmpReg1, tmpReg2}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.AndExpression(self:register(tmpReg1), self:register(tmpReg2))), {self.POS_REGISTER}, {tmpReg1, tmpReg2}, false); self:freeRegister(tmpReg2); self:freeRegister(tmpReg1); tmpReg2 = self:compileExpression(Ast.NumberExpression(finalBlock.id), funcDepth, 1)[1]; - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.OrExpression(self:register(scope, self.POS_REGISTER), self:register(scope, tmpReg2))), {self.POS_REGISTER}, {self.POS_REGISTER, tmpReg2}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(self:register(self.POS_REGISTER), self:register(tmpReg2))), {self.POS_REGISTER}, {self.POS_REGISTER, tmpReg2}, false); self:freeRegister(tmpReg2); self:setActiveBlock(innerBlock); - scope = innerBlock.scope; self.registers[self.POS_REGISTER] = posState; local varReg = self:getVarRegister(statement.scope, statement.id, funcDepth, nil); if(self:isUpvalue(statement.scope, statement.id)) then - self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {varReg}, {}, false); - self:addStatement(self:setUpvalueMember(scope, self:register(scope, varReg), self:register(scope, currentReg)), {}, {varReg, currentReg}, true); + self:addStatement(self:setRegister(varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.allocUpvalFunction), {})), {varReg}, {}, false); + self:addStatement(self:setUpvalueMember(self:register(varReg), self:register(currentReg)), {}, {varReg, currentReg}, true); else - self:addStatement(self:setRegister(scope, varReg, self:register(scope, currentReg)), {varReg}, {currentReg}, false); + self:addStatement(self:setRegister(varReg, self:register(currentReg)), {varReg}, {currentReg}, false); end self:compileBlock(statement.body, funcDepth); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(checkBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(checkBlock.id)), {self.POS_REGISTER}, {}, false); -- Loop state is live on the back edge; release it only after exiting. self:setActiveBlock(finalBlock); diff --git a/src/prometheus/cff/statements/function_call.lua b/src/prometheus/cff/statements/function_call.lua index bfd21f25..bf7d855c 100644 --- a/src/prometheus/cff/statements/function_call.lua +++ b/src/prometheus/cff/statements/function_call.lua @@ -8,7 +8,6 @@ local Ast = require("prometheus.ast"); local AstKind = Ast.AstKind; return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local baseReg = self:compileExpression(statement.base, funcDepth, 1)[1]; local retReg = self:allocRegister(false); local regs = {}; @@ -18,18 +17,18 @@ return function(self, statement, funcDepth) if i == #statement.args and (expr.kind == AstKind.FunctionCallExpression or expr.kind == AstKind.PassSelfFunctionCallExpression or expr.kind == AstKind.VarargExpression) then local reg = self:compileExpression(expr, funcDepth, self.RETURN_ALL)[1]; table.insert(args, Ast.FunctionCallExpression( - self:unpack(scope), - {self:register(scope, reg)})); + self:unpack(), + {self:register(reg)})); table.insert(regs, reg); else local reg = self:compileExpression(expr, funcDepth, 1)[1]; - local argExpr = self:register(scope, reg); + local argExpr = self:register(reg); table.insert(args, argExpr); table.insert(regs, reg); end end - self:addStatement(self:setRegister(scope, retReg, self:functionCall(scope, baseReg, args)), {retReg}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(retReg, self:functionCall(baseReg, args)), {retReg}, {baseReg, unpack(regs)}, true); self:freeRegister(baseReg, false); self:freeRegister(retReg, false); for _, reg in ipairs(regs) do diff --git a/src/prometheus/cff/statements/function_declaration.lua b/src/prometheus/cff/statements/function_declaration.lua index a6bf6c57..29588516 100644 --- a/src/prometheus/cff/statements/function_declaration.lua +++ b/src/prometheus/cff/statements/function_declaration.lua @@ -7,27 +7,26 @@ local Ast = require("prometheus.ast"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local retReg = self:compileFunction(statement, funcDepth); if(#statement.indices > 0) then local tblReg; - if statement.scope.isGlobal then + if not statement.symbol then tblReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tblReg, Ast.StringExpression(statement.scope:getVariableName(statement.id))), {tblReg}, {}, false); - self:addStatement(self:setRegister(scope, tblReg, Ast.IndexExpression(self:env(scope), self:register(scope, tblReg))), {tblReg}, {tblReg}, true); + self:addStatement(self:setRegister(tblReg, Ast.StringExpression(statement.name)), {tblReg}, {}, false); + self:addStatement(self:setRegister(tblReg, Ast.IndexExpression(self:env(), self:register(tblReg))), {tblReg}, {tblReg}, true); else if self.scopeFunctionDepths[statement.scope] == funcDepth then if self:isUpvalue(statement.scope, statement.id) then tblReg = self:allocRegister(false); local reg = self:getVarRegister(statement.scope, statement.id, funcDepth); - self:addStatement(self:setRegister(scope, tblReg, self:getUpvalueMember(scope, self:register(scope, reg))), {tblReg}, {reg}, true); + self:addStatement(self:setRegister(tblReg, self:getUpvalueMember(self:register(reg))), {tblReg}, {reg}, true); else tblReg = self:getVarRegister(statement.scope, statement.id, funcDepth, retReg); end else tblReg = self:allocRegister(false); local upvalId = self:getUpvalueId(statement.scope, statement.id); - self:addStatement(self:setRegister(scope, tblReg, self:getUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)))), {tblReg}, {}, true); + self:addStatement(self:setRegister(tblReg, self:getUpvalueMember(Ast.IndexExpression(Ast.VariableExpression(self.currentUpvaluesVar), Ast.NumberExpression(upvalId)))), {tblReg}, {}, true); end end @@ -36,7 +35,7 @@ return function(self, statement, funcDepth) local indexReg = self:compileExpression(Ast.StringExpression(index), funcDepth, 1)[1]; local tblRegOld = tblReg; tblReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tblReg, Ast.IndexExpression(self:register(scope, tblRegOld), self:register(scope, indexReg))), {tblReg}, {tblRegOld, indexReg}, false); + self:addStatement(self:setRegister(tblReg, Ast.IndexExpression(self:register(tblRegOld), self:register(indexReg))), {tblReg}, {tblRegOld, indexReg}, false); self:freeRegister(tblRegOld, false); self:freeRegister(indexReg, false); end @@ -44,9 +43,9 @@ return function(self, statement, funcDepth) local index = statement.indices[#statement.indices]; local indexReg = self:compileExpression(Ast.StringExpression(index), funcDepth, 1)[1]; self:addStatement(Ast.AssignmentStatement({ - Ast.AssignmentIndexing(self:register(scope, tblReg), self:register(scope, indexReg)), + Ast.AssignmentIndexing(self:register(tblReg), self:register(indexReg)), }, { - self:register(scope, retReg), + self:register(retReg), }), {}, {tblReg, indexReg, retReg}, true); self:freeRegister(indexReg, false); self:freeRegister(tblReg, false); @@ -54,26 +53,26 @@ return function(self, statement, funcDepth) return; end - if statement.scope.isGlobal then + if not statement.symbol then local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(statement.scope:getVariableName(statement.id))), {tmpReg}, {}, false); - self:addStatement(Ast.AssignmentStatement({Ast.AssignmentIndexing(self:env(scope), self:register(scope, tmpReg))}, - {self:register(scope, retReg)}), {}, {tmpReg, retReg}, true); + self:addStatement(self:setRegister(tmpReg, Ast.StringExpression(statement.name)), {tmpReg}, {}, false); + self:addStatement(Ast.AssignmentStatement({Ast.AssignmentIndexing(self:env(), self:register(tmpReg))}, + {self:register(retReg)}), {}, {tmpReg, retReg}, true); self:freeRegister(tmpReg, false); else if self.scopeFunctionDepths[statement.scope] == funcDepth then if self:isUpvalue(statement.scope, statement.id) then local reg = self:getVarRegister(statement.scope, statement.id, funcDepth); - self:addStatement(self:setUpvalueMember(scope, self:register(scope, reg), self:register(scope, retReg)), {}, {reg, retReg}, true); + self:addStatement(self:setUpvalueMember(self:register(reg), self:register(retReg)), {}, {reg, retReg}, true); else local reg = self:getVarRegister(statement.scope, statement.id, funcDepth, retReg); if reg ~= retReg then - self:addStatement(self:setRegister(scope, reg, self:register(scope, retReg)), {reg}, {retReg}, false); + self:addStatement(self:setRegister(reg, self:register(retReg)), {reg}, {retReg}, false); end end else local upvalId = self:getUpvalueId(statement.scope, statement.id); - self:addStatement(self:setUpvalueMember(scope, Ast.IndexExpression(Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(scope, retReg)), {}, {retReg}, true); + self:addStatement(self:setUpvalueMember(Ast.IndexExpression(Ast.VariableExpression(self.currentUpvaluesVar), Ast.NumberExpression(upvalId)), self:register(retReg)), {}, {retReg}, true); end end self:freeRegister(retReg, false); diff --git a/src/prometheus/cff/statements/if_statement.lua b/src/prometheus/cff/statements/if_statement.lua index 449aa435..1cb3e070 100644 --- a/src/prometheus/cff/statements/if_statement.lua +++ b/src/prometheus/cff/statements/if_statement.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local conditionReg = self:compileExpression(statement.condition, funcDepth, 1)[1]; local finalBlock = self:createBlock(); @@ -20,14 +19,13 @@ return function(self, statement, funcDepth) local innerBlock = self:createBlock(); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(scope, conditionReg), Ast.NumberExpression(innerBlock.id)), Ast.NumberExpression(nextBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(conditionReg), Ast.NumberExpression(innerBlock.id)), Ast.NumberExpression(nextBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); self:freeRegister(conditionReg, false); self:setActiveBlock(innerBlock); - scope = innerBlock.scope self:compileBlock(statement.body, funcDepth); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); for i, eif in ipairs(statement.elseifs) do self:setActiveBlock(nextBlock); @@ -38,23 +36,20 @@ return function(self, statement, funcDepth) else nextBlock = finalBlock; end - local scope = self.activeBlock.scope; - - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(scope, conditionReg), Ast.NumberExpression(innerBlock.id)), Ast.NumberExpression(nextBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(conditionReg), Ast.NumberExpression(innerBlock.id)), Ast.NumberExpression(nextBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); self:freeRegister(conditionReg, false); self:setActiveBlock(innerBlock); - scope = innerBlock.scope; self:compileBlock(eif.body, funcDepth); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); end if statement.elsebody then self:setActiveBlock(nextBlock); self:compileBlock(statement.elsebody, funcDepth); - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(finalBlock.id)), {self.POS_REGISTER}, {}, false); end self:setActiveBlock(finalBlock); diff --git a/src/prometheus/cff/statements/local_function_declaration.lua b/src/prometheus/cff/statements/local_function_declaration.lua index 4339ad67..2c62a690 100644 --- a/src/prometheus/cff/statements/local_function_declaration.lua +++ b/src/prometheus/cff/statements/local_function_declaration.lua @@ -7,19 +7,17 @@ local Ast = require("prometheus.ast"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; - if(self:isUpvalue(statement.scope, statement.id)) then local varReg = self:getVarRegister(statement.scope, statement.id, funcDepth, nil); - self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {varReg}, {}, false); + self:addStatement(self:setRegister(varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.allocUpvalFunction), {})), {varReg}, {}, false); local retReg = self:compileFunction(statement, funcDepth); - self:addStatement(self:setUpvalueMember(scope, self:register(scope, varReg), self:register(scope, retReg)), {}, {varReg, retReg}, true); + self:addStatement(self:setUpvalueMember(self:register(varReg), self:register(retReg)), {}, {varReg, retReg}, true); self:freeRegister(retReg, false); else local retReg = self:compileFunction(statement, funcDepth); local varReg = self:getVarRegister(statement.scope, statement.id, funcDepth, retReg); - self:addStatement(self:copyRegisters(scope, {varReg}, {retReg}), {varReg}, {retReg}, false); + self:addStatement(self:copyRegisters({varReg}, {retReg}), {varReg}, {retReg}, false); self:freeRegister(retReg, false); end end; diff --git a/src/prometheus/cff/statements/local_variable_declaration.lua b/src/prometheus/cff/statements/local_variable_declaration.lua index 168acded..7358e4ff 100644 --- a/src/prometheus/cff/statements/local_variable_declaration.lua +++ b/src/prometheus/cff/statements/local_variable_declaration.lua @@ -14,7 +14,6 @@ local function isSideEffectFreeLiteral(expression) end return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local exprregs = {}; for i, expr in ipairs(statement.expressions) do if(i == #statement.expressions and #statement.ids > #statement.expressions) then @@ -48,12 +47,12 @@ return function(self, statement, funcDepth) if(self:isUpvalue(statement.scope, id)) then local varreg = self:getVarRegister(statement.scope, id, funcDepth); local varReg = self:getVarRegister(statement.scope, id, funcDepth, nil); - self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.allocUpvalFunction), {})), {varReg}, {}, false); - self:addStatement(self:setUpvalueMember(scope, self:register(scope, varReg), self:register(scope, exprregs[i])), {}, {varReg, exprregs[i]}, true); + self:addStatement(self:setRegister(varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.allocUpvalFunction), {})), {varReg}, {}, false); + self:addStatement(self:setUpvalueMember(self:register(varReg), self:register(exprregs[i])), {}, {varReg, exprregs[i]}, true); self:freeRegister(exprregs[i], false); else local varreg = self:getVarRegister(statement.scope, id, funcDepth, exprregs[i]); - self:addStatement(self:copyRegisters(scope, {varreg}, {exprregs[i]}), {varreg}, {exprregs[i]}, false); + self:addStatement(self:copyRegisters({varreg}, {exprregs[i]}), {varreg}, {exprregs[i]}, false); self:freeRegister(exprregs[i], false); end end diff --git a/src/prometheus/cff/statements/pass_self_function_call.lua b/src/prometheus/cff/statements/pass_self_function_call.lua index aef4e928..fb7b2b53 100644 --- a/src/prometheus/cff/statements/pass_self_function_call.lua +++ b/src/prometheus/cff/statements/pass_self_function_call.lua @@ -8,29 +8,28 @@ local Ast = require("prometheus.ast"); local AstKind = Ast.AstKind; return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local baseReg = self:compileExpression(statement.base, funcDepth, 1)[1]; local tmpReg = self:allocRegister(false); - local args = { self:register(scope, baseReg) }; + local args = { self:register(baseReg) }; local regs = { baseReg }; for i, expr in ipairs(statement.args) do if i == #statement.args and (expr.kind == AstKind.FunctionCallExpression or expr.kind == AstKind.PassSelfFunctionCallExpression or expr.kind == AstKind.VarargExpression) then local reg = self:compileExpression(expr, funcDepth, self.RETURN_ALL)[1]; table.insert(args, Ast.FunctionCallExpression( - self:unpack(scope), - {self:register(scope, reg)})); + self:unpack(), + {self:register(reg)})); table.insert(regs, reg); else local reg = self:compileExpression(expr, funcDepth, 1)[1]; - table.insert(args, self:register(scope, reg)); + table.insert(args, self:register(reg)); table.insert(regs, reg); end end - self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(statement.passSelfFunctionName)), {tmpReg}, {}, false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {tmpReg, baseReg}, true); + self:addStatement(self:setRegister(tmpReg, Ast.StringExpression(statement.passSelfFunctionName)), {tmpReg}, {}, false); + self:addStatement(self:setRegister(tmpReg, Ast.IndexExpression(self:register(baseReg), self:register(tmpReg))), {tmpReg}, {tmpReg, baseReg}, true); - self:addStatement(self:setRegister(scope, tmpReg, self:functionCall(scope, tmpReg, args)), {tmpReg}, {tmpReg, unpack(regs)}, true); + self:addStatement(self:setRegister(tmpReg, self:functionCall(tmpReg, args)), {tmpReg}, {tmpReg, unpack(regs)}, true); self:freeRegister(tmpReg, false); for _, reg in ipairs(regs) do diff --git a/src/prometheus/cff/statements/repeat_statement.lua b/src/prometheus/cff/statements/repeat_statement.lua index 30313e05..e20d316a 100644 --- a/src/prometheus/cff/statements/repeat_statement.lua +++ b/src/prometheus/cff/statements/repeat_statement.lua @@ -7,30 +7,27 @@ local Ast = require("prometheus.ast"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local innerBlock = self:createBlock(); local finalBlock = self:createBlock(); statement.__start_block = innerBlock; statement.__final_block = finalBlock; - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(innerBlock.id)), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.NumberExpression(innerBlock.id)), {self.POS_REGISTER}, {}, false); self:setActiveBlock(innerBlock); for _, stat in ipairs(statement.body.statements) do self:compileStatement(stat, funcDepth); end; - - local scope = self.activeBlock.scope; local conditionReg = (self:compileExpression(statement.condition, funcDepth, 1))[1]; - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(scope, conditionReg), Ast.NumberExpression(finalBlock.id)), Ast.NumberExpression(innerBlock.id))), { self.POS_REGISTER }, { conditionReg }, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(conditionReg), Ast.NumberExpression(finalBlock.id)), Ast.NumberExpression(innerBlock.id))), { self.POS_REGISTER }, { conditionReg }, false); self:freeRegister(conditionReg, false); for id, _ in ipairs(statement.body.scope.variables) do local varReg = self:getVarRegister(statement.body.scope, id, funcDepth, nil); if self:isUpvalue(statement.body.scope, id) then - self:addStatement(self:setRegister(scope, varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.freeUpvalueFunc), { self:register(scope, varReg) })), { varReg }, { varReg }, false); + self:addStatement(self:setRegister(varReg, Ast.FunctionCallExpression(Ast.VariableExpression(self.freeUpvalueFunc), { self:register(varReg) })), { varReg }, { varReg }, false); else - self:addStatement(self:setRegister(scope, varReg, Ast.NilExpression()), { varReg }, {}, false); + self:addStatement(self:setRegister(varReg, Ast.NilExpression()), { varReg }, {}, false); end; self:freeRegister(varReg, true); end; diff --git a/src/prometheus/cff/statements/return.lua b/src/prometheus/cff/statements/return.lua index 392ff280..05cee338 100644 --- a/src/prometheus/cff/statements/return.lua +++ b/src/prometheus/cff/statements/return.lua @@ -8,7 +8,6 @@ local Ast = require("prometheus.ast"); local AstKind = Ast.AstKind; return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local entries = {}; local regs = {}; @@ -16,20 +15,20 @@ return function(self, statement, funcDepth) if i == #statement.args and (expr.kind == AstKind.FunctionCallExpression or expr.kind == AstKind.PassSelfFunctionCallExpression or expr.kind == AstKind.VarargExpression) then local reg = self:compileExpression(expr, funcDepth, self.RETURN_ALL)[1]; table.insert(entries, Ast.FunctionCallExpression( - self:unpack(scope), - {self:register(scope, reg)})); + self:unpack(), + {self:register(reg)})); table.insert(regs, reg); else local reg = self:compileExpression(expr, funcDepth, 1)[1]; - table.insert(entries, self:register(scope, reg)); + table.insert(entries, self:register(reg)); table.insert(regs, reg); end end - self:addStatement(self:setReturn(scope, self:pack(scope, entries)), {self.RETURN_REGISTER}, regs, false); + self:addStatement(self:setReturn(self:pack(entries)), {self.RETURN_REGISTER}, regs, false); for _, reg in ipairs(regs) do self:freeRegister(reg, false); end - self:addStatement(self:setPos(self.activeBlock.scope, nil), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setPos(nil), {self.POS_REGISTER}, {}, false); self.activeBlock.advanceToNextBlock = false; end; diff --git a/src/prometheus/cff/statements/while_statement.lua b/src/prometheus/cff/statements/while_statement.lua index 661645eb..79dbc438 100644 --- a/src/prometheus/cff/statements/while_statement.lua +++ b/src/prometheus/cff/statements/while_statement.lua @@ -7,7 +7,6 @@ local Ast = require("prometheus.ast"); return function(self, statement, funcDepth) - local scope = self.activeBlock.scope; local innerBlock = self:createBlock(); local finalBlock = self:createBlock(); local checkBlock = self:createBlock(); @@ -15,17 +14,15 @@ return function(self, statement, funcDepth) statement.__start_block = checkBlock; statement.__final_block = finalBlock; - self:addStatement(self:setPos(scope, checkBlock.id), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setPos(checkBlock.id), {self.POS_REGISTER}, {}, false); self:setActiveBlock(checkBlock); - scope = self.activeBlock.scope; local conditionReg = self:compileExpression(statement.condition, funcDepth, 1)[1]; - self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(scope, conditionReg), Ast.NumberExpression(innerBlock.id)), Ast.NumberExpression(finalBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); + self:addStatement(self:setRegister(self.POS_REGISTER, Ast.OrExpression(Ast.AndExpression(self:register(conditionReg), Ast.NumberExpression(innerBlock.id)), Ast.NumberExpression(finalBlock.id))), {self.POS_REGISTER}, {conditionReg}, false); self:freeRegister(conditionReg, false); self:setActiveBlock(innerBlock); - local scope = self.activeBlock.scope; self:compileBlock(statement.body, funcDepth); - self:addStatement(self:setPos(scope, checkBlock.id), {self.POS_REGISTER}, {}, false); + self:addStatement(self:setPos(checkBlock.id), {self.POS_REGISTER}, {}, false); self:setActiveBlock(finalBlock); end; diff --git a/src/prometheus/cff/strings.lua b/src/prometheus/cff/strings.lua index bc66c445..66b4937f 100644 --- a/src/prometheus/cff/strings.lua +++ b/src/prometheus/cff/strings.lua @@ -17,7 +17,7 @@ return function(ast) shuffle(strings) local scope = ast.body.scope - local pool = scope:addVariable() + local pool = Ast.LocalSymbol() local indices, entries = {}, {} for i, value in ipairs(strings) do indices[value] = i @@ -34,12 +34,12 @@ return function(ast) elseif node.kind == Kind.StringExpression then local index = indices[node.value] if index then - return Ast.IndexExpression(Ast.VariableExpression(scope, pool), Ast.NumberExpression(index)) + return Ast.IndexExpression(Ast.VariableExpression(pool), Ast.NumberExpression(index)) end end end) -- Insert only after replacement so each pooled value remains embedded once. table.insert(ast.body.statements, 1, - Ast.LocalVariableDeclaration(scope, {pool}, {Ast.TableConstructorExpression(entries)})) + Ast.LocalVariableDeclaration({pool}, {Ast.TableConstructorExpression(entries)})) return ast end diff --git a/src/prometheus/cff/upvalue.lua b/src/prometheus/cff/upvalue.lua index 43930415..b0a7ac87 100644 --- a/src/prometheus/cff/upvalue.lua +++ b/src/prometheus/cff/upvalue.lua @@ -5,159 +5,141 @@ -- This Script contains the upvalue and GC management for the compiler. local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); local util = require("prometheus.util"); local unpack = unpack or table.unpack; return function(Compiler) function Compiler:createUpvaluesGcFunc() - local scope = Scope:new(self.scope); - local selfVar = scope:addVariable(); + local selfVar = Ast.LocalSymbol(); - local iteratorVar = scope:addVariable(); - local valueVar = scope:addVariable(); - - local whileScope = Scope:new(scope); - - local ifScope = Scope:new(whileScope); - - return Ast.FunctionLiteralExpression({Ast.VariableExpression(scope, selfVar)}, Ast.Block({ - Ast.LocalVariableDeclaration(scope, {iteratorVar, valueVar}, {Ast.NumberExpression(1), Ast.IndexExpression(Ast.VariableExpression(scope, selfVar), Ast.NumberExpression(1))}), + local iteratorVar = Ast.LocalSymbol(); + local valueVar = Ast.LocalSymbol(); + return Ast.FunctionLiteralExpression({Ast.VariableExpression(selfVar)}, Ast.Block({ + Ast.LocalVariableDeclaration({iteratorVar, valueVar}, {Ast.NumberExpression(1), Ast.IndexExpression(Ast.VariableExpression(selfVar), Ast.NumberExpression(1))}), Ast.WhileStatement(Ast.Block({ Ast.AssignmentStatement({ - Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, valueVar)), - Ast.AssignmentVariable(scope, iteratorVar), + Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(valueVar)), + Ast.AssignmentVariable(iteratorVar), }, { - Ast.SubExpression(Ast.IndexExpression(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, valueVar)), Ast.NumberExpression(1)), - Ast.AddExpression(unpack(util.shuffle{Ast.VariableExpression(scope, iteratorVar), Ast.NumberExpression(1)})), + Ast.SubExpression(Ast.IndexExpression(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(valueVar)), Ast.NumberExpression(1)), + Ast.AddExpression(unpack(util.shuffle{Ast.VariableExpression(iteratorVar), Ast.NumberExpression(1)})), }), - Ast.IfStatement(Ast.EqualsExpression(unpack(util.shuffle{Ast.IndexExpression(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, valueVar)), Ast.NumberExpression(0)})), Ast.Block({ + Ast.IfStatement(Ast.EqualsExpression(unpack(util.shuffle{Ast.IndexExpression(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(valueVar)), Ast.NumberExpression(0)})), Ast.Block({ Ast.AssignmentStatement({ - Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, valueVar)), - Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesTable), Ast.VariableExpression(scope, valueVar)), + Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(valueVar)), + Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesTable), Ast.VariableExpression(valueVar)), }, { Ast.NilExpression(), Ast.NilExpression(), }) - }, ifScope), {}, nil), + }), {}, nil), Ast.AssignmentStatement({ - Ast.AssignmentVariable(scope, valueVar), + Ast.AssignmentVariable(valueVar), }, { - Ast.IndexExpression(Ast.VariableExpression(scope, selfVar), Ast.VariableExpression(scope, iteratorVar)), + Ast.IndexExpression(Ast.VariableExpression(selfVar), Ast.VariableExpression(iteratorVar)), }), - }, whileScope), Ast.VariableExpression(scope, valueVar), scope); - }, scope)); + }), Ast.VariableExpression(valueVar)); + })); end function Compiler:createFreeUpvalueFunc() - local scope = Scope:new(self.scope); - local argVar = scope:addVariable(); - local ifScope = Scope:new(scope); - return Ast.FunctionLiteralExpression({Ast.VariableExpression(scope, argVar)}, Ast.Block({ + local argVar = Ast.LocalSymbol(); + return Ast.FunctionLiteralExpression({Ast.VariableExpression(argVar)}, Ast.Block({ Ast.AssignmentStatement({ - Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, argVar)) + Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(argVar)) }, { - Ast.SubExpression(Ast.IndexExpression(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, argVar)), Ast.NumberExpression(1)); + Ast.SubExpression(Ast.IndexExpression(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(argVar)), Ast.NumberExpression(1)); }), - Ast.IfStatement(Ast.EqualsExpression(unpack(util.shuffle{Ast.IndexExpression(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, argVar)), Ast.NumberExpression(0)})), Ast.Block({ + Ast.IfStatement(Ast.EqualsExpression(unpack(util.shuffle{Ast.IndexExpression(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(argVar)), Ast.NumberExpression(0)})), Ast.Block({ Ast.AssignmentStatement({ - Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(scope, argVar)), - Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesTable), Ast.VariableExpression(scope, argVar)), + Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(argVar)), + Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesTable), Ast.VariableExpression(argVar)), }, { Ast.NilExpression(), Ast.NilExpression(), }) - }, ifScope), {}, nil) - }, scope)) + }), {}, nil) + })) end function Compiler:createUpvaluesProxyFunc() - local scope = Scope:new(self.scope); - - local entriesVar = scope:addVariable(); + local entriesVar = Ast.LocalSymbol(); + local proxyVar = Ast.LocalSymbol(); + local metatableVar = Ast.LocalSymbol(); + local forArg = Ast.LocalSymbol(); - local ifScope = Scope:new(scope); - local proxyVar = ifScope:addVariable(); - local metatableVar = ifScope:addVariable(); - local elseScope = Scope:new(scope); - - local forScope = Scope:new(scope); - local forArg = forScope:addVariable(); - - return Ast.FunctionLiteralExpression({Ast.VariableExpression(scope, entriesVar)}, Ast.Block({ - Ast.ForStatement(forScope, forArg, Ast.NumberExpression(1), Ast.LenExpression(Ast.VariableExpression(scope, entriesVar)), Ast.NumberExpression(1), Ast.Block({ + return Ast.FunctionLiteralExpression({Ast.VariableExpression(entriesVar)}, Ast.Block({ + Ast.ForStatement(forArg, Ast.NumberExpression(1), Ast.LenExpression(Ast.VariableExpression(entriesVar)), Ast.NumberExpression(1), Ast.Block({ Ast.AssignmentStatement({ - Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.IndexExpression(Ast.VariableExpression(scope, entriesVar), Ast.VariableExpression(forScope, forArg))) + Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.IndexExpression(Ast.VariableExpression(entriesVar), Ast.VariableExpression(forArg))) }, { Ast.AddExpression(unpack(util.shuffle{ - Ast.IndexExpression(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.IndexExpression(Ast.VariableExpression(scope, entriesVar), Ast.VariableExpression(forScope, forArg))), + Ast.IndexExpression(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.IndexExpression(Ast.VariableExpression(entriesVar), Ast.VariableExpression(forArg))), Ast.NumberExpression(1), })) }) - }, forScope), scope); - Ast.IfStatement(Ast.VariableExpression(self.scope, self.newproxyVar), Ast.Block({ - Ast.LocalVariableDeclaration(ifScope, {proxyVar}, { - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.newproxyVar), { + })); + Ast.IfStatement(Ast.VariableExpression(self.newproxyVar), Ast.Block({ + Ast.LocalVariableDeclaration({proxyVar}, { + Ast.FunctionCallExpression(Ast.VariableExpression(self.newproxyVar), { Ast.BooleanExpression(true) }); }); - Ast.LocalVariableDeclaration(ifScope, {metatableVar}, { - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.getmetatableVar), { - Ast.VariableExpression(ifScope, proxyVar); + Ast.LocalVariableDeclaration({metatableVar}, { + Ast.FunctionCallExpression(Ast.VariableExpression(self.getmetatableVar), { + Ast.VariableExpression(proxyVar); }); }); Ast.AssignmentStatement({ - Ast.AssignmentIndexing(Ast.VariableExpression(ifScope, metatableVar), Ast.StringExpression("__index")), - Ast.AssignmentIndexing(Ast.VariableExpression(ifScope, metatableVar), Ast.StringExpression("__gc")), - Ast.AssignmentIndexing(Ast.VariableExpression(ifScope, metatableVar), Ast.StringExpression("__len")), + Ast.AssignmentIndexing(Ast.VariableExpression(metatableVar), Ast.StringExpression("__index")), + Ast.AssignmentIndexing(Ast.VariableExpression(metatableVar), Ast.StringExpression("__gc")), + Ast.AssignmentIndexing(Ast.VariableExpression(metatableVar), Ast.StringExpression("__len")), }, { - Ast.VariableExpression(scope, entriesVar), - Ast.VariableExpression(self.scope, self.upvaluesGcFunctionVar), + Ast.VariableExpression(entriesVar), + Ast.VariableExpression(self.upvaluesGcFunctionVar), Ast.FunctionLiteralExpression({}, Ast.Block({ Ast.ReturnStatement({Ast.NumberExpression(self.upvalsProxyLenReturn)}) - }, Scope:new(ifScope))); + })); }); Ast.ReturnStatement({ - Ast.VariableExpression(ifScope, proxyVar) + Ast.VariableExpression(proxyVar) }) - }, ifScope), {}, Ast.Block({ - Ast.ReturnStatement({Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.setmetatableVar), { + }), {}, Ast.Block({ + Ast.ReturnStatement({Ast.FunctionCallExpression(Ast.VariableExpression(self.setmetatableVar), { Ast.TableConstructorExpression({}), Ast.TableConstructorExpression({ - Ast.KeyedTableEntry(Ast.StringExpression("__gc"), Ast.VariableExpression(self.scope, self.upvaluesGcFunctionVar)), - Ast.KeyedTableEntry(Ast.StringExpression("__index"), Ast.VariableExpression(scope, entriesVar)), + Ast.KeyedTableEntry(Ast.StringExpression("__gc"), Ast.VariableExpression(self.upvaluesGcFunctionVar)), + Ast.KeyedTableEntry(Ast.StringExpression("__index"), Ast.VariableExpression(entriesVar)), Ast.KeyedTableEntry(Ast.StringExpression("__len"), Ast.FunctionLiteralExpression({}, Ast.Block({ Ast.ReturnStatement({Ast.NumberExpression(self.upvalsProxyLenReturn)}) - }, Scope:new(ifScope)))), + }))), }) })}) - }, elseScope)); - }, scope)); + })); + })); end function Compiler:createAllocUpvalFunction() - local scope = Scope:new(self.scope); - return Ast.FunctionLiteralExpression({}, Ast.Block({ Ast.AssignmentStatement({ - Ast.AssignmentVariable(self.scope, self.currentUpvalId), + Ast.AssignmentVariable(self.currentUpvalId), },{ Ast.AddExpression(unpack(util.shuffle({ - Ast.VariableExpression(self.scope, self.currentUpvalId), + Ast.VariableExpression(self.currentUpvalId), Ast.NumberExpression(1), }))), } ), Ast.AssignmentStatement({ - Ast.AssignmentIndexing(Ast.VariableExpression(self.scope, self.upvaluesReferenceCountsTable), Ast.VariableExpression(self.scope, self.currentUpvalId)), + Ast.AssignmentIndexing(Ast.VariableExpression(self.upvaluesReferenceCountsTable), Ast.VariableExpression(self.currentUpvalId)), }, { Ast.NumberExpression(1), }), Ast.ReturnStatement({ - Ast.VariableExpression(self.scope, self.currentUpvalId), + Ast.VariableExpression(self.currentUpvalId), }) - }, scope)); + })); end end diff --git a/src/prometheus/names.lua b/src/prometheus/names.lua new file mode 100644 index 00000000..66b1cca8 --- /dev/null +++ b/src/prometheus/names.lua @@ -0,0 +1,30 @@ +-- Allocate spellings from lexical analysis while preserving symbol identities. +local function renameScope(scope, settings) + if not scope.isGlobal then + local forbidden = {} + for _, keyword in pairs(settings.Keywords) do forbidden[keyword] = true end + for outer, ids in pairs(scope.variablesFromHigherScopes) do + for id, count in pairs(ids) do + if count > 0 then forbidden[outer.variables[id]] = true end + end + end + scope.variablesLookup = {} + local index, prefix = 0, settings.prefix or '' + for id, symbol in ipairs(scope.symbols) do + local original, name = symbol.name + repeat + local generated = settings.generateName(index, scope, original) + name = prefix .. (generated or original) + index = index + 1 + until not forbidden[name] + symbol.name, scope.variables[id] = name, name + scope.variablesLookup[name], forbidden[name] = id, true + end + end + for _, child in ipairs(scope.children) do renameScope(child, settings) end +end + +return function(ast, settings) + assert(ast.bindingAnalysis, 'Name allocation requires binding analysis') + renameScope(ast.globalScope, settings) +end diff --git a/src/prometheus/parser.lua b/src/prometheus/parser.lua index df9c0a02..bf65bbd7 100644 --- a/src/prometheus/parser.lua +++ b/src/prometheus/parser.lua @@ -17,7 +17,6 @@ local Enums = require("prometheus.enums"); local util = require("prometheus.util"); local Ast = require("prometheus.ast"); local analyzeBindings = require("prometheus.bindinganalysis"); -local Scope = require("prometheus.scope"); local logger = require("logger"); local AstKind = Ast.AstKind; @@ -27,6 +26,33 @@ local lookupify = util.lookupify; local TokenKind = Tokenizer.TokenKind; +-- Transient parser name environments never become part of the AST. +local Bindings = {}; +Bindings.__index = Bindings; +function Bindings:new(parent) + return setmetatable({parent = parent, names = {}}, self); +end +function Bindings:create(name, token) + if self.names[name] and token then + logger:warn("Warning at Position " .. tostring(token.line) .. ":" .. tostring(token.linePos) + .. ', the variable "' .. name .. '" is already defined in that scope'); + end + return Ast.LocalSymbol(name); +end +function Bindings:enable(symbol) + self.names[symbol.name] = symbol; +end +function Bindings:declare(name, token) + local symbol = self:create(name, token); + self:enable(symbol); + return symbol; +end +function Bindings:resolve(name) + local symbol = self.names[name]; + if not symbol and self.parent then return self.parent:resolve(name); end + return symbol and Ast.VariableExpression(symbol) or Ast.GlobalVariableExpression(name); +end + local Parser = {}; local ASSIGNMENT_NO_WARN_LOOKUP = lookupify{ @@ -150,10 +176,10 @@ function Parser:parse(code) self.tokens = self.tokenizer:scanAll(); self.length = #self.tokens; - -- Create Global Variable Scope - local globalScope = Scope:newGlobal(); + -- Begin transient name lookup at the chunk boundary + local globalScope = Bindings:new(); - local ast = Ast.TopNode(self:block(globalScope, false), globalScope); + local ast = Ast.TopNode(self:block(globalScope, false)); -- File Must be Over when Top Node is Fully Parsed expect(self, TokenKind.Eof); @@ -173,7 +199,7 @@ end -- Parse a Code Block function Parser:block(parentScope, currentLoop, scope) - scope = scope or Scope:new(parentScope); + scope = scope or Bindings:new(parentScope); local statements = {}; repeat @@ -184,7 +210,7 @@ function Parser:block(parentScope, currentLoop, scope) -- Consume Eventual Semicolon after terminating return, break or continue consume(self, TokenKind.Symbol, ";"); - return Ast.Block(statements, scope); + return Ast.Block(statements); end function Parser:statement(scope, currentLoop) @@ -201,7 +227,7 @@ function Parser:statement(scope, currentLoop) logger:error(generateError(self, "the break Statement is only valid inside of loops")); end -- Return true as Second value because break must be the last Statement in a block - return Ast.BreakStatement(currentLoop, scope), true; + return Ast.BreakStatement(currentLoop), true; end -- Continue Statement - only valid inside of Loops - only valid in LuaU @@ -211,7 +237,7 @@ function Parser:statement(scope, currentLoop) logger:error(generateError(self, "the continue Statement is only valid inside of loops")); end -- Return true as Second value because continue must be the last Statement in a block - return Ast.ContinueStatement(currentLoop, scope), true; + return Ast.ContinueStatement(currentLoop), true; end -- do ... end Statement @@ -225,7 +251,7 @@ function Parser:statement(scope, currentLoop) if(consume(self, TokenKind.Keyword, "while")) then local condition = self:expression(scope); expect(self, TokenKind.Keyword, "do"); - local stat = Ast.WhileStatement(nil, condition, scope); + local stat = Ast.WhileStatement(nil, condition); stat.body = self:block(scope, stat); expect(self, TokenKind.Keyword, "end"); return stat; @@ -233,8 +259,8 @@ function Parser:statement(scope, currentLoop) -- Repeat Statement if(consume(self, TokenKind.Keyword, "repeat")) then - local repeatScope = Scope:new(scope); - local stat = Ast.RepeatStatement(nil, nil, scope); + local repeatScope = Bindings:new(scope); + local stat = Ast.RepeatStatement(nil, nil); stat.body = self:block(nil, stat, repeatScope); expect(self, TokenKind.Keyword, "until"); stat.condition = self:expression(repeatScope); @@ -285,25 +311,23 @@ function Parser:statement(scope, currentLoop) if(consume(self, TokenKind.Keyword, "function")) then -- TODO: Parse Function Declaration Name local obj = self:funcName(scope); - local baseScope = obj.scope; - local baseId = obj.id; local indices = obj.indices; - local funcScope = Scope:new(scope); + local funcScope = Bindings:new(scope); expect(self, TokenKind.Symbol, "("); local args = self:functionArgList(funcScope); expect(self, TokenKind.Symbol, ")"); if(obj.passSelf) then - local id = funcScope:addVariable("self", obj.token); - table.insert(args, 1, Ast.VariableExpression(funcScope, id)); + local symbol = funcScope:declare("self", obj.token); + table.insert(args, 1, Ast.VariableExpression(symbol)); end local body = self:block(nil, false, funcScope); expect(self, TokenKind.Keyword, "end"); - return Ast.FunctionDeclaration(baseScope, baseId, indices, args, body); + return Ast.FunctionDeclaration(obj.target, indices, args, body); end -- Local Function or Variable Declaration @@ -313,8 +337,8 @@ function Parser:statement(scope, currentLoop) local ident = expect(self, TokenKind.Ident); local name = ident.value; - local id = scope:addVariable(name, ident); - local funcScope = Scope:new(scope); + local symbol = scope:declare(name, ident); + local funcScope = Bindings:new(scope); expect(self, TokenKind.Symbol, "("); local args = self:functionArgList(funcScope); @@ -323,36 +347,36 @@ function Parser:statement(scope, currentLoop) local body = self:block(nil, false, funcScope); expect(self, TokenKind.Keyword, "end"); - return Ast.LocalFunctionDeclaration(scope, id, args, body); + return Ast.LocalFunctionDeclaration(symbol, args, body); end -- Local Variable Declaration - local ids = self:nameList(scope); + local symbols = self:nameList(scope); local expressions = {}; if(consume(self, TokenKind.Symbol, "=")) then expressions = self:exprList(scope); end - -- Variables can only be reffered to in the next statement, so the id's are enabled after the expressions have been parsed - self:enableNameList(scope, ids); + -- Variables can only be reffered to in the next statement, so the symbol's are enabled after the expressions have been parsed + self:enableNameList(scope, symbols); - if(#expressions > #ids) then - logger:warn(generateWarning(peek(self, -1), string.format("assigning %d values to %d variable" .. ((#ids > 1 and "s") or ""), #expressions, #ids))); - elseif(#ids > #expressions and #expressions > 0 and not ASSIGNMENT_NO_WARN_LOOKUP[expressions[#expressions].kind]) then + if(#expressions > #symbols) then + logger:warn(generateWarning(peek(self, -1), string.format("assigning %d values to %d variable" .. ((#symbols > 1 and "s") or ""), #expressions, #symbols))); + elseif(#symbols > #expressions and #expressions > 0 and not ASSIGNMENT_NO_WARN_LOOKUP[expressions[#expressions].kind]) then logger:warn(generateWarning(peek(self, -1), string.format("assigning %d value" .. ((#expressions > 1 and "s") or "") .. - " to %d variables initializes extra variables with nil, add a nil value to silence", #expressions, #ids))); + " to %d variables initializes extra variables with nil, add a nil value to silence", #expressions, #symbols))); end - return Ast.LocalVariableDeclaration(scope, ids, expressions); + return Ast.LocalVariableDeclaration(symbols, expressions); end -- For Statement if(consume(self, TokenKind.Keyword, "for")) then -- Normal for Statement if(is(self, TokenKind.Symbol, "=", 1)) then - local forScope = Scope:new(scope); + local forScope = Bindings:new(scope); local ident = expect(self, TokenKind.Ident); - local varId = forScope:addDisabledVariable(ident.value, ident); + local symbol = forScope:create(ident.value, ident); expect(self, TokenKind.Symbol, "="); local initialValue = self:expression(scope); @@ -364,8 +388,8 @@ function Parser:statement(scope, currentLoop) incrementBy = self:expression(scope); end - local stat = Ast.ForStatement(forScope, varId, initialValue, finalValue, incrementBy, nil, scope); - forScope:enableVariable(varId); + local stat = Ast.ForStatement(symbol, initialValue, finalValue, incrementBy, nil); + forScope:enable(symbol); expect(self, TokenKind.Keyword, "do"); stat.body = self:block(nil, stat, forScope); expect(self, TokenKind.Keyword, "end"); @@ -373,9 +397,9 @@ function Parser:statement(scope, currentLoop) end -- For ... in ... statement - local forScope = Scope:new(scope); + local forScope = Bindings:new(scope); - local ids = self:nameList(forScope); + local symbols = self:nameList(forScope); expect(self, TokenKind.Keyword, "in"); local expressions = self:exprList(scope); -- Enable Ids after Expression Parsing so that code like this works: @@ -383,9 +407,9 @@ function Parser:statement(scope, currentLoop) -- for y,z in ipairs(z) do -- print(y, z); -- end - self:enableNameList(forScope, ids); + self:enableNameList(forScope, symbols); expect(self, TokenKind.Keyword, "do"); - local stat = Ast.ForInStatement(forScope, ids, expressions, nil, scope); + local stat = Ast.ForInStatement(symbols, expressions, nil); stat.body = self:block(nil, stat, forScope); expect(self, TokenKind.Keyword, "end"); @@ -523,24 +547,24 @@ end -- list of local variable names function Parser:nameList(scope) - local ids = {}; + local symbols = {}; local ident = expect(self, TokenKind.Ident); - local id = scope:addDisabledVariable(ident.value, ident); - table.insert(ids, id); + local symbol = scope:create(ident.value, ident); + table.insert(symbols, symbol); while(consume(self, TokenKind.Symbol, ",")) do ident = expect(self, TokenKind.Ident); - id = scope:addDisabledVariable(ident.value, ident); - table.insert(ids, id); + symbol = scope:create(ident.value, ident); + table.insert(symbols, symbol); end - return ids; + return symbols; end function Parser:enableNameList(scope, list) - for i, id in ipairs(list) do - scope:enableVariable(id); + for i, symbol in ipairs(list) do + scope:enable(symbol); end end @@ -550,7 +574,7 @@ function Parser:funcName(scope) local ident = expect(self, TokenKind.Ident); local baseName = ident.value; - local baseScope, baseId = scope:resolve(baseName); + local target = scope:resolve(baseName); local indices = {}; local passSelf = false; @@ -564,8 +588,7 @@ function Parser:funcName(scope) end return { - scope = baseScope, - id = baseId, + target = target, indices = indices, passSelf = passSelf, token = ident, @@ -749,7 +772,7 @@ function Parser:tableOrFunctionLiteral(scope) end function Parser:expressionFunctionLiteral(parentScope) - local scope = Scope:new(parentScope); + local scope = Bindings:new(parentScope); expect(self, TokenKind.Keyword, "function"); @@ -774,8 +797,8 @@ function Parser:functionArgList(scope) local ident = get(self); local name = ident.value; - local id = scope:addVariable(name, ident); - table.insert(args, Ast.VariableExpression(scope, id)); + local symbol = scope:declare(name, ident); + table.insert(args, Ast.VariableExpression(symbol)); while(consume(self, TokenKind.Symbol, ",")) do if(consume(self, TokenKind.Symbol, "...")) then @@ -786,8 +809,8 @@ function Parser:functionArgList(scope) ident = get(self); name = ident.value; - id = scope:addVariable(name, ident); - table.insert(args, Ast.VariableExpression(scope, id)); + symbol = scope:declare(name, ident); + table.insert(args, Ast.VariableExpression(symbol)); end end @@ -946,8 +969,7 @@ function Parser:expressionLiteral(scope) local ident = get(self); local name = ident.value; - local scope, id = scope:resolve(name); - return Ast.VariableExpression(scope, id); + return scope:resolve(name); end -- IfElse diff --git a/src/prometheus/pipeline.lua b/src/prometheus/pipeline.lua index ab4d174c..088a9cbe 100644 --- a/src/prometheus/pipeline.lua +++ b/src/prometheus/pipeline.lua @@ -9,6 +9,7 @@ local Enums = require("prometheus.enums"); local util = require("prometheus.util"); local Parser = require("prometheus.parser"); local analyzeBindings = require("prometheus.bindinganalysis"); +local renameVariables = require("prometheus.names"); local Unparser = require("prometheus.unparser"); local logger = require("logger"); @@ -257,8 +258,7 @@ function Pipeline:renameVariables(ast) logger:error(string.format("The Prefix \"%s\" is not a valid Identifier in %s", self.VarNamePrefix, self.LuaVersion)); end - local globalScope = ast.globalScope; - globalScope:renameVariables({ + renameVariables(ast, { Keywords = self.conventions.Keywords; generateName = generatorFunction; prefix = self.VarNamePrefix; diff --git a/src/prometheus/scope.lua b/src/prometheus/scope.lua deleted file mode 100644 index 4eda4742..00000000 --- a/src/prometheus/scope.lua +++ /dev/null @@ -1,245 +0,0 @@ --- This Script is Part of the Prometheus Obfuscator by levno-710 --- --- scope.lua --- --- Construction-time name resolution and derived lexical metadata. --- Binding analysis owns scope ancestry, reference counts, and capture metadata. - -local logger = require("logger"); -local config = require("config"); - -local Scope = {}; - -local scopeI = 0; -local function nextName() - scopeI = scopeI + 1; - return "local_scope_" .. tostring(scopeI); -end - -local function generateWarning(token, message) - return "Warning at Position " .. tostring(token.line) .. ":" .. tostring(token.linePos) .. ", " .. message; -end - --- Create a new Local Scope -function Scope:new(parentScope, name) - local scope = { - isGlobal = false, - parentScope = parentScope, - variables = {}, - symbols = {}, - referenceCounts = {}; - variablesLookup = {}, - variablesFromHigherScopes = {}, - skipIdLookup = {}; - name = name or nextName(), - children = {}, - level = parentScope and parentScope.level and (parentScope.level + 1) or 1; - } - - setmetatable(scope, self); - self.__index = self; - return scope; -end - --- Create a new Global Scope -function Scope:newGlobal() - local scope = { - isGlobal = true, - parentScope = nil, - variables = {}, - symbols = {}, - variablesLookup = {}; - referenceCounts = {}; - skipIdLookup = {}; - name = "global_scope", - children = {}, - level = 0, - }; - - setmetatable(scope, self); - self.__index = self; - - return scope; -end - --- Returns the Parent Scope -function Scope:getParent(parentScope) - return self.parentScope; -end - -local next_name_i = 1; --- Adds a Variable to the scope and returns the variable id, if no name is passed then a name is generated -function Scope:addVariable(name, token) - if (not name) then - name = string.format("%s%i", config.IdentPrefix, next_name_i); - next_name_i = next_name_i + 1; - end - - if self.variablesLookup[name] ~= nil then - if(token) then - logger:warn(generateWarning(token, "the variable \"" .. name .. "\" is already defined in that scope")); - else - logger:error(string.format("A variable with the name \"%s\" was already defined, you should have no variables starting with \"%s\"", name, config.IdentPrefix)); - end - - --return self.variablesLookup[name]; - end - - table.insert(self.variables, name); - local id = #self.variables; - self.variablesLookup[name] = id; - return id; -end - -function Scope:enableVariable(id) - local name = self.variables[id]; - self.variablesLookup[name] = id; -end - -function Scope:addDisabledVariable(name, token) - if (not name) then - name = string.format("%s%i", config.IdentPrefix, next_name_i); - next_name_i = next_name_i + 1; - end - - if self.variablesLookup[name] ~= nil then - if(token) then - logger:warn(generateWarning(token, "the variable \"" .. name .. "\" is already defined in that scope")); - else - logger:warn(string.format("a variable with the name \"%s\" was already defined", name)); - end - - --return self.variablesLookup[name]; - end - - table.insert(self.variables, name); - local id = #self.variables; - return id; -end - -function Scope:addIfNotExists(id) - if(not self.variables[id]) then - local name = string.format("%s%i", config.IdentPrefix, next_name_i); - next_name_i = next_name_i + 1; - self.variables[id] = name; - self.variablesLookup[name] = id; - end - return id; -end - --- Returns wether the variable is defined in this Scope -function Scope:hasVariable(name) - if(self.isGlobal) then - if self.variablesLookup[name] == nil then - self:addVariable(name); - end - return true; - end - return self.variablesLookup[name] ~= nil; -end - --- Get List of all Variables defined in this Scope -function Scope:getVariables() - return self.variables; -end - -function Scope:getReferences(id) - return self.referenceCounts[id] or 0; -end - --- Resolve the scope of a variable by name -function Scope:resolve(name) - if(self:hasVariable(name)) then - return self, self.variablesLookup[name]; - end - assert(self.parentScope, "No Global Variable Scope was Created! This should not be Possible!"); - local scope, id = self.parentScope:resolve(name); - return scope, id; -end - -function Scope:resolveGlobal(name) - if(self.isGlobal and self:hasVariable(name)) then - return self, self.variablesLookup[name]; - end - assert(self.parentScope, "No Global Variable Scope was Created! This should not be Possible!"); - local scope, id = self.parentScope:resolveGlobal(name); - return scope, id; -end - --- Construction adapter: stable identities survive scope reconstruction. -function Scope:getSymbol(id) - assert(not self.isGlobal, "Globals do not have local symbols"); - assert(self.variables[id], "Unknown local variable id: " .. tostring(id)); - if not self.symbols[id] then - self.symbols[id] = require("prometheus.ast").LocalSymbol(self.variables[id]); - self.symbols[id].scope, self.symbols[id].id = self, id; - end - return self.symbols[id]; -end - --- Returns the name of an Variable by id - this is used for unparsing -function Scope:getVariableName(id) - return self.variables[id]; -end - --- Remove A Variable from this Scope -function Scope:removeVariable(id) - local name = self.variables[id]; - self.variables[id] = nil; - self.variablesLookup[name] = nil; - self.skipIdLookup[id] = true; -end - -function Scope:getMaxId() - return #self.variables; -end - --- Rename Variables from that scope downwards --- this function needs a settings object with the following properties --- Keywords => forbidden Variable Names --- generateName(id, scope, originalName) => function to generate unique variable name based on the id and scope -function Scope:renameVariables(settings) - if(not self.isGlobal) then - local prefix = settings.prefix or ""; - local forbiddenNamesLookup = {}; - for _, keyword in pairs(settings.Keywords) do - forbiddenNamesLookup[keyword] = true; - end - - for scope, ids in pairs(self.variablesFromHigherScopes) do - for id, count in pairs(ids) do - if count and count > 0 then - local name = scope:getVariableName(id); - forbiddenNamesLookup[name] = true; - end - end - end - - self.variablesLookup = {}; - - local i = 0; - for id, originalName in pairs(self.variables) do - if(not self.skipIdLookup[id] and (self.referenceCounts[id] or 0) >= 0) then - local name; - repeat - name = prefix .. settings.generateName(i, self, originalName); - if name == nil then - name = originalName; - end - i = i + 1; - until not forbiddenNamesLookup[name]; - - self.variables[id] = name; - if self.symbols[id] then self.symbols[id].name = name; end - forbiddenNamesLookup[name] = true; - self.variablesLookup[name] = id; - end - end - end - - for _, scope in pairs(self.children) do - scope:renameVariables(settings); - end -end - -return Scope; \ No newline at end of file diff --git a/src/prometheus/steps/ConstantArray.lua b/src/prometheus/steps/ConstantArray.lua index 200f8ddd..35e34ea6 100644 --- a/src/prometheus/steps/ConstantArray.lua +++ b/src/prometheus/steps/ConstantArray.lua @@ -9,7 +9,6 @@ local Step = require("prometheus.step"); local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); local visitast = require("prometheus.visitast"); local util = require("prometheus.util") local Parser = require("prometheus.parser"); @@ -142,11 +141,11 @@ function ConstantArray:indexing(index, data) end return Ast.FunctionCallExpression(Ast.IndexExpression( - Ast.VariableExpression(wrappers.scope, wrappers.id), + Ast.VariableExpression(wrappers.symbol), Ast.StringExpression(wrapper.index) ), args); else - return Ast.FunctionCallExpression(Ast.VariableExpression(self.rootScope, self.wrapperId), { + return Ast.FunctionCallExpression(Ast.VariableExpression(self.wrapperSymbol), { Ast.NumberExpression(index - self.wrapperOffset); }); end @@ -202,9 +201,11 @@ function ConstantArray:addRotateCode(ast, shift) local newAst = parser:parse(string.gsub(string.gsub(rotateCode, "SHIFT", tostring(shift)), "LEN", tostring(#self.constants))); local forStat = newAst.body.statements[1]; visitast(newAst, nil, function(node, data) - if(node.kind == AstKind.VariableExpression or node.kind == AstKind.GlobalVariableExpression) then - if(node.scope:getVariableName(node.id) == "ARR") then - return Ast.VariableExpression(self.rootScope, self.arrId); + if node.name == "ARR" then + if node.kind == AstKind.AssignmentGlobalVariable then + return Ast.AssignmentVariable(self.arrSymbol); + elseif node.kind == AstKind.GlobalVariableExpression then + return Ast.VariableExpression(self.arrSymbol); end end end) @@ -271,12 +272,12 @@ function ConstantArray:addDecodeCode(ast) local forStat = newAst.body.statements[1]; visitast(newAst, nil, function(node, data) - if(node.kind == AstKind.VariableExpression or node.kind == AstKind.GlobalVariableExpression) then - if(node.scope:getVariableName(node.id) == "ARR") then - return Ast.VariableExpression(self.rootScope, self.arrId); + if(node.kind == AstKind.GlobalVariableExpression or node.kind == AstKind.AssignmentGlobalVariable) then + if(node.name == "ARR") then + return node.kind == AstKind.AssignmentGlobalVariable and Ast.AssignmentVariable(self.arrSymbol) or Ast.VariableExpression(self.arrSymbol); end - if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE") then + if(node.name == "LOOKUP_TABLE") then return self:createBase64Lookup(); end end @@ -355,12 +356,12 @@ function ConstantArray:addDecodeCode(ast) local forStat = newAst.body.statements[1]; visitast(newAst, nil, function(node, data) - if(node.kind == AstKind.VariableExpression or node.kind == AstKind.GlobalVariableExpression) then - if(node.scope:getVariableName(node.id) == "ARR") then - return Ast.VariableExpression(self.rootScope, self.arrId); + if(node.kind == AstKind.GlobalVariableExpression or node.kind == AstKind.AssignmentGlobalVariable) then + if(node.name == "ARR") then + return node.kind == AstKind.AssignmentGlobalVariable and Ast.AssignmentVariable(self.arrSymbol) or Ast.VariableExpression(self.arrSymbol); end - if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE") then + if(node.name == "LOOKUP_TABLE") then return self:createBase85Lookup(); end end @@ -475,16 +476,16 @@ function ConstantArray:addDecodeCode(ast) local forStat = newAst.body.statements[1]; visitast(newAst, nil, function(node, data) - if(node.kind == AstKind.VariableExpression or node.kind == AstKind.GlobalVariableExpression) then - if(node.scope:getVariableName(node.id) == "ARR") then - return Ast.VariableExpression(self.rootScope, self.arrId); + if(node.kind == AstKind.GlobalVariableExpression or node.kind == AstKind.AssignmentGlobalVariable) then + if(node.name == "ARR") then + return node.kind == AstKind.AssignmentGlobalVariable and Ast.AssignmentVariable(self.arrSymbol) or Ast.VariableExpression(self.arrSymbol); end - if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE_64") then + if(node.name == "LOOKUP_TABLE_64") then return self:createBase64Lookup(); end - if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE_85") then + if(node.name == "LOOKUP_TABLE_85") then return self:createBase85Lookup(); end end @@ -598,8 +599,7 @@ end function ConstantArray:apply(ast, pipeline) initPrefixes(); - self.rootScope = ast.body.scope; - self.arrId = self.rootScope:addVariable(); + self.arrSymbol = Ast.LocalSymbol(); self.base64chars = table.concat(util.shuffle{ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", @@ -650,15 +650,14 @@ function ConstantArray:apply(ast, pipeline) -- Set Wrapper Function Offset self.wrapperOffset = math.random(-self.MaxWrapperOffset, self.MaxWrapperOffset); - self.wrapperId = self.rootScope:addVariable(); + self.wrapperSymbol = Ast.LocalSymbol(); visitast(ast, function(node, data) -- Add Local Wrapper Functions if self.LocalWrapperCount > 0 and node.kind == AstKind.Block and node.isFunctionBlock and math.random() <= self.LocalWrapperThreshold then - local id = node.scope:addVariable() + local symbol = Ast.LocalSymbol() data.functionData.local_wrappers = { - id = id; - scope = node.scope, + symbol = symbol; }; local nameLookup = {}; for i = 1, self.LocalWrapperCount, 1 do @@ -706,13 +705,11 @@ function ConstantArray:apply(ast, pipeline) local offset = wrapper.offset; local name = wrapper.index; - local funcScope = Scope:new(node.scope); - local arg = nil; local args = {}; for i = 1, self.LocalWrapperArgCount, 1 do - args[i] = funcScope:addVariable(); + args[i] = Ast.LocalSymbol(); if i == argPos then arg = args[i]; end @@ -722,31 +719,26 @@ function ConstantArray:apply(ast, pipeline) -- Create add and Subtract code if offset < 0 then - addSubArg = Ast.SubExpression(Ast.VariableExpression(funcScope, arg), Ast.NumberExpression(-offset)); + addSubArg = Ast.SubExpression(Ast.VariableExpression(arg), Ast.NumberExpression(-offset)); else - addSubArg = Ast.AddExpression(Ast.VariableExpression(funcScope, arg), Ast.NumberExpression(offset)); + addSubArg = Ast.AddExpression(Ast.VariableExpression(arg), Ast.NumberExpression(offset)); end - local callArg = Ast.FunctionCallExpression(Ast.VariableExpression(self.rootScope, self.wrapperId), { + local callArg = Ast.FunctionCallExpression(Ast.VariableExpression(self.wrapperSymbol), { addSubArg }); - local fargs = {}; - for i, v in ipairs(args) do - fargs[i] = Ast.VariableExpression(funcScope, v); - end - - elems[i] = Ast.KeyedTableEntry( + elems[i] = Ast.KeyedTableEntry( Ast.StringExpression(name), - Ast.FunctionLiteralExpression(fargs, Ast.Block({ + Ast.FunctionLiteralExpression(args, Ast.Block({ Ast.ReturnStatement({ callArg }); - }, funcScope)) + })) ) end - table.insert(node.statements, 1, Ast.LocalVariableDeclaration(node.scope, { - wrappers.id + table.insert(node.statements, 1, Ast.LocalVariableDeclaration({ + wrappers.symbol }, { Ast.TableConstructorExpression(elems) })); @@ -758,29 +750,27 @@ function ConstantArray:apply(ast, pipeline) local steps = util.shuffle({ -- Add Wrapper Function Code function() - local funcScope = Scope:new(self.rootScope); - - local arg = funcScope:addVariable(); + local arg = Ast.LocalSymbol(); local addSubArg; -- Create add and Subtract code if self.wrapperOffset < 0 then - addSubArg = Ast.SubExpression(Ast.VariableExpression(funcScope, arg), Ast.NumberExpression(-self.wrapperOffset)); + addSubArg = Ast.SubExpression(Ast.VariableExpression(arg), Ast.NumberExpression(-self.wrapperOffset)); else - addSubArg = Ast.AddExpression(Ast.VariableExpression(funcScope, arg), Ast.NumberExpression(self.wrapperOffset)); + addSubArg = Ast.AddExpression(Ast.VariableExpression(arg), Ast.NumberExpression(self.wrapperOffset)); end -- Create and Add the Function Declaration - table.insert(ast.body.statements, 1, Ast.LocalFunctionDeclaration(self.rootScope, self.wrapperId, { - Ast.VariableExpression(funcScope, arg) + table.insert(ast.body.statements, 1, Ast.LocalFunctionDeclaration(self.wrapperSymbol, { + arg }, Ast.Block({ Ast.ReturnStatement({ Ast.IndexExpression( - Ast.VariableExpression(self.rootScope, self.arrId), + Ast.VariableExpression(self.arrSymbol), addSubArg ) }); - }, funcScope))); + }))); -- Resulting Code: -- function xy(a) @@ -803,10 +793,10 @@ function ConstantArray:apply(ast, pipeline) end -- Add the Array Declaration - table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration(self.rootScope, {self.arrId}, {self:createArray()})); + table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration({self.arrSymbol}, {self:createArray()})); - self.rootScope = nil; - self.arrId = nil; + self.arrSymbol = nil; + self.wrapperSymbol = nil; self.constants = nil; self.lookup = nil; diff --git a/src/prometheus/steps/EncryptStrings.lua b/src/prometheus/steps/EncryptStrings.lua index 9e446e21..6bd0b823 100644 --- a/src/prometheus/steps/EncryptStrings.lua +++ b/src/prometheus/steps/EncryptStrings.lua @@ -197,26 +197,23 @@ function EncryptStrings:apply(ast, _) local newAst = Parser:new({ LuaVersion = Enums.LuaVersion.Lua51 }):parse(code); local doStat = newAst.body.statements[1]; - local scope = ast.body.scope; - local decryptVar = scope:addVariable(); - local stringsVar = scope:addVariable(); + local decryptVar = Ast.LocalSymbol("_decrypt"); + local stringsVar = Ast.LocalSymbol("_strings"); visitast(newAst, nil, function(node, data) if(node.kind == AstKind.FunctionDeclaration) then - if(node.scope:getVariableName(node.id) == "DECRYPT") then - node.scope = scope; - node.id = decryptVar; - node.symbol = scope:getSymbol(decryptVar); + if(node.name == "DECRYPT") then + node.symbol = decryptVar; node.name = nil; end end if(node.kind == AstKind.AssignmentVariable or node.kind == AstKind.VariableExpression or node.kind == AstKind.AssignmentGlobalVariable or node.kind == AstKind.GlobalVariableExpression) then - if(node.scope:getVariableName(node.id) == "STRINGS") then + if(node.name == "STRINGS") then if node.kind == AstKind.AssignmentVariable or node.kind == AstKind.AssignmentGlobalVariable then - return Ast.AssignmentVariable(scope, stringsVar); + return Ast.AssignmentVariable(stringsVar); end - return Ast.VariableExpression(scope, stringsVar); + return Ast.VariableExpression(stringsVar); end end end) @@ -224,7 +221,7 @@ function EncryptStrings:apply(ast, _) visitast(ast, nil, function(node, data) if(node.kind == AstKind.StringExpression) then local encrypted, seed = Encryptor.encrypt(node.value); - return Ast.IndexExpression(Ast.VariableExpression(scope, stringsVar), Ast.FunctionCallExpression(Ast.VariableExpression(scope, decryptVar), { + return Ast.IndexExpression(Ast.VariableExpression(stringsVar), Ast.FunctionCallExpression(Ast.VariableExpression(decryptVar), { Ast.StringExpression(encrypted), Ast.NumberExpression(seed), })); end @@ -233,7 +230,7 @@ function EncryptStrings:apply(ast, _) -- Insert to Main Ast table.insert(ast.body.statements, 1, doStat); - table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration(scope, util.shuffle{ decryptVar, stringsVar }, {})); + table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration(util.shuffle{ decryptVar, stringsVar }, {})); return ast end diff --git a/src/prometheus/steps/ProxifyLocals.lua b/src/prometheus/steps/ProxifyLocals.lua index 07d4a98e..019d4b8f 100644 --- a/src/prometheus/steps/ProxifyLocals.lua +++ b/src/prometheus/steps/ProxifyLocals.lua @@ -2,328 +2,210 @@ -- -- ProxifyLocals.lua -- --- This Script provides a Obfuscation Step for putting all Locals into Proxy Objects +-- This Step wraps local values in proxy objects. -local Step = require("prometheus.step"); -local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); -local visitast = require("prometheus.visitast"); +local Step = require("prometheus.step") +local Ast = require("prometheus.ast") +local visitast = require("prometheus.visitast") local RandomLiterals = require("prometheus.randomLiterals") -local AstKind = Ast.AstKind; - -local ProxifyLocals = Step:extend(); -ProxifyLocals.Description = "This Step wraps all locals into Proxy Objects"; -ProxifyLocals.Name = "Proxify Locals"; - +local AstKind = Ast.AstKind +local ProxifyLocals = Step:extend() +ProxifyLocals.Description = "This Step wraps all locals into Proxy Objects" +ProxifyLocals.Name = "Proxify Locals" ProxifyLocals.SettingsDescriptor = { LiteralType = { - name = "LiteralType", - description = "The type of the randomly generated literals", - type = "enum", - values = { - "dictionary", - "number", - "string", - "any", - }, - default = "string", + name = "LiteralType", description = "The type of the randomly generated literals", + type = "enum", values = {"dictionary", "number", "string", "any"}, default = "string", }, } local function shallowcopy(orig) - local orig_type = type(orig) - local copy - if orig_type == 'table' then - copy = {} - for orig_key, orig_value in pairs(orig) do - copy[orig_key] = orig_value - end - else -- number, string, boolean, etc - copy = orig - end - return copy + local copy = {} + for key, value in pairs(orig) do copy[key] = value end + return copy end local function callNameGenerator(generatorFunction, ...) - if(type(generatorFunction) == "table") then - generatorFunction = generatorFunction.generateName; - end - return generatorFunction(...); + if type(generatorFunction) == "table" then generatorFunction = generatorFunction.generateName end + return generatorFunction(...) end local MetatableExpressions = { - { - constructor = Ast.AddExpression, - key = "__add"; - }, - { - constructor = Ast.SubExpression, - key = "__sub"; - }, - { - constructor = Ast.IndexExpression, - key = "__index"; - }, - { - constructor = Ast.MulExpression, - key = "__mul"; - }, - { - constructor = Ast.DivExpression, - key = "__div"; - }, - { - constructor = Ast.PowExpression, - key = "__pow"; - }, - { - constructor = Ast.StrCatExpression, - key = "__concat"; - } + {constructor = Ast.AddExpression, key = "__add"}, + {constructor = Ast.SubExpression, key = "__sub"}, + {constructor = Ast.IndexExpression, key = "__index"}, + {constructor = Ast.MulExpression, key = "__mul"}, + {constructor = Ast.DivExpression, key = "__div"}, + {constructor = Ast.PowExpression, key = "__pow"}, + {constructor = Ast.StrCatExpression, key = "__concat"}, } function ProxifyLocals:init(_) end local function generateLocalMetatableInfo(pipeline) - local usedOps = {}; - local info = {}; - for i, v in ipairs({"setValue", "getValue", "index"}) do - local rop; - repeat - rop = MetatableExpressions[math.random(#MetatableExpressions)]; - until not usedOps[rop]; - usedOps[rop] = true; - info[v] = rop; - end - - info.valueName = callNameGenerator(pipeline.namegenerator, math.random(1, 4096)); - - return info; + local usedOps, info = {}, {} + for _, name in ipairs({"setValue", "getValue", "index"}) do + local op + repeat op = MetatableExpressions[math.random(#MetatableExpressions)] until not usedOps[op] + usedOps[op] = true + info[name] = op + end + info.valueName = callNameGenerator(pipeline.namegenerator, math.random(1, 4096)) + return info end -function ProxifyLocals:CreateAssignmentExpression(info, expr, parentScope) - local metatableVals = {}; - - -- Setvalue Entry - local setValueFunctionScope = Scope:new(parentScope); - local setValueSelf = setValueFunctionScope:addVariable(); - local setValueArg = setValueFunctionScope:addVariable(); - local setvalueFunctionLiteral = Ast.FunctionLiteralExpression( - { - Ast.VariableExpression(setValueFunctionScope, setValueSelf), -- Argument 1 - Ast.VariableExpression(setValueFunctionScope, setValueArg), -- Argument 2 - }, - Ast.Block({ -- Create Function Body - Ast.AssignmentStatement({ - Ast.AssignmentIndexing(Ast.VariableExpression(setValueFunctionScope, setValueSelf), Ast.StringExpression(info.valueName)); - }, { - Ast.VariableExpression(setValueFunctionScope, setValueArg) - }) - }, setValueFunctionScope) - ); - table.insert(metatableVals, Ast.KeyedTableEntry(Ast.StringExpression(info.setValue.key), setvalueFunctionLiteral)); - - -- Getvalue Entry - local getValueFunctionScope = Scope:new(parentScope); - local getValueSelf = getValueFunctionScope:addVariable(); - local getValueArg = getValueFunctionScope:addVariable(); - local getValueIdxExpr; - if(info.getValue.key == "__index" or info.setValue.key == "__index") then - getValueIdxExpr = Ast.FunctionCallExpression(Ast.VariableExpression(getValueFunctionScope:resolveGlobal("rawget")), { - Ast.VariableExpression(getValueFunctionScope, getValueSelf), - Ast.StringExpression(info.valueName), - }); - else - getValueIdxExpr = Ast.IndexExpression(Ast.VariableExpression(getValueFunctionScope, getValueSelf), Ast.StringExpression(info.valueName)); - end - local getvalueFunctionLiteral = Ast.FunctionLiteralExpression( - { - Ast.VariableExpression(getValueFunctionScope, getValueSelf), -- Argument 1 - Ast.VariableExpression(getValueFunctionScope, getValueArg), -- Argument 2 - }, - Ast.Block({ -- Create Function Body - Ast.ReturnStatement({ - getValueIdxExpr; - }); - }, getValueFunctionScope) - ); - table.insert(metatableVals, Ast.KeyedTableEntry(Ast.StringExpression(info.getValue.key), getvalueFunctionLiteral)); +local function localSymbol(name) + return Ast.LocalSymbol(name) +end - local setMetatableVar = Ast.VariableExpression(self.setMetatableVarScope, self.setMetatableVarId); - setMetatableVar.__ignoreProxifyLocals = true; - return Ast.FunctionCallExpression( - setMetatableVar, - { - Ast.TableConstructorExpression({ - Ast.KeyedTableEntry(Ast.StringExpression(info.valueName), expr) - }), - Ast.TableConstructorExpression(metatableVals) - } - ); +function ProxifyLocals:CreateAssignmentExpression(info, expr) + local metatableVals = {} + local setSelf, setArg = localSymbol(), localSymbol() + local setFunction = Ast.FunctionLiteralExpression({setSelf, setArg}, Ast.Block({ + Ast.AssignmentStatement({ + Ast.AssignmentIndexing(Ast.VariableExpression(setSelf), Ast.StringExpression(info.valueName)), + }, {Ast.VariableExpression(setArg)}), + })) + table.insert(metatableVals, Ast.KeyedTableEntry(Ast.StringExpression(info.setValue.key), setFunction)) + + local getSelf, getArg = localSymbol(), localSymbol() + local getValue + if info.getValue.key == "__index" or info.setValue.key == "__index" then + getValue = Ast.FunctionCallExpression(Ast.GlobalVariableExpression("rawget"), { + Ast.VariableExpression(getSelf), Ast.StringExpression(info.valueName), + }) + else + getValue = Ast.IndexExpression(Ast.VariableExpression(getSelf), Ast.StringExpression(info.valueName)) + end + local getFunction = Ast.FunctionLiteralExpression({getSelf, getArg}, Ast.Block({ + Ast.ReturnStatement({getValue}), + })) + table.insert(metatableVals, Ast.KeyedTableEntry(Ast.StringExpression(info.getValue.key), getFunction)) + + return Ast.FunctionCallExpression(Ast.VariableExpression(self.setMetatableSymbol), { + Ast.TableConstructorExpression({Ast.KeyedTableEntry(Ast.StringExpression(info.valueName), expr)}), + Ast.TableConstructorExpression(metatableVals), + }) end function ProxifyLocals:apply(ast, pipeline) - local localMetatableInfos = {}; - local function getLocalMetatableInfo(scope, id) - -- Global Variables should not be transformed - if(scope.isGlobal) then return nil end; - - localMetatableInfos[scope] = localMetatableInfos[scope] or {}; - if localMetatableInfos[scope][id] then - -- If locked, return no Metatable - if localMetatableInfos[scope][id].locked then - return nil - end - return localMetatableInfos[scope][id]; - end - local localMetatableInfo = generateLocalMetatableInfo(pipeline); - localMetatableInfos[scope][id] = localMetatableInfo; - return localMetatableInfo; - end - - local function disableMetatableInfo(scope, id) - -- Global Variables should not be transformed - if(scope.isGlobal) then return nil end; - - localMetatableInfos[scope] = localMetatableInfos[scope] or {}; - localMetatableInfos[scope][id] = {locked = true} - end - - -- Create Setmetatable Variable - self.setMetatableVarScope = ast.body.scope; - self.setMetatableVarId = ast.body.scope:addVariable(); - - -- Create Empty Function Variable - self.emptyFunctionScope = ast.body.scope; - self.emptyFunctionId = ast.body.scope:addVariable(); - self.emptyFunctionUsed = false; - - -- Add Empty Function Declaration - table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration(self.emptyFunctionScope, {self.emptyFunctionId}, { - Ast.FunctionLiteralExpression({}, Ast.Block({}, Scope:new(ast.body.scope))); - })); - - - visitast(ast, function(node, data) - -- Expand proxified local functions into a declaration followed by an - -- assignment. The declaration must exist before its body is created so - -- recursive references resolve to the same local binding. - if(node.kind == AstKind.Block) then - local i = 1; - while i <= #node.statements do - local statement = node.statements[i]; - if statement.kind == AstKind.LocalFunctionDeclaration then - local info = getLocalMetatableInfo(statement.scope, statement.id); - if info then - local declaration = Ast.LocalVariableDeclaration(statement.scope, {statement.id}, {}); - declaration.__skipProxifyLocals = true; - local literal = Ast.FunctionLiteralExpression(statement.args, statement.body); - local assignmentTarget = Ast.AssignmentVariable(statement.scope, statement.id); - assignmentTarget.__ignoreProxifyLocals = true; - local assignment = Ast.AssignmentStatement( - {assignmentTarget}, - {self:CreateAssignmentExpression(info, literal, statement.scope)} - ); - assignment.__skipProxifyLocals = true; - node.statements[i] = declaration; - table.insert(node.statements, i + 1, assignment); - i = i + 1; - end - end - i = i + 1; - end - end - - -- Lock for loop variables - if(node.kind == AstKind.ForStatement) then - disableMetatableInfo(node.scope, node.id) - end - if(node.kind == AstKind.ForInStatement) then - for i, id in ipairs(node.ids) do - disableMetatableInfo(node.scope, id); - end - end - - -- Lock Function Arguments - if(node.kind == AstKind.FunctionDeclaration or node.kind == AstKind.LocalFunctionDeclaration or node.kind == AstKind.FunctionLiteralExpression) then - for i, expr in ipairs(node.args) do - if expr.kind == AstKind.VariableExpression then - disableMetatableInfo(expr.scope, expr.id); - end - end - end - - -- Assignment Statements may be Obfuscated Differently - if(node.kind == AstKind.AssignmentStatement) then - if not node.__skipProxifyLocals and #node.lhs == 1 and node.lhs[1].kind == AstKind.AssignmentVariable then - local variable = node.lhs[1]; - local localMetatableInfo = getLocalMetatableInfo(variable.scope, variable.id); - if localMetatableInfo then - local args = shallowcopy(node.rhs); - local vexp = Ast.VariableExpression(variable.scope, variable.id); - vexp.__ignoreProxifyLocals = true; - args[1] = localMetatableInfo.setValue.constructor(vexp, args[1]); - self.emptyFunctionUsed = true; - return Ast.FunctionCallStatement(Ast.VariableExpression(self.emptyFunctionScope, self.emptyFunctionId), args); - end - end - end - end, function(node, data) - -- Local Variable Declaration - if(node.kind == AstKind.LocalVariableDeclaration and not node.__skipProxifyLocals) then - for i, id in ipairs(node.ids) do - local expr = node.expressions[i] or Ast.NilExpression(); - local localMetatableInfo = getLocalMetatableInfo(node.scope, id); - -- Apply Only to Some Variables if Threshold is non 1 - if localMetatableInfo then - local newExpr = self:CreateAssignmentExpression(localMetatableInfo, expr, node.scope); - node.expressions[i] = newExpr; - end - end - end - - -- Variable Expression - if(node.kind == AstKind.VariableExpression and not node.__ignoreProxifyLocals) then - local localMetatableInfo = getLocalMetatableInfo(node.scope, node.id); - -- Apply Only to Some Variables if Threshold is non 1 - if localMetatableInfo then - local literal; - if self.LiteralType == "dictionary" then - literal = RandomLiterals.Dictionary(); - elseif self.LiteralType == "number" then - literal = RandomLiterals.Number(); - elseif self.LiteralType == "string" then - literal = RandomLiterals.String(pipeline); - else - literal = RandomLiterals.Any(pipeline); - end - return localMetatableInfo.getValue.constructor(node, literal); - end - end - - -- Assignment Variable for Assignment Statement - if(node.kind == AstKind.AssignmentVariable and not node.__ignoreProxifyLocals) then - local localMetatableInfo = getLocalMetatableInfo(node.scope, node.id); - -- Apply Only to Some Variables if Threshold is non 1 - if localMetatableInfo then - return Ast.AssignmentIndexing(node, Ast.StringExpression(localMetatableInfo.valueName)); - end - end - - -- Function Declaration - if(node.kind == AstKind.FunctionDeclaration) then - local localMetatableInfo = getLocalMetatableInfo(node.scope, node.id); - if(localMetatableInfo) then - table.insert(node.indices, 1, localMetatableInfo.valueName); - end - end - end) + local localMetatableInfos = {} + local function isGlobal(node) + return node.kind == AstKind.GlobalVariableExpression or node.kind == AstKind.AssignmentGlobalVariable + end + local function getLocalMetatableInfo(symbol) + if not symbol or localMetatableInfos[symbol] == false then return nil end + if localMetatableInfos[symbol] then return localMetatableInfos[symbol] end + local info = generateLocalMetatableInfo(pipeline) + localMetatableInfos[symbol] = info + return info + end + local function disableMetatableInfo(symbol) + if symbol then localMetatableInfos[symbol] = false end + end - -- Add Setmetatable Variable Declaration - table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration(self.setMetatableVarScope, {self.setMetatableVarId}, { - Ast.VariableExpression(self.setMetatableVarScope:resolveGlobal("setmetatable")) - })); + self.setMetatableSymbol = localSymbol() + self.emptyFunctionSymbol = localSymbol() + -- Generated implementation bindings are never proxified themselves. + localMetatableInfos[self.setMetatableSymbol] = false + localMetatableInfos[self.emptyFunctionSymbol] = false + self.emptyFunctionUsed = false + local emptyFunctionDeclaration = Ast.LocalVariableDeclaration({self.emptyFunctionSymbol}, { + Ast.FunctionLiteralExpression({}, Ast.Block({})), + }) + emptyFunctionDeclaration.__skipProxifyLocals = true + table.insert(ast.body.statements, 1, emptyFunctionDeclaration) + + visitast(ast, function(node) + -- Declare first, then assign. The early declaration makes recursive + -- references inside the function literal point at the same binding. + if node.kind == AstKind.Block then + local i = 1 + while i <= #node.statements do + local statement = node.statements[i] + if statement.kind == AstKind.LocalFunctionDeclaration then + local info = getLocalMetatableInfo(statement.symbol) + if info then + local declaration = Ast.LocalVariableDeclaration({statement.symbol}, {}) + declaration.__skipProxifyLocals = true + local literal = Ast.FunctionLiteralExpression(statement.args, statement.body) + local target = Ast.AssignmentVariable(statement.symbol) + target.__ignoreProxifyLocals = true + local assignment = Ast.AssignmentStatement({target}, { + self:CreateAssignmentExpression(info, literal), + }) + assignment.__skipProxifyLocals = true + node.statements[i] = declaration + table.insert(node.statements, i + 1, assignment) + i = i + 1 + end + end + i = i + 1 + end + end + + if node.kind == AstKind.ForStatement then disableMetatableInfo(node.symbol) end + if node.kind == AstKind.ForInStatement then + for _, symbol in ipairs(node.symbols) do disableMetatableInfo(symbol) end + end + if node.kind == AstKind.FunctionDeclaration or node.kind == AstKind.LocalFunctionDeclaration + or node.kind == AstKind.FunctionLiteralExpression then + for _, arg in ipairs(node.args) do + if arg.kind == AstKind.VariableExpression then disableMetatableInfo(arg.symbol) + elseif arg.kind == "LocalSymbol" then disableMetatableInfo(arg) end + end + end + + if node.kind == AstKind.AssignmentStatement and not node.__skipProxifyLocals + and #node.lhs == 1 and node.lhs[1].kind == AstKind.AssignmentVariable then + local variable = node.lhs[1] + local info = not isGlobal(variable) and getLocalMetatableInfo(variable.symbol) + if info then + local args = shallowcopy(node.rhs) + local reference = Ast.VariableExpression(variable.symbol) + reference.__ignoreProxifyLocals = true + args[1] = info.setValue.constructor(reference, args[1]) + self.emptyFunctionUsed = true + return Ast.FunctionCallStatement(Ast.VariableExpression(self.emptyFunctionSymbol), args) + end + end + end, function(node) + if node.kind == AstKind.LocalVariableDeclaration and not node.__skipProxifyLocals then + for i, symbol in ipairs(node.symbols) do + local info = getLocalMetatableInfo(symbol) + if info then + node.expressions[i] = self:CreateAssignmentExpression(info, node.expressions[i] or Ast.NilExpression()) + end + end + end + if node.kind == AstKind.VariableExpression and not node.__ignoreProxifyLocals and not isGlobal(node) then + local info = getLocalMetatableInfo(node.symbol) + if info then + local literal + if self.LiteralType == "dictionary" then literal = RandomLiterals.Dictionary() + elseif self.LiteralType == "number" then literal = RandomLiterals.Number() + elseif self.LiteralType == "string" then literal = RandomLiterals.String(pipeline) + else literal = RandomLiterals.Any(pipeline) end + return info.getValue.constructor(node, literal) + end + end + if node.kind == AstKind.AssignmentVariable and not node.__ignoreProxifyLocals and not isGlobal(node) then + local info = getLocalMetatableInfo(node.symbol) + if info then + node.__ignoreProxifyLocals = true + return Ast.AssignmentIndexing(node, Ast.StringExpression(info.valueName)) + end + end + if node.kind == AstKind.FunctionDeclaration and node.symbol then + local info = getLocalMetatableInfo(node.symbol) + if info then table.insert(node.indices, 1, info.valueName) end + end + end) + + table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration({self.setMetatableSymbol}, { + Ast.GlobalVariableExpression("setmetatable"), + })) end -return ProxifyLocals; +return ProxifyLocals diff --git a/src/prometheus/steps/SplitStrings.lua b/src/prometheus/steps/SplitStrings.lua index 9a61e6f4..26fad732 100644 --- a/src/prometheus/steps/SplitStrings.lua +++ b/src/prometheus/steps/SplitStrings.lua @@ -82,7 +82,7 @@ local function generateTableConcatNode(chunks, data) table.insert(chunkNodes, Ast.TableEntry(Ast.StringExpression(chunk))); end local tb = Ast.TableConstructorExpression(chunkNodes); - return Ast.FunctionCallExpression(Ast.VariableExpression(data.tableConcatScope, data.tableConcatId), {tb}); + return Ast.FunctionCallExpression(Ast.VariableExpression(data.tableConcatSymbol), {tb}); end local function generateStrCatNode(chunks) @@ -164,7 +164,7 @@ local function generateCustomNodeArgs(chunks, data, variant) end -local function generateCustomFunctionLiteral(parentScope, variant) +local function generateCustomFunctionLiteral(variant) local parser = Parser:new({ LuaVersion = LuaVersion.Lua51; }); @@ -185,26 +185,24 @@ local function generateCustomFunctionLiteral(parentScope, variant) end end -local function generateGlobalCustomFunctionDeclaration(ast, data) +local function generateGlobalCustomFunctionDeclaration(data) local parser = Parser:new({ LuaVersion = LuaVersion.Lua51; }); -- Custom Function Type 1 if data.customFunctionVariant == 1 then - local astScope = ast.body.scope; local funcDeclNode = parser:parse(custom1Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; - return Ast.LocalVariableDeclaration(astScope, {data.customFuncId}, + return Ast.LocalVariableDeclaration({data.customFuncSymbol}, {Ast.FunctionLiteralExpression(funcArgs, funcBody)}); -- Custom Function Type 2 else - local astScope = ast.body.scope; local funcDeclNode = parser:parse(custom2Code).body.statements[1]; local funcBody = funcDeclNode.body; local funcArgs = funcDeclNode.args; - return Ast.LocalVariableDeclaration(data.customFuncScope, {data.customFuncId}, + return Ast.LocalVariableDeclaration({data.customFuncSymbol}, {Ast.FunctionLiteralExpression(funcArgs, funcBody)}); end end @@ -218,17 +216,11 @@ function SplitStrings:apply(ast, pipeline) if(self.ConcatenationType == "table") then - local scope = ast.body.scope; - local id = scope:addVariable(); - data.tableConcatScope = scope; - data.tableConcatId = id; + data.tableConcatSymbol = Ast.LocalSymbol("_table_concat"); elseif(self.ConcatenationType == "custom") then data.customFunctionType = self.CustomFunctionType; if data.customFunctionType == "global" then - local scope = ast.body.scope; - local id = scope:addVariable(); - data.customFuncScope = scope; - data.customFuncId = id; + data.customFuncSymbol = Ast.LocalSymbol("_custom_concat"); data.customFunctionVariant = self:variant(); end end @@ -244,12 +236,10 @@ function SplitStrings:apply(ast, pipeline) if(self.ConcatenationType == "custom" and data.customFunctionType == "local" and node.kind == Ast.AstKind.Block and node.isFunctionBlock) then data.functionData.localFunctions = {}; for i = 1, customLocalFunctionsCount, 1 do - local scope = data.scope; - local id = scope:addVariable(); + local symbol = Ast.LocalSymbol("_custom_concat"); local variant = self:variant(); table.insert(data.functionData.localFunctions, { - scope = scope, - id = id, + symbol = symbol, variant = variant, used = false, }); @@ -263,8 +253,8 @@ function SplitStrings:apply(ast, pipeline) if(self.ConcatenationType == "custom" and data.customFunctionType == "local" and node.kind == Ast.AstKind.Block and node.isFunctionBlock) then for i, func in ipairs(data.functionData.localFunctions) do if func.used then - local literal = generateCustomFunctionLiteral(func.scope, func.variant); - table.insert(node.statements, 1, Ast.LocalVariableDeclaration(func.scope, {func.id}, {literal})); + local literal = generateCustomFunctionLiteral(func.variant); + table.insert(node.statements, 1, Ast.LocalVariableDeclaration({func.symbol}, {literal})); end end end @@ -292,18 +282,18 @@ function SplitStrings:apply(ast, pipeline) elseif self.ConcatenationType == "custom" then if self.CustomFunctionType == "global" then local args = generateCustomNodeArgs(chunks, data, data.customFunctionVariant); - node = Ast.FunctionCallExpression(Ast.VariableExpression(data.customFuncScope, data.customFuncId), args); + node = Ast.FunctionCallExpression(Ast.VariableExpression(data.customFuncSymbol), args); elseif self.CustomFunctionType == "local" then local lfuncs = data.functionData.localFunctions; local idx = math.random(1, #lfuncs); local func = lfuncs[idx]; local args = generateCustomNodeArgs(chunks, data, func.variant); func.used = true; - node = Ast.FunctionCallExpression(Ast.VariableExpression(func.scope, func.id), args); + node = Ast.FunctionCallExpression(Ast.VariableExpression(func.symbol), args); elseif self.CustomFunctionType == "inline" then local variant = self:variant(); local args = generateCustomNodeArgs(chunks, data, variant); - local literal = generateCustomFunctionLiteral(data.scope, variant); + local literal = generateCustomFunctionLiteral(variant); node = Ast.FunctionCallExpression(literal, args); end end @@ -316,13 +306,11 @@ function SplitStrings:apply(ast, pipeline) if(self.ConcatenationType == "table") then - local globalScope = data.globalScope; - local tableScope, tableId = globalScope:resolve("table") - table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration(data.tableConcatScope, {data.tableConcatId}, - {Ast.IndexExpression(Ast.VariableExpression(tableScope, tableId), Ast.StringExpression("concat"))})); + table.insert(ast.body.statements, 1, Ast.LocalVariableDeclaration({data.tableConcatSymbol}, + {Ast.IndexExpression(Ast.GlobalVariableExpression("table"), Ast.StringExpression("concat"))})); elseif(self.ConcatenationType == "custom" and self.CustomFunctionType == "global") then - table.insert(ast.body.statements, 1, generateGlobalCustomFunctionDeclaration(ast, data)); + table.insert(ast.body.statements, 1, generateGlobalCustomFunctionDeclaration(data)); end end -return SplitStrings; \ No newline at end of file +return SplitStrings; diff --git a/src/prometheus/steps/Watermark.lua b/src/prometheus/steps/Watermark.lua index 64b5997c..603e3bc0 100644 --- a/src/prometheus/steps/Watermark.lua +++ b/src/prometheus/steps/Watermark.lua @@ -6,7 +6,6 @@ local Step = require("prometheus.step"); local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); local Watermark = Step:extend(); Watermark.Description = "This Step will add a watermark to the script"; @@ -34,27 +33,23 @@ end function Watermark:apply(ast) local body = ast.body; if string.len(self.Content) > 0 then - local scope, variable = ast.globalScope:resolve(self.CustomVariable); - local watermark = Ast.AssignmentVariable(ast.globalScope, variable); - - local functionScope = Scope:new(body.scope); - - local arg = functionScope:addVariable(); + local watermark = Ast.AssignmentGlobalVariable(self.CustomVariable); + local arg = Ast.LocalSymbol("_watermark_arg"); local statement = Ast.PassSelfFunctionCallStatement(Ast.StringExpression(self.Content), "gsub", { Ast.StringExpression(".+"), Ast.FunctionLiteralExpression({ - Ast.VariableExpression(functionScope, arg) + Ast.VariableExpression(arg) }, Ast.Block({ Ast.AssignmentStatement({ watermark }, { - Ast.VariableExpression(functionScope, arg) + Ast.VariableExpression(arg) }) - }, functionScope)) + })) }); table.insert(ast.body.statements, 1, statement) end end -return Watermark; \ No newline at end of file +return Watermark; diff --git a/src/prometheus/steps/WatermarkCheck.lua b/src/prometheus/steps/WatermarkCheck.lua index 5ae45e3d..48cb352e 100644 --- a/src/prometheus/steps/WatermarkCheck.lua +++ b/src/prometheus/steps/WatermarkCheck.lua @@ -6,7 +6,6 @@ local Step = require("prometheus.step"); local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); local Watermark = require("prometheus.steps.Watermark"); local WatermarkCheck = Step:extend(); @@ -37,12 +36,11 @@ function WatermarkCheck:apply(ast, pipeline) local body = ast.body; local watermarkExpression = Ast.StringExpression(self.Content); - local scope, variable = ast.globalScope:resolve(self.CustomVariable); - local watermark = Ast.VariableExpression(ast.globalScope, variable); + local watermark = Ast.GlobalVariableExpression(self.CustomVariable); local notEqualsExpression = Ast.NotEqualsExpression(watermark, watermarkExpression); - local ifBody = Ast.Block({Ast.ReturnStatement({})}, Scope:new(ast.body.scope)); + local ifBody = Ast.Block({Ast.ReturnStatement({})}); table.insert(body.statements, 1, Ast.IfStatement(notEqualsExpression, ifBody, {}, nil)); end -return WatermarkCheck; \ No newline at end of file +return WatermarkCheck; diff --git a/src/prometheus/steps/WrapInFunction.lua b/src/prometheus/steps/WrapInFunction.lua index 961b1e6b..c3864771 100644 --- a/src/prometheus/steps/WrapInFunction.lua +++ b/src/prometheus/steps/WrapInFunction.lua @@ -6,7 +6,6 @@ local Step = require("prometheus.step"); local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); local WrapInFunction = Step:extend(); WrapInFunction.Description = "This Step Wraps the Entire Script into a Function"; @@ -29,14 +28,12 @@ function WrapInFunction:apply(ast) for i = 1, self.Iterations, 1 do local body = ast.body; - local scope = Scope:new(ast.globalScope); - ast.body = Ast.Block({ Ast.ReturnStatement({ Ast.FunctionCallExpression(Ast.FunctionLiteralExpression({Ast.VarargExpression()}, body), {Ast.VarargExpression()}) }); - }, scope); + }); end end -return WrapInFunction; \ No newline at end of file +return WrapInFunction; diff --git a/src/prometheus/unparser.lua b/src/prometheus/unparser.lua index e5918e23..21adc1e3 100644 --- a/src/prometheus/unparser.lua +++ b/src/prometheus/unparser.lua @@ -13,6 +13,7 @@ local config = require("config"); local Ast = require("prometheus.ast"); local analyzeBindings = require("prometheus.bindinganalysis"); +local renameVariables = require("prometheus.names"); local Enums = require("prometheus.enums"); local util = require("prometheus.util"); local logger = require("logger"); @@ -111,7 +112,7 @@ function Unparser:unparse(ast) -- Preserve symbol identity even for generated trees whose preferred names -- collide. Keep existing names whenever lexical lookup would be unambiguous. local attempts = {}; - ast.globalScope:renameVariables({ + renameVariables(ast, { Keywords = self.conventions.Keywords, generateName = function(_, scope, originalName) attempts[scope] = attempts[scope] or {}; @@ -202,7 +203,7 @@ function Unparser:unparseStatement(statement, tabbing) -- For Statement elseif(statement.kind == AstKind.ForStatement) then local bodyCode = self:unparseBlock(statement.body, tabbing); - push("for", self:whitespace(), statement.scope:getVariableName(statement.id), self:optionalWhitespace(), "="); + push("for", self:whitespace(), statement.symbol and statement.symbol.name or statement.name, self:optionalWhitespace(), "="); push(self:optionalWhitespace(), self:unparseExpression(statement.initialValue, tabbing), ","); push(self:optionalWhitespace(), self:unparseExpression(statement.finalValue, tabbing), ","); local incrementByCode = statement.incrementBy and self:unparseExpression(statement.incrementBy, tabbing) or "1"; @@ -214,11 +215,11 @@ function Unparser:unparseStatement(statement, tabbing) -- For In Statement elseif(statement.kind == AstKind.ForInStatement) then push("for", self:whitespace()); - for i, id in ipairs(statement.ids) do + for i, symbol in ipairs(statement.symbols) do if(i ~= 1) then push(",", self:optionalWhitespace()); end - push(statement.scope:getVariableName(id)); + push(symbol.name); end push(self:whitespace(), "in"); local exprcode = self:unparseExpression(statement.expressions[1], tabbing); @@ -259,7 +260,7 @@ function Unparser:unparseStatement(statement, tabbing) -- Function Declaration elseif(statement.kind == AstKind.FunctionDeclaration) then - local funcname = statement.scope:getVariableName(statement.id); + local funcname = statement.symbol and statement.symbol.name or statement.name; for _, index in ipairs(statement.indices) do funcname = funcname .. "." .. index; end @@ -271,7 +272,7 @@ function Unparser:unparseStatement(statement, tabbing) if(arg.kind == AstKind.VarargExpression) then push("..."); else - push(arg.scope:getVariableName(arg.id)); + push(arg.symbol.name); end end push(")"); @@ -281,7 +282,7 @@ function Unparser:unparseStatement(statement, tabbing) -- Local Function Declaration elseif(statement.kind == AstKind.LocalFunctionDeclaration) then - local funcname = statement.scope:getVariableName(statement.id); + local funcname = statement.symbol and statement.symbol.name or statement.name; push("local", self:whitespace(), "function", self:whitespace(), funcname, "("); for i, arg in ipairs(statement.args) do if i > 1 then @@ -290,7 +291,7 @@ function Unparser:unparseStatement(statement, tabbing) if(arg.kind == AstKind.VarargExpression) then push("..."); else - push(arg.scope:getVariableName(arg.id)); + push(arg.symbol.name); end end push(")"); @@ -301,11 +302,11 @@ function Unparser:unparseStatement(statement, tabbing) -- Local Variable Declaration elseif(statement.kind == AstKind.LocalVariableDeclaration) then push("local", self:whitespace()); - for i, id in ipairs(statement.ids) do + for i, symbol in ipairs(statement.symbols) do if i > 1 then push(",", self:optionalWhitespace()); end - push(statement.scope:getVariableName(id)); + push(symbol.name); end if(#statement.expressions > 0) then push(self:optionalWhitespace(), "=", self:optionalWhitespace()); @@ -450,7 +451,7 @@ function Unparser:unparseExpression(expression, tabbing) end if(expression.kind == AstKind.VariableExpression or expression.kind == AstKind.AssignmentVariable) then - return expression.scope:getVariableName(expression.id); + return expression.symbol.name; end if(expression.kind == AstKind.StringExpression) then @@ -658,7 +659,7 @@ function Unparser:unparseExpression(expression, tabbing) if(arg.kind == AstKind.VarargExpression) then push("..."); else - push(arg.scope:getVariableName(arg.id)); + push(arg.symbol.name); end end push(")"); diff --git a/src/prometheus/vm/compiler.lua b/src/prometheus/vm/compiler.lua index ec85b9fc..4912027c 100644 --- a/src/prometheus/vm/compiler.lua +++ b/src/prometheus/vm/compiler.lua @@ -123,7 +123,7 @@ end function Compiler:readVariable(scope, id) local register = self:alloc() if scope.isGlobal then - self:emit('GLOBAL', register, self:constant(scope:getVariableName(id))) + self:emit('GLOBAL', register, self:constant(scope.variables[id])) elseif self.analysis.owners[scope] == self.owner then self:emit(self:isCaptured(scope, id) and 'GETCELL' or 'MOVE', register, self:localRegister(scope, id)) @@ -133,7 +133,7 @@ end function Compiler:writeVariable(scope, id, source) if scope.isGlobal then - self:emit('SETGLOBAL', self:constant(scope:getVariableName(id)), source) + self:emit('SETGLOBAL', self:constant(scope.variables[id]), source) elseif self.analysis.owners[scope] == self.owner then self:emit(self:isCaptured(scope, id) and 'SETCELL' or 'MOVE', self:localRegister(scope, id), source) diff --git a/src/prometheus/vm/flatten.lua b/src/prometheus/vm/flatten.lua index e719a939..78e5a9fe 100644 --- a/src/prometheus/vm/flatten.lua +++ b/src/prometheus/vm/flatten.lua @@ -1,18 +1,17 @@ -- Flatten a generated interpreter without virtualizing its large data initializer. local Ast = require('prometheus.ast') -local Scope = require('prometheus.scope') local visitast = require('prometheus.visitast') local Parser = require('prometheus.parser') local Unparser = require('prometheus.unparser') local CffCompiler = require('prometheus.cff.compiler') local Enums = require('prometheus.enums') +local analyzeBindings = require('prometheus.bindinganalysis') local function externalVariables(ast, runtime) local owned, variables, seen = {}, {}, {} - local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})}, - Scope:new(ast.globalScope)), ast.globalScope) + local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})})) visitast(wrapper, function(node) - if node.kind == 'Block' then owned[node.scope] = true end + if node.kind == 'Block' and node.scope then owned[node.scope] = true end end) local writes = false visitast(wrapper, function(node) @@ -21,7 +20,7 @@ local function externalVariables(ast, runtime) or kind == 'AssignmentVariable' or kind == 'AssignmentGlobalVariable' or kind == 'FunctionDeclaration' then local scope = node.scope - if not scope.isGlobal and not owned[scope] then + if scope and not scope.isGlobal and not owned[scope] then if kind == 'AssignmentVariable' or kind == 'AssignmentGlobalVariable' or (kind == 'FunctionDeclaration' and #node.indices == 0) then writes = true @@ -40,6 +39,7 @@ local function externalVariables(ast, runtime) end return function(ast) + analyzeBindings(ast) local runtime = ast.vmRuntime local variables, writes = externalVariables(ast, runtime) if writes then return nil end @@ -61,16 +61,14 @@ return function(ast) while used[name] do name = name .. '_' end used[name], markers[symbol], captures[name], markerNames[i] = true, name, symbol, name end - local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})}, - Scope:new(ast.globalScope)), ast.globalScope) + local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})})) visitast(wrapper, nil, function(node) local name = markers[node.symbol] if name then if node.kind == 'VariableExpression' then return Ast.GlobalVariableExpression(name) end if node.kind == 'FunctionDeclaration' then node.symbol, node.name = nil, name - node.scope, node.id = ast.globalScope:resolve(name) - node.baseScope, node.baseId = node.scope, node.id + node.scope, node.id = nil, nil end end end) @@ -87,8 +85,7 @@ return function(ast) local replacementSource = 'return function(...) return (function(...) ' .. flattened .. ' end)(' .. forwarded .. ') end' local replacement = parser:parse(replacementSource).body.statements[1].args[1] - local replacementAst = Ast.TopNode(Ast.Block({Ast.ReturnStatement({replacement})}, - Scope:new(ast.globalScope)), ast.globalScope) + local replacementAst = Ast.TopNode(Ast.Block({Ast.ReturnStatement({replacement})})) visitast(replacementAst, nil, function(node) if node.kind == 'GlobalVariableExpression' and captures[node.name] then return Ast.VariableExpression(captures[node.name]) @@ -98,6 +95,6 @@ return function(ast) if node == runtime then return replacement, true end end) ast.vmRuntime = replacement - require('prometheus.bindinganalysis')(ast) + analyzeBindings(ast) return ast end diff --git a/src/prometheus/vm/statements.lua b/src/prometheus/vm/statements.lua index 89e10c02..76d9458c 100644 --- a/src/prometheus/vm/statements.lua +++ b/src/prometheus/vm/statements.lua @@ -148,7 +148,7 @@ return function(Compiler) self:emit("EQ", ended, first, nilreg) local exit = self:emit("JTRUE", ended, 0) self:emit("MOVE", control, first) - for i, id in ipairs(node.ids or node.vars) do + for i, id in ipairs(node.ids) do local value = self:alloc() self:emit("TAKE", value, results, i) self:initVariable(node.scope, id, value) diff --git a/src/prometheus/vm/strings.lua b/src/prometheus/vm/strings.lua index 675c3b1e..8a782469 100644 --- a/src/prometheus/vm/strings.lua +++ b/src/prometheus/vm/strings.lua @@ -4,32 +4,33 @@ local visitast = require('prometheus.visitast') local Kind = Ast.AstKind return function(ast) - local runtime, scope = ast.vmRuntime, ast.body.scope + local runtime = ast.vmRuntime if not runtime then return end - local cacheScope = runtime.body.scope local data, cache local entries, ids, variables, values = {}, {}, {}, {} - local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})}, scope), ast.globalScope) + local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})})) visitast(wrapper, nil, function(node, context) if node.kind == Kind.StringExpression then - if not data then data, cache = scope:addVariable(), cacheScope:addVariable() end + if not data then + data, cache = Ast.LocalSymbol('_vm_string_data'), Ast.LocalSymbol('_vm_string_cache') + end local id = ids[node.value] if not id then id = #entries + 1 ids[node.value] = id entries[id] = Ast.TableEntry(Ast.StringExpression(node.value)) - variables[id] = cacheScope:addVariable() - values[id] = Ast.IndexExpression(Ast.VariableExpression(cacheScope, cache), + variables[id] = Ast.LocalSymbol('_vm_string_value_' .. id) + values[id] = Ast.IndexExpression(Ast.VariableExpression(cache), Ast.NumberExpression(id)) end - return Ast.VariableExpression(cacheScope, variables[id]) + return Ast.VariableExpression(variables[id]) end end) if not data then return end table.insert(runtime.body.statements, 1, - Ast.LocalVariableDeclaration(cacheScope, {cache}, {Ast.VariableExpression(scope, data)})) + Ast.LocalVariableDeclaration({cache}, {Ast.VariableExpression(data)})) table.insert(runtime.body.statements, 2, - Ast.LocalVariableDeclaration(cacheScope, variables, values)) + Ast.LocalVariableDeclaration(variables, values)) table.insert(ast.body.statements, 1, - Ast.LocalVariableDeclaration(scope, {data}, {Ast.TableConstructorExpression(entries)})) + Ast.LocalVariableDeclaration({data}, {Ast.TableConstructorExpression(entries)})) end diff --git a/tests/harness/ast-bindings.lua b/tests/harness/ast-bindings.lua index 24847131..ed6d1178 100644 --- a/tests/harness/ast-bindings.lua +++ b/tests/harness/ast-bindings.lua @@ -150,4 +150,54 @@ local proxy = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, 'local function f(n) if n == 0 then return 1 end return f(n - 1) end return f(3)') assert(assert(loadstring(proxy))() == 1) +-- Steps must also operate on syntax with no cached scopes or numeric slots. +local function stripMetadata(node, seen) + if type(node) ~= 'table' or seen[node] then return end + seen[node] = true + for _, field in ipairs({'scope', 'id', 'ids', 'vars', 'parentScope', 'baseScope', + 'baseId', 'globalScope', 'bindingAnalysis'}) do node[field] = nil end + for _, child in pairs(node) do stripMetadata(child, seen) end +end +for _, config in ipairs({ + {Name = 'EncryptStrings'}, + {Name = 'ConstantArray', Settings = {Threshold = 1, StringsOnly = false, + LocalWrapperThreshold = 1, LocalWrapperCount = 2, LocalWrapperArgCount = 2}}, + {Name = 'ProxifyLocals'}, + {Name = 'SplitStrings', Settings = {CustomFunctionType = 'local', ConcatenationType = 'custom'}}, + {Name = 'SplitStrings', Settings = {CustomFunctionType = 'global', ConcatenationType = 'custom'}}, + {Name = 'SplitStrings', Settings = {CustomFunctionType = 'inline', ConcatenationType = 'custom'}}, + {Name = 'SplitStrings', Settings = {ConcatenationType = 'table'}}, + {Name = 'SplitStrings', Settings = {ConcatenationType = 'strcat'}}, + {Name = 'WrapInFunction'}, +}) do + local pipeline = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, Steps = {config}}) + local raw = parse('local function f(n) if n == 0 then return "scope free" end return f(n-1) end return f(2)') + stripMetadata(raw, {}) + -- Force probability gates open on every Lua runtime, while retaining the + -- regular integer RNG for generated constants and helper variants. + local random = math.random + math.random = function(a, b) + if a == nil then return 0.5 end + if b == nil then return random(a) end + return random(a, b) + end + local success, transformed = pcall(pipeline.steps[1].apply, pipeline.steps[1], raw, pipeline) + math.random = random + assert(success, transformed) + assert(run(transformed or raw) == 'scope free', config.Name .. ' requires legacy scope metadata') +end +-- Backend entry points must derive their own metadata as well, including the +-- bridge used when flattening an interpreter after string encryption. +local pipeline = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, + Steps = {{Name = 'Vmify'}, {Name = 'EncryptStrings'}, {Name = 'Cff'}}}) +local raw = parse('local value = "scope free"; return value') +for _, step in ipairs(pipeline.steps) do + stripMetadata(raw, {}) + raw = step:apply(raw, pipeline) or raw +end +assert(run(raw) == 'scope free') + +local ok = pcall(Ast.VariableExpression, {}) +assert(not ok, 'Legacy scope-based reference construction must be rejected') + print('AST binding identity, visibility, globals and reconstruction checks passed') diff --git a/tests/harness/cff-native.lua b/tests/harness/cff-native.lua index 535638f3..92b1166a 100644 --- a/tests/harness/cff-native.lua +++ b/tests/harness/cff-native.lua @@ -29,9 +29,8 @@ local function controlFlowCounts(root) functionCount = countKind(root, Ast.AstKind.FunctionLiteralExpression), } end -local function visitExpression(expression, parentAst, visitor) - local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({expression}),}, parentAst.body.scope), - parentAst.globalScope) +local function visitExpression(expression, visitor) + local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({expression})})) visitast(wrapper, visitor) end local function applyCff(step, ast, pipeline) @@ -221,7 +220,7 @@ local hoistedAst = Parser:new({LuaVersion = 'Lua51'}):parse(payloadSource) hoistedAst = Steps.Vmify:new():apply(hoistedAst) local runtimeCountsBeforeHoist = controlFlowCounts(hoistedAst.vmRuntime) local runtimeStringSet, runtimeStringCount = {}, 0 -visitExpression(hoistedAst.vmRuntime, hoistedAst, function(node) +visitExpression(hoistedAst.vmRuntime, function(node) if node.kind == Ast.AstKind.StringExpression and not runtimeStringSet[node.value] then runtimeStringSet[node.value], runtimeStringCount = true, runtimeStringCount + 1 end @@ -234,8 +233,8 @@ assert(countKind(hoistedAst.vmRuntime, Ast.AstKind.StringExpression) == 0, local runtimeCache = hoistedAst.vmRuntime.body.statements assert(runtimeCache[1].kind == Ast.AstKind.LocalVariableDeclaration and runtimeCache[2].kind == Ast.AstKind.LocalVariableDeclaration - and #runtimeCache[1].ids > 0 and #runtimeCache[1].expressions > 0 - and #runtimeCache[2].ids > 0 and #runtimeCache[2].expressions > 0, + and #runtimeCache[1].symbols > 0 and #runtimeCache[1].expressions > 0 + and #runtimeCache[2].symbols > 0 and #runtimeCache[2].expressions > 0, 'Hoisted runtime cache locals were not initialized') local afterHoistCounts = controlFlowCounts(hoistedAst.vmRuntime) for key, value in pairs(runtimeCountsBeforeHoist) do @@ -248,7 +247,7 @@ visitast(hoistedAst, function(node) plaintextFound = true end end) -visitExpression(hoistedAst.vmData, hoistedAst, function(node) +visitExpression(hoistedAst.vmData, function(node) if node.kind == Ast.AstKind.StringExpression then encryptedDataStrings = encryptedDataStrings + 1 end end) assert(not plaintextFound and encryptedDataStrings > 0, @@ -263,7 +262,7 @@ for _, statement in ipairs(hoistedAst.body.statements) do end local encryptedCacheStrings = 0 if cacheInitializer then - visitExpression(cacheInitializer, hoistedAst, function(node) + visitExpression(cacheInitializer, function(node) if node.kind == Ast.AstKind.StringExpression then encryptedCacheStrings = encryptedCacheStrings + 1 end end) end @@ -287,7 +286,7 @@ assert(repeatedAst.vmRuntime == repeatedRuntime 'Repeated string encryption added empty runtime cache declarations') visitast(repeatedRuntime, function(node) if node.kind == Ast.AstKind.LocalVariableDeclaration then - assert(#node.ids > 0, 'Repeated hoisting emitted an empty local declaration') + assert(#node.symbols > 0, 'Repeated hoisting emitted an empty local declaration') end end) local repeatedChunk = compile('return 42,nil,...', { diff --git a/tests/harness/cff-strings.lua b/tests/harness/cff-strings.lua index 066c169a..789c98fd 100644 --- a/tests/harness/cff-strings.lua +++ b/tests/harness/cff-strings.lua @@ -16,6 +16,15 @@ local function inventory(ast) end) return counts end +local function declarationSymbolCount(ast) + local count = 0 + visitast(ast, function(node) + if node.kind == 'LocalVariableDeclaration' then + count = count + #(node.symbols or {}) + end + end) + return count +end local function unique(ast) local counts = inventory(ast) for value, count in pairs(counts) do @@ -28,9 +37,9 @@ local function pack(...) return {n = select('#', ...), ...} end -- Empty/unique inputs need no pool. Sharing is idempotent. for _, source in ipairs({'return ...', 'return "single", 7'}) do local ast = parser:parse(source) - local statements, variables = #ast.body.statements, #ast.body.scope.variables + local statements, symbols = #ast.body.statements, declarationSymbolCount(ast) assert(shareStrings(ast) == ast) - assert(#ast.body.statements == statements and #ast.body.scope.variables == variables) + assert(#ast.body.statements == statements and declarationSymbolCount(ast) == symbols) end local source = [[ local object = {message = 'shared', [''] = 'shared'} @@ -46,11 +55,13 @@ shareStrings(ast) local counts = unique(ast) assert(counts.shared == 1 and counts['a\000b'] == 1 and counts[''] == 1) local pool = ast.body.statements[1] -assert(pool.kind == 'LocalVariableDeclaration' and #pool.ids == 1) -local statements, variables = #ast.body.statements, #ast.body.scope.variables +assert(pool.kind == 'LocalVariableDeclaration' and #pool.symbols == 1) +local poolSymbol = pool.symbols[1] +local statements, symbols = #ast.body.statements, declarationSymbolCount(ast) shareStrings(ast) assert(ast.body.statements[1] == pool and #ast.body.statements == statements - and #ast.body.scope.variables == variables, 'sharing must be idempotent') + and pool.symbols[1] == poolSymbol and declarationSymbolCount(ast) == symbols, + 'sharing must be idempotent') local pipeline = Prometheus.Pipeline:fromConfig({LuaVersion='Lua51', Seed=17, Steps={}}) pipeline:renameVariables(ast) local chunk = assert(loadstring(unparser:unparse(ast))) diff --git a/tests/harness/vm-data.lua b/tests/harness/vm-data.lua index 334459c6..c1e08e02 100644 --- a/tests/harness/vm-data.lua +++ b/tests/harness/vm-data.lua @@ -106,11 +106,10 @@ visitast(blankAst, function(node) end end) assert(blankAst.vmData.kind == 'TableConstructorExpression') -local runtimeWrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({blankAst.vmRuntime})}, - blankAst.body.scope), blankAst.globalScope) +local runtimeWrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({blankAst.vmRuntime})})) visitast(runtimeWrapper, function(node) - if node.kind == 'VariableExpression' and not node.scope.isGlobal then - assert(node.scope:getVariableName(node.id) ~= 'rebuild', 'reconstruction leaked into runtime') + if node.kind == 'VariableExpression' then + assert((node.symbol and node.symbol.name or node.name) ~= 'rebuild', 'reconstruction leaked into runtime') end end) From cf305b2a6997713f901ebf1bbc648ed37b304446 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Mon, 2 Nov 2026 14:25:30 +0100 Subject: [PATCH 3/5] Fix repeated proxy passes and AST binding metadata edge cases --- doc/advanced/ast-bindings.md | 8 ++- src/prometheus/bindinganalysis.lua | 13 +++-- src/prometheus/steps/ProxifyLocals.lua | 23 ++++---- src/prometheus/unparser.lua | 10 ++-- tests/harness/ast-bindings.lua | 73 ++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 18 deletions(-) diff --git a/doc/advanced/ast-bindings.md b/doc/advanced/ast-bindings.md index 4f2fe481..e21b6782 100644 --- a/doc/advanced/ast-bindings.md +++ b/doc/advanced/ast-bindings.md @@ -23,6 +23,10 @@ local read = Ast.VariableExpression(value) local write = Ast.AssignmentVariable(value) ``` +Names are preferences: the unparser normalizes invalid identifier characters and +leading digits, and resolves collisions and reserved words while preserving symbol +identity. Valid names are retained when they are unambiguous. + The AST constructors are symbol-only. `Ast.LocalFunctionDeclaration(symbol, args, body)` declares a local function. `Ast.FunctionDeclaration(target, indices, args, body)` accepts a global reference or a local reference as its @@ -78,7 +82,9 @@ own initializer. Local functions are active while their bodies are analyzed, which allows recursion. Function arguments and loop variables are active in their function or loop body. A reference is valid only while its symbol is active on the enclosing lexical path. References from nested functions are -recorded as captures. +recorded as captures. Compound assignments record their local target as both a +read and a write, while retaining one reference occurrence. Updating an indexed +target reads its table and index bindings; it does not write those bindings. Analysis does not widen a lexical region to make a reference valid. If a transform moves a reference outside its symbol's declaration region, it must diff --git a/src/prometheus/bindinganalysis.lua b/src/prometheus/bindinganalysis.lua index ea3d6a62..c2e7ab8e 100644 --- a/src/prometheus/bindinganalysis.lua +++ b/src/prometheus/bindinganalysis.lua @@ -44,7 +44,7 @@ return function(ast) return id end - local function reference(node, scope, owner) + local function reference(node, scope, owner, readWrite) local target, id if node.kind == K.GlobalVariableExpression or node.kind == K.AssignmentGlobalVariable or (node.kind == K.FunctionDeclaration and not node.symbol) then @@ -65,6 +65,11 @@ return function(ast) local uses = writes and analysis.writes or analysis.reads uses[symbol] = uses[symbol] or {} uses[symbol][#uses[symbol] + 1] = node + if writes and readWrite then + analysis.reads[symbol] = analysis.reads[symbol] or {} + local reads = analysis.reads[symbol] + reads[#reads + 1] = node + end if analysis.owners[target] ~= owner then analysis.captured[symbol] = true end end node.scope, node.id = target, id @@ -83,11 +88,11 @@ return function(ast) for _, node in ipairs(list or {}) do expression(node, scope, owner) end end - function expression(node, scope, owner) + function expression(node, scope, owner, readWrite) local kind = node.kind if kind == K.VariableExpression or kind == K.AssignmentVariable or kind == K.GlobalVariableExpression or kind == K.AssignmentGlobalVariable then - reference(node, scope, owner) + reference(node, scope, owner, readWrite) elseif binary[kind] then expression(node.lhs, scope, owner); expression(node.rhs, scope, owner) elseif kind == K.NotExpression or kind == K.NegateExpression or kind == K.LenExpression then @@ -159,7 +164,7 @@ return function(ast) elseif kind == K.AssignmentStatement then expressions(node.lhs, scope, owner); expressions(node.rhs, scope, owner) elseif compound[kind] then - expression(node.lhs, scope, owner); expression(node.rhs, scope, owner) + expression(node.lhs, scope, owner, true); expression(node.rhs, scope, owner) elseif kind == K.ReturnStatement then expressions(node.args, scope, owner) elseif kind == K.FunctionCallStatement or kind == K.PassSelfFunctionCallStatement then expression(node.base, scope, owner); expressions(node.args, scope, owner) diff --git a/src/prometheus/steps/ProxifyLocals.lua b/src/prometheus/steps/ProxifyLocals.lua index 019d4b8f..0d7c3588 100644 --- a/src/prometheus/steps/ProxifyLocals.lua +++ b/src/prometheus/steps/ProxifyLocals.lua @@ -91,6 +91,9 @@ end function ProxifyLocals:apply(ast, pipeline) local localMetatableInfos = {} + -- Rewrite exemptions belong to this invocation, including when the same + -- step instance is applied repeatedly to an already transformed tree. + local skipped, ignored = {}, {} local function isGlobal(node) return node.kind == AstKind.GlobalVariableExpression or node.kind == AstKind.AssignmentGlobalVariable end @@ -114,7 +117,7 @@ function ProxifyLocals:apply(ast, pipeline) local emptyFunctionDeclaration = Ast.LocalVariableDeclaration({self.emptyFunctionSymbol}, { Ast.FunctionLiteralExpression({}, Ast.Block({})), }) - emptyFunctionDeclaration.__skipProxifyLocals = true + skipped[emptyFunctionDeclaration] = true table.insert(ast.body.statements, 1, emptyFunctionDeclaration) visitast(ast, function(node) @@ -128,14 +131,14 @@ function ProxifyLocals:apply(ast, pipeline) local info = getLocalMetatableInfo(statement.symbol) if info then local declaration = Ast.LocalVariableDeclaration({statement.symbol}, {}) - declaration.__skipProxifyLocals = true + skipped[declaration] = true local literal = Ast.FunctionLiteralExpression(statement.args, statement.body) local target = Ast.AssignmentVariable(statement.symbol) - target.__ignoreProxifyLocals = true + ignored[target] = true local assignment = Ast.AssignmentStatement({target}, { self:CreateAssignmentExpression(info, literal), }) - assignment.__skipProxifyLocals = true + skipped[assignment] = true node.statements[i] = declaration table.insert(node.statements, i + 1, assignment) i = i + 1 @@ -157,21 +160,21 @@ function ProxifyLocals:apply(ast, pipeline) end end - if node.kind == AstKind.AssignmentStatement and not node.__skipProxifyLocals + if node.kind == AstKind.AssignmentStatement and not skipped[node] and #node.lhs == 1 and node.lhs[1].kind == AstKind.AssignmentVariable then local variable = node.lhs[1] local info = not isGlobal(variable) and getLocalMetatableInfo(variable.symbol) if info then local args = shallowcopy(node.rhs) local reference = Ast.VariableExpression(variable.symbol) - reference.__ignoreProxifyLocals = true + ignored[reference] = true args[1] = info.setValue.constructor(reference, args[1]) self.emptyFunctionUsed = true return Ast.FunctionCallStatement(Ast.VariableExpression(self.emptyFunctionSymbol), args) end end end, function(node) - if node.kind == AstKind.LocalVariableDeclaration and not node.__skipProxifyLocals then + if node.kind == AstKind.LocalVariableDeclaration and not skipped[node] then for i, symbol in ipairs(node.symbols) do local info = getLocalMetatableInfo(symbol) if info then @@ -179,7 +182,7 @@ function ProxifyLocals:apply(ast, pipeline) end end end - if node.kind == AstKind.VariableExpression and not node.__ignoreProxifyLocals and not isGlobal(node) then + if node.kind == AstKind.VariableExpression and not ignored[node] and not isGlobal(node) then local info = getLocalMetatableInfo(node.symbol) if info then local literal @@ -190,10 +193,10 @@ function ProxifyLocals:apply(ast, pipeline) return info.getValue.constructor(node, literal) end end - if node.kind == AstKind.AssignmentVariable and not node.__ignoreProxifyLocals and not isGlobal(node) then + if node.kind == AstKind.AssignmentVariable and not ignored[node] and not isGlobal(node) then local info = getLocalMetatableInfo(node.symbol) if info then - node.__ignoreProxifyLocals = true + ignored[node] = true return Ast.AssignmentIndexing(node, Ast.StringExpression(info.valueName)) end end diff --git a/src/prometheus/unparser.lua b/src/prometheus/unparser.lua index 21adc1e3..6396fc6d 100644 --- a/src/prometheus/unparser.lua +++ b/src/prometheus/unparser.lua @@ -115,10 +115,14 @@ function Unparser:unparse(ast) renameVariables(ast, { Keywords = self.conventions.Keywords, generateName = function(_, scope, originalName) + -- Symbol names are preferences. Make them valid identifier spellings + -- before resolving collisions and reserved words in the allocator. + local base = originalName:gsub(self.notIdentPattern, "_"); + if #base == 0 or base:find(self.numberPattern) then base = "_" .. base; end attempts[scope] = attempts[scope] or {}; - local count = attempts[scope][originalName] or 0; - attempts[scope][originalName] = count + 1; - return count == 0 and originalName or originalName .. "_" .. count; + local count = attempts[scope][base] or 0; + attempts[scope][base] = count + 1; + return count == 0 and base or base .. "_" .. count; end, }); diff --git a/tests/harness/ast-bindings.lua b/tests/harness/ast-bindings.lua index ed6d1178..262e3a37 100644 --- a/tests/harness/ast-bindings.lua +++ b/tests/harness/ast-bindings.lua @@ -87,6 +87,34 @@ local info = analyze(ast) assert(info.captured[shared] and #info.reads[shared] == 1 and #info.writes[shared] == 1) assert(info.writes[shared][1] == write and run(ast)() == 42) +-- Compound targets are one occurrence with both a read and a write. Reading +-- the address of an indexed target does not write its table or index bindings. +for _, operator in ipairs({'+=', '-=', '*=', '/=', '%=', '^=', '..='}) do + local initial, operand = '1', '2' + if operator == '..=' then initial, operand = '"a"', '"b"' end + local compound = Parser:new({LuaVersion = 'LuaU'}):parse( + 'local x=' .. initial .. '; x ' .. operator .. ' ' .. operand .. '; return x') + local symbol = compound.body.statements[1].symbols[1] + local target = compound.body.statements[2].lhs + local analysis = compound.bindingAnalysis + assert(#analysis.reads[symbol] == 2 and #analysis.writes[symbol] == 1, operator) + assert(analysis.reads[symbol][1] == target and analysis.writes[symbol][1] == target) + assert(#analysis.references[symbol] == 2, 'Compound access must not duplicate reference occurrences') +end +local indexed = Parser:new({LuaVersion = 'LuaU'}):parse( + 'local t={1}; local i=1; t[i] += 2; return t[i]') +for i = 1, 2 do + local symbol = indexed.body.statements[i].symbols[1] + assert(#indexed.bindingAnalysis.reads[symbol] == 2) + assert(not indexed.bindingAnalysis.writes[symbol], 'Indexed updates must not write their address bindings') +end +local compoundCapture = Parser:new({LuaVersion = 'LuaU'}):parse( + 'local x=0; return function() x += 1; return x end') +local captured = compoundCapture.body.statements[1].symbols[1] +assert(compoundCapture.bindingAnalysis.captured[captured] + and #compoundCapture.bindingAnalysis.reads[captured] == 2 + and #compoundCapture.bindingAnalysis.writes[captured] == 1) + -- Global accesses have distinct kinds and never acquire local symbols. ast = parse('result = print; return result') assert(ast.body.statements[1].lhs[1].kind == K.AssignmentGlobalVariable) @@ -144,12 +172,57 @@ ast = tree({Ast.LocalVariableDeclaration({outer}, {Ast.NumberExpression(1)}), plain = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, Steps = {}}):unparse(ast) assert(assert(loadstring(plain))() == 1) +-- Preferred names can be arbitrary labels. Printing must choose valid names +-- without shadowing globals or merging bindings that normalize to the same base. +local Unparser = require('prometheus.unparser') +for _, version in ipairs({'Lua51', 'LuaU'}) do + local symbols, values, references = {}, {}, {} + for i, preferred in ipairs({'bad-name', 'bad name', 'bad_name', '1st', '', 'end', 'continue', 'normal_name'}) do + symbols[i], values[i] = Ast.LocalSymbol(preferred), Ast.NumberExpression(i) + references[i] = Ast.VariableExpression(symbols[i]) + end + references[#references + 1] = Ast.GlobalVariableExpression('bad_name') + local constructed = tree({Ast.LocalVariableDeclaration(symbols, values), Ast.ReturnStatement(references)}) + local unparser = Unparser:new({LuaVersion = version}) + local output = unparser:unparse(constructed) + for _, symbol in ipairs(symbols) do assert(unparser:isValidIdentifier(symbol.name)) end + assert(symbols[#symbols].name == 'normal_name', 'Valid preferred names should be preserved') + assert(unparser:unparse(constructed) == output, 'Name normalization must be stable across repeated printing') + local chunk = assert(loadstring(output)) + setfenv(chunk, {bad_name = 99}) + local result = {chunk()} + for i = 1, #symbols do assert(result[i] == i) end + assert(result[#symbols + 1] == 99, 'Normalized locals must not capture global accesses') +end + -- Lowering a recursive local function must preserve its declaration point. local proxy = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, Steps = {{Name = 'ProxifyLocals'}}}):apply( 'local function f(n) if n == 0 then return 1 end return f(n - 1) end return f(3)') assert(assert(loadstring(proxy))() == 1) +-- Repeated passes must transform earlier generated declarations and uses +-- consistently, including when a caller reuses the same step instance. +local proxySources = { + 'local function f() return 1 end return f()', + 'local function f(n) if n==0 then return 1 end return f(n-1) end return f(3)', + 'local x=0; local function f() x=x+1; return x end; f(); return f()', +} +for _, seed in ipairs({1, 4, 17}) do + for i, source in ipairs(proxySources) do + local pipeline = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = seed, + Steps = {{Name = 'ProxifyLocals'}, {Name = 'ProxifyLocals'}}}) + local chunk = assert(loadstring(pipeline:apply(source))) + assert(chunk() == (i == 3 and 2 or 1), 'Repeated ProxifyLocals changed function behavior') + end +end +local pipeline = Prometheus.Pipeline:fromConfig({LuaVersion = 'Lua51', Seed = 1, + Steps = {{Name = 'ProxifyLocals'}}}) +local reused, transformed = pipeline.steps[1], parse(proxySources[2]) +math.randomseed(1) +for i = 1, 3 do reused:apply(transformed, pipeline) end +assert(run(transformed) == 1, 'Reusing a step instance must not retain rewrite exemptions') + -- Steps must also operate on syntax with no cached scopes or numeric slots. local function stripMetadata(node, seen) if type(node) ~= 'table' or seen[node] then return end From 5495e69861211d678abf0cab02ae1acea1f921a0 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Mon, 2 Nov 2026 14:40:38 +0100 Subject: [PATCH 4/5] Preserve binding identities with direct AST bridge rewriting --- doc/advanced/ast-bindings.md | 4 ++ src/prometheus/cff/compile_top.lua | 2 +- src/prometheus/cff/native.lua | 78 ++++++++++++------------------ src/prometheus/vm/flatten.lua | 69 ++++++++++---------------- tests/harness/cff-native.lua | 69 ++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 93 deletions(-) diff --git a/doc/advanced/ast-bindings.md b/doc/advanced/ast-bindings.md index e21b6782..89329771 100644 --- a/doc/advanced/ast-bindings.md +++ b/doc/advanced/ast-bindings.md @@ -105,6 +105,10 @@ local binding, create a fresh `Ast.LocalSymbol` for the clone and point its cloned references to that symbol. Reusing the original symbol would make two declarations claim one identity. Global nodes can be cloned by name. +Native Cff wrappers and standalone interpreter flattening are built directly as +AST nodes. They preserve local symbols and use fresh symbols for bridge parameters; +printing and reparsing fragments must not be used to recover binding identities. + Lowering backends may attach private slot or scope metadata for their own implementation. Such fields are derived and backend-specific; AST transforms must use symbols and must not rely on those fields for binding identity. diff --git a/src/prometheus/cff/compile_top.lua b/src/prometheus/cff/compile_top.lua index 64cbc47c..8906869b 100644 --- a/src/prometheus/cff/compile_top.lua +++ b/src/prometheus/cff/compile_top.lua @@ -13,7 +13,7 @@ local AstKind = Ast.AstKind; return function(Compiler) function Compiler:compileTopNode(node) - if self.nativeExpressions then self:prepareNativeExpressions(node); end + if self.nativeExpressions then self:prepareNativeExpressions(); end local startBlock = self:createBlock(); self.startBlockId = startBlock.id; self:setActiveBlock(startBlock); diff --git a/src/prometheus/cff/native.lua b/src/prometheus/cff/native.lua index bd12e983..60ccd0c2 100644 --- a/src/prometheus/cff/native.lua +++ b/src/prometheus/cff/native.lua @@ -1,23 +1,21 @@ -- Keep selected expressions native, bridging captures through Cff's shared cells. local Ast = require('prometheus.ast') -local Parser = require('prometheus.parser') -local Unparser = require('prometheus.unparser') local Kind = Ast.AstKind -local function walk(expression, ast, previsit, postvisit) +local function walk(expression, previsit, postvisit) return require('prometheus.visitast')(Ast.TopNode(Ast.Block({ Ast.ReturnStatement({expression}), })), previsit, postvisit) end -local function captures(expression, ast) +local function captures(expression) local owned, variables, seen = {}, {}, {} - walk(expression, ast, function(node) - if node.kind == Kind.Block and node.scope and node.scope ~= ast.body.scope then + walk(expression, function(node) + if node.kind == Kind.Block and node.scope then owned[node.scope] = true end end) - walk(expression, ast, function(node) + walk(expression, function(node) local isGlobal = node.kind == Kind.GlobalVariableExpression or node.kind == Kind.AssignmentGlobalVariable or (node.kind == Kind.FunctionDeclaration and not node.symbol) @@ -36,10 +34,10 @@ local function captures(expression, ast) end return function(Compiler) - function Compiler:prepareNativeExpressions(ast) - self.nativeAst, self.nativeResults = ast, {} + function Compiler:prepareNativeExpressions() + self.nativeResults = {} for expression in pairs(self.nativeExpressions) do - local variables = captures(expression, ast) + local variables = captures(expression) for _, variable in ipairs(variables) do self:makeUpvalue(variable.scope, variable.id) end @@ -47,8 +45,7 @@ return function(Compiler) end function Compiler:compileNativeExpression(expression, funcDepth, numReturns) - local ast = self.nativeAst - local variables, lookup, owned = captures(expression, ast) + local variables, lookup, owned = captures(expression) local entries, reads = {}, {} for _, variable in ipairs(variables) do local depth = self.scopeFunctionDepths[variable.scope] @@ -64,41 +61,31 @@ return function(Compiler) entries[#entries + 1] = Ast.TableEntry(id) end - -- Fresh marker names avoid shadowing any names inside the native expression. - local used = {} - walk(expression, ast, function(node) - if node.scope then - for _, name in pairs(node.scope.variables) do used[name] = true end - end - end) - local names = {} - for _, label in ipairs({'proxy', 'env', 'values', 'setenv', 'result'}) do - local name = '__prometheus_native_' .. label - while used[name] do name = name .. '_' end - used[name], names[label] = true, name - end - local function marker(label) - return Ast.GlobalVariableExpression(names[label]) - end + -- The bridge parameters are bindings, so native locals can keep their + -- symbols even when preferred names collide with each other or helpers. + local proxySymbol = Ast.LocalSymbol('native_proxy') + local envSymbol = Ast.LocalSymbol('native_env') + local valuesSymbol = Ast.LocalSymbol('native_values') + local resultSymbol = Ast.LocalSymbol('native_result') local function access(node, assignment) local base, index if node.kind == Kind.GlobalVariableExpression or node.kind == Kind.AssignmentGlobalVariable or (node.kind == Kind.FunctionDeclaration and not node.symbol) then local name = node.name - if name == '_ENV' and not assignment then return marker('env') end - base, index = marker('env'), Ast.StringExpression(name) + if name == '_ENV' and not assignment then return Ast.VariableExpression(envSymbol) end + base, index = Ast.VariableExpression(envSymbol), Ast.StringExpression(name) else local symbol = node.symbol local scope, id = symbol and symbol.scope, symbol and symbol.id local slot = scope and lookup[scope] and lookup[scope][id] if not slot then return end - base = marker('values') - index = Ast.IndexExpression(marker('proxy'), Ast.NumberExpression(slot)) + base = Ast.VariableExpression(valuesSymbol) + index = Ast.IndexExpression(Ast.VariableExpression(proxySymbol), Ast.NumberExpression(slot)) end if assignment then return Ast.AssignmentIndexing(base, index) end return Ast.IndexExpression(base, index) end - walk(expression, ast, nil, function(node) + walk(expression, nil, function(node) if node.kind == Kind.VariableExpression or node.kind == Kind.GlobalVariableExpression then return access(node, false) end if node.kind == Kind.AssignmentVariable or node.kind == Kind.AssignmentGlobalVariable then return access(node, true) end if node.kind == Kind.FunctionDeclaration and not owned[node.scope] then @@ -113,23 +100,18 @@ return function(Compiler) end end) - local source = Unparser:new({LuaVersion = 'Lua51'}):unparseExpression(expression, 0) - local finish = '' + local statements = {Ast.LocalVariableDeclaration({resultSymbol}, {expression})} if expression.kind == Kind.FunctionLiteralExpression then - finish = 'if ' .. names.setenv .. ' then ' .. names.setenv .. '(' .. names.result - .. ',' .. names.env .. ') end;' + statements[#statements + 1] = Ast.IfStatement( + Ast.VariableExpression(self.setfenvVar), Ast.Block({ + Ast.FunctionCallStatement(Ast.VariableExpression(self.setfenvVar), { + Ast.VariableExpression(resultSymbol), Ast.VariableExpression(envSymbol), + }), + }), {}) end - local parsed = Parser:new({LuaVersion = 'Lua51', AllowInternalIdentifiers = true}):parse('return function(' - .. names.proxy .. ',' .. names.env .. ',' .. names.values .. ') local ' - .. names.result .. '=' .. source .. ';' .. finish .. 'return ' .. names.result .. ' end') - local factory = parsed.body.statements[1].args[1] - self.nativeResults[expression] = factory.body.statements[1].expressions[1] - require('prometheus.visitast')(parsed, nil, function(node, data) - if node.kind == Kind.GlobalVariableExpression then - assert(node.name == names.setenv, 'Unexpected native global') - return Ast.VariableExpression(self.setfenvVar) - end - end) + statements[#statements + 1] = Ast.ReturnStatement({Ast.VariableExpression(resultSymbol)}) + local factory = Ast.FunctionLiteralExpression({proxySymbol, envSymbol, valuesSymbol}, Ast.Block(statements)) + self.nativeResults[expression] = expression local proxy = Ast.NilExpression() if #entries > 0 then proxy = Ast.FunctionCallExpression(Ast.VariableExpression(self.upvaluesProxyFunctionVar), {Ast.TableConstructorExpression(entries)}) diff --git a/src/prometheus/vm/flatten.lua b/src/prometheus/vm/flatten.lua index 78e5a9fe..3371dc97 100644 --- a/src/prometheus/vm/flatten.lua +++ b/src/prometheus/vm/flatten.lua @@ -1,10 +1,7 @@ -- Flatten a generated interpreter without virtualizing its large data initializer. local Ast = require('prometheus.ast') local visitast = require('prometheus.visitast') -local Parser = require('prometheus.parser') -local Unparser = require('prometheus.unparser') local CffCompiler = require('prometheus.cff.compiler') -local Enums = require('prometheus.enums') local analyzeBindings = require('prometheus.bindinganalysis') local function externalVariables(ast, runtime) @@ -43,54 +40,38 @@ return function(ast) local runtime = ast.vmRuntime local variables, writes = externalVariables(ast, runtime) if writes then return nil end - local parser = Parser:new({LuaVersion = Enums.LuaVersion.Lua51, AllowInternalIdentifiers = true}) - local unparser = Unparser:new({LuaVersion = Enums.LuaVersion.Lua51}) - -- Serialize captures through fresh marker names, then reconnect the parsed - -- forwarding references to their original symbols. Textual name resolution - -- would silently rebind them when surrounding locals are renamed. - local used, markers, captures = {}, {}, {} - visitast(ast, function(node) - if node.scope then - for _, name in pairs(node.scope.variables) do used[name] = true end - end - if node.name then used[node.name] = true end - end) - local markerNames = {} + -- Give standalone captures fresh parameter bindings. The forwarding reads + -- below still point to the actual outer symbols, without textual rebinding. + local parameters, replacements = {}, {} + local forwarded = {} for i, symbol in ipairs(variables) do - local name = '__prometheus_capture_' .. i - while used[name] do name = name .. '_' end - used[name], markers[symbol], captures[name], markerNames[i] = true, name, symbol, name + local parameter = Ast.LocalSymbol('runtime_capture_' .. i) + parameters[i], replacements[symbol] = parameter, parameter + forwarded[i] = Ast.VariableExpression(symbol) end + forwarded[#forwarded + 1] = Ast.VarargExpression() local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})})) visitast(wrapper, nil, function(node) - local name = markers[node.symbol] - if name then - if node.kind == 'VariableExpression' then return Ast.GlobalVariableExpression(name) end - if node.kind == 'FunctionDeclaration' then - node.symbol, node.name = nil, name - node.scope, node.id = nil, nil - end + local parameter = replacements[node.symbol] + if parameter then + if node.kind == 'VariableExpression' then return Ast.VariableExpression(parameter) end + if node.kind == 'FunctionDeclaration' then node.symbol = parameter end end end) - local runtimeSource = unparser:unparseExpression(runtime, 0) - local prefix = '' - local arguments = '...' - if #variables > 0 then - prefix = 'local ' .. table.concat(markerNames, ',') .. '=...;' - arguments = 'select(' .. (#variables + 1) .. ',...)' + local statements, arguments = {}, {Ast.VarargExpression()} + if #parameters > 0 then + statements[1] = Ast.LocalVariableDeclaration(parameters, {Ast.VarargExpression()}) + arguments = {Ast.FunctionCallExpression(Ast.GlobalVariableExpression('select'), { + Ast.NumberExpression(#parameters + 1), Ast.VarargExpression(), + })} end - local standalone = parser:parse(prefix .. 'return (' .. runtimeSource .. ')(' .. arguments .. ')') - local flattened = unparser:unparse(CffCompiler:new():compile(standalone)) - local forwarded = #variables > 0 and table.concat(markerNames, ',') .. ',...' or '...' - local replacementSource = 'return function(...) return (function(...) ' - .. flattened .. ' end)(' .. forwarded .. ') end' - local replacement = parser:parse(replacementSource).body.statements[1].args[1] - local replacementAst = Ast.TopNode(Ast.Block({Ast.ReturnStatement({replacement})})) - visitast(replacementAst, nil, function(node) - if node.kind == 'GlobalVariableExpression' and captures[node.name] then - return Ast.VariableExpression(captures[node.name]) - end - end) + statements[#statements + 1] = Ast.ReturnStatement({Ast.FunctionCallExpression(runtime, arguments)}) + local standalone = Ast.TopNode(Ast.Block(statements)) + local flattened = CffCompiler:new():compile(standalone) + local replacement = Ast.FunctionLiteralExpression({Ast.VarargExpression()}, Ast.Block({ + Ast.ReturnStatement({Ast.FunctionCallExpression( + Ast.FunctionLiteralExpression({Ast.VarargExpression()}, flattened.body), forwarded)}), + })) visitast(ast, function(node) if node == runtime then return replacement, true end end) diff --git a/tests/harness/cff-native.lua b/tests/harness/cff-native.lua index 92b1166a..823ec572 100644 --- a/tests/harness/cff-native.lua +++ b/tests/harness/cff-native.lua @@ -307,4 +307,73 @@ assert(result.n == 5 and result[1] == plaintext and result[2] == plaintext and result[3] == 'hoist_second_value' and result[4] == 'tail' and result[5] == nil, 'Final Medium pipeline changed hoisted payload strings or result arity') +-- Native expressions retain binding identity instead of recovering bindings +-- from printed names. Exercise functions and tables containing functions, with +-- names that also collide with the environment bridge's preferred name. +do + local parser = Parser:new({LuaVersion = 'Lua51'}) + local unparser = Unparser:new({LuaVersion = 'Lua51'}) + for _, seed in ipairs({1, 4, 17}) do + for _, tableCase in ipairs({false, true}) do + local source = 'function() local first=1; local second=2; return first,second,_VERSION end' + local ast = parser:parse('return ' .. (tableCase and '{' .. source .. '}' or source)) + local native = ast.body.statements[1].args[1] + local fn = tableCase and native.entries[1].value or native + for i = 1, 2 do fn.body.statements[i].symbols[1].name = 'native_env' end + local compiler = CffCompiler:new() + compiler.nativeExpressions = {[native] = true} + math.randomseed(seed) + local compiled = compiler:compile(ast) + local value = assert(loadstring(unparser:unparse(compiled)))() + local result = pack((tableCase and value[1] or value)()) + assert(result.n == 3 and result[1] == 1 and result[2] == 2 and result[3] == _VERSION, + 'Native Cff merged distinct symbols with colliding preferred names') + end + end +end + +-- The standalone flattening bridge uses fresh capture bindings and forwards +-- actual outer symbols. All source locals may share a preferred name, including +-- the generated capture parameter's name; trailing nil arguments must survive. +do + local flatten = require('prometheus.vm.flatten') + local parser = Parser:new({LuaVersion = 'Lua51'}) + local unparser = Unparser:new({LuaVersion = 'Lua51'}) + for _, seed in ipairs({1, 4, 17}) do + local ast = parser:parse([[ + local outside=10; local other=20 + return (function(...) + local first=1; local second=2 + return outside,other,first,second,... + end)(...) + ]]) + local runtime = ast.body.statements[3].args[1].base + for i = 1, 2 do + ast.body.statements[i].symbols[1].name = 'runtime_capture_1' + runtime.body.statements[i].symbols[1].name = 'runtime_capture_1' + end + ast.vmRuntime = runtime + math.randomseed(seed) + local compiled = assert(flatten(ast)) + local result = pack(assert(loadstring(unparser:unparse(compiled)))(nil, 3, nil)) + assert(result.n == 7 and result[1] == 10 and result[2] == 20 + and result[3] == 1 and result[4] == 2 and result[5] == nil + and result[6] == 3 and result[7] == nil, + 'Flattening changed local bindings, captures, or argument arity') + end + local ast = parser:parse([[ + local object={} + local result=(function() + function object.run(value) return value+1 end + return object + end)() + return result==object,object.run(41) + ]]) + ast.vmRuntime = ast.body.statements[2].expressions[1].base + math.randomseed(1) + local compiled = assert(flatten(ast)) + local same, value = assert(loadstring(unparser:unparse(compiled)))() + assert(same and value == 42, 'Flattening detached a captured table or its method target') +end + print('Cff native VM exclusion and captured closure checks passed') From 36bf1296547c3ef38a515caff1fbe1436e5aaadf Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Mon, 2 Nov 2026 14:42:47 +0100 Subject: [PATCH 5/5] Remove GitHub README override --- .github/README.md | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 .github/README.md diff --git a/.github/README.md b/.github/README.md deleted file mode 100644 index ed385aa8..00000000 --- a/.github/README.md +++ /dev/null @@ -1,41 +0,0 @@ -CI runs the full Lua suite for every PR targeting `master`. The shared -`actions/validate` action records the checked-out Git tree in a small artifact -named with the tree hash and run attempt. This identifies what was tested; only -a successful `Test.yml` workflow run makes it a passing result. - -On a push to `master`, validation locates the merged PR's latest CI run. It -reuses that run only if its recorded tree matches the new `master` tree exactly. -This works across merge, squash, and rebase methods when the resulting files -are identical. Direct pushes, changed merge results, and missing or expired -artifacts fall back to the full suite. - -On a `v*` tag, release validation looks for the latest `master` CI run for the -exact tagged commit, including annotated tags. It requires the same tree proof -and a successful conclusion. A queued or running CI run is awaited for up to -70 minutes before falling back to tests. Unavailable API results, cancelled -runs, and missing evidence also fall back to tests. A known failed validation -for the same source blocks progression; fix or rerun the failed CI run first. -Older successful runs never override a newer matching failure. - -Successful fallback tests let the workflow continue. Each validation attempt -records its own source, so a master fallback can be reused by a subsequent tag. -Artifacts are retained for 90 days; older releases can still proceed by testing -again. Existing CI runs without validation artifacts also require fresh tests. -Recording an artifact is best effort: if uploading it fails, validation still -proceeds, and later stages fall back to tests when they cannot find the record. - -Every reused result emits a notice, an explicit skipped-suite step, and a -workflow summary explaining the skip, with the matching tree, source run URL, -and attempt number. Fallback and blocked decisions also explain their reason -in the summary. - -Release packages are built on Linux and macOS, then extracted into a temporary -directory and smoke-tested (version, help, obfuscation, and execution using the -bundled runtime). Publishing requires both packages and their smoke tests to -succeed. The normal PR → merge → tag → release path runs the full suite once. - -Run the validation policy tests locally with: - -```sh -node --test scripts/ci/reuse-validation.test.cjs -```