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 -``` 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..89329771 --- /dev/null +++ b/doc/advanced/ast-bindings.md @@ -0,0 +1,114 @@ +# AST Bindings + +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 one symbol for each local declaration and reuse it for every read and +write of that binding: + +```lua +local Ast = require("prometheus.ast") +local value = Ast.LocalSymbol("value") + +local declaration = Ast.LocalVariableDeclaration({value}, { + Ast.NumberExpression(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 +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. + +Old scope-plus-numeric-ID construction signatures have been removed. Do not +pass scopes or IDs to AST constructors. + +## Globals + +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 + +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. 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 +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 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. + +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/doc/advanced/register-vm.md b/doc/advanced/register-vm.md index ba712367..1e5a9550 100644 --- a/doc/advanced/register-vm.md +++ b/doc/advanced/register-vm.md @@ -1,12 +1,24 @@ # 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 -- `vm/analysis.lua`: lexical scope ownership 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, value lists, calls, branches, and table constructors. diff --git a/src/prometheus/ast.lua b/src/prometheus/ast.lua index ef390dd7..5746ee13 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; @@ -159,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) @@ -193,30 +192,16 @@ function Ast.TableConstructorExpression(entries) end -- Create Statement Block -function Ast.Block(statements, scope) - 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) @@ -318,47 +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) - return { - 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) - return { - 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) @@ -371,42 +330,25 @@ function Ast.IfStatement(condition, body, elseifs, elsebody) } end -function Ast.FunctionDeclaration(scope, id, indices, args, body) - return { - kind = AstKind.FunctionDeclaration, - scope = scope, - baseScope = scope, - id = id, - baseId = id, - indices = indices, - args = args, - body = body, - getName = function(self) - return self.scope:getVariableName(self.id); - 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 {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) - return { - kind = AstKind.LocalFunctionDeclaration, - scope = scope, - id = id, - args = args, - body = body, - getName = function(self) - return self.scope:getVariableName(self.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 {kind = AstKind.LocalFunctionDeclaration, symbol = symbol, args = args, body = body, + getName = function(self) return self.symbol.name end}; end -function Ast.LocalVariableDeclaration(scope, ids, expressions) - return { - 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() @@ -769,31 +711,41 @@ function Ast.FunctionCallExpression(base, args) } end -function Ast.VariableExpression(scope, id) - scope:addReference(id); - return { - kind = AstKind.VariableExpression, - scope = scope, - id = id, - getName = function(self) - return self.scope.getVariableName(self.id); - end, - } +-- A symbol identifies a binding, independently of its name or lexical owner. +local nextSymbol = 0; +function Ast.LocalSymbol(name) + nextSymbol = nextSymbol + 1; + return {kind = "LocalSymbol", name = name or ("__prometheus_local_" .. nextSymbol)}; 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, - } +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) + return {kind = AstKind.GlobalVariableExpression, name = name}; +end + +function Ast.AssignmentGlobalVariable(name) + return {kind = AstKind.AssignmentGlobalVariable, name = name}; end +function Ast.VariableExpression(symbol) + return localReference(AstKind.VariableExpression, symbol); +end + +function Ast.AssignmentVariable(symbol) + return localReference(AstKind.AssignmentVariable, symbol); +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..c2e7ab8e --- /dev/null +++ b/src/prometheus/bindinganalysis.lua @@ -0,0 +1,208 @@ +-- 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 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 = {isGlobal = true} + ast.globalScope = global + local analysis = {owners = {}, captured = {}, declarations = {}, references = {}, reads = {}, writes = {}} + local blocks, active, loops = {}, {}, {} + local expression, statement, block, functionBody + + local function initialize(scope, parent, owner) + scope.parentScope, scope.level = parent, parent and parent.level + 1 or 0 + scope.children, scope.referenceCounts, scope.variablesFromHigherScopes = {}, {}, {} + scope.variables, scope.variablesLookup, scope.symbols = {}, {}, {} + 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, readWrite) + 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 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 + 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, 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, 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 + 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 = {isGlobal = false} + 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, 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) + 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 + 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/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 b3a77c8b..8906869b 100644 --- a/src/prometheus/cff/compile_top.lua +++ b/src/prometheus/cff/compile_top.lua @@ -13,15 +13,16 @@ 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(); - local scope = startBlock.scope; self.startBlockId = startBlock.id; self:setActiveBlock(startBlock); local varAccessLookup = lookupify{ AstKind.AssignmentVariable, + AstKind.AssignmentGlobalVariable, AstKind.VariableExpression, + AstKind.GlobalVariableExpression, AstKind.FunctionDeclaration, AstKind.LocalFunctionDeclaration, } @@ -49,15 +50,12 @@ 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: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 @@ -86,16 +84,14 @@ 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), {}); + 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); - oldActiveBlock.scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar); - 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; @@ -105,30 +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) 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); + 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); - 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); + self:addStatement(self:setRegister(argReg, Ast.IndexExpression(Ast.VariableExpression(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), { + 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); @@ -137,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 @@ -150,32 +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 - scope:addReferenceToHigherScope(self.scope, self.createVarargClosureVar); - 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)); - scope:addReferenceToHigherScope(varScope, var); - 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 @@ -183,17 +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 - scope:addReferenceToHigherScope(self.scope, self.freeUpvalueFunc); - 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 305799bf..e8aca3c7 100644 --- a/src/prometheus/cff/compiler.lua +++ b/src/prometheus/cff/compiler.lua @@ -5,7 +5,7 @@ -- This Script is the main compiler module. local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); +local analyzeBindings = require("prometheus.bindinganalysis"); local util = require("prometheus.util"); local lookupify = util.lookupify; @@ -83,6 +83,7 @@ function Compiler:popRegisterUsageInfo() end function Compiler:compile(ast) + analyzeBindings(ast); self.blocks = {}; self.registers = {}; self.activeBlock = nil; @@ -98,80 +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"); - - 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(); - 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 = {}; @@ -182,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 @@ -192,159 +165,149 @@ 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); + + + 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(); - 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 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); @@ -359,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({ @@ -398,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, { @@ -412,100 +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) - scope:addReferenceToHigherScope(self.scope, self.getfenvVar, 2); - scope:addReferenceToHigherScope(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) - 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), - 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 createClosurePosArg = createClosureScope:addVariable(); - local createClosureUpvalsArg = createClosureScope:addVariable(); - local createClosureEnvArg = createClosureScope:addVariable(); - 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 var = Ast.LocalSymbol(); + + + 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 b1469b1d..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,35 +255,30 @@ 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; -- For single block if len == 1 then - tb[l].block.scope:setParent(pScope); return tb[l].block; end -- For small ranges, use elseif chain if len <= 4 then - local ifScope = Scope:new(pScope); 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 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 - tb[i].block.scope:setParent(ifScope); - 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 @@ -292,12 +286,11 @@ return function(Compiler) end -- Last block becomes else - tb[r].block.scope:setParent(ifScope); local elseBlock = tb[r].block; return Ast.Block({ Ast.IfStatement(firstCondition, firstBlock, elseifs, elseBlock); - }, ifScope); + }); end -- For larger ranges, use binary split with and/or chaining @@ -307,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); @@ -319,33 +310,26 @@ 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 - self.whileScope:setParent(self.containerFuncScope); - end + local whileBody = buildElseifChain(blocks, 1, #blocks); - 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, @@ -360,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.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/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 502dc86d..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,14 +29,13 @@ 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); + 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 cab95386..60ccd0c2 100644 --- a/src/prometheus/cff/native.lua +++ b/src/prometheus/cff/native.lua @@ -1,30 +1,32 @@ -- 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}), - }, ast.body.scope), ast.globalScope), previsit, postvisit) + })), 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 ~= 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) if (node.kind == Kind.VariableExpression or node.kind == Kind.AssignmentVariable - 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 + 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) @@ -32,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 @@ -43,60 +45,49 @@ return function(Compiler) end function Compiler:compileNativeExpression(expression, funcDepth, numReturns) - local ast, scope = self.nativeAst, self.activeBlock.scope - 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] 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) - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentUpvaluesVar) - 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 - -- 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) - local _, id = ast.globalScope:resolve(names[label]) - return Ast.VariableExpression(ast.globalScope, id) - 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.scope.isGlobal then - local name = node.scope:getVariableName(node.id) - if name == '_ENV' and not assignment then return marker('env') end - base, index = marker('env'), Ast.StringExpression(name) + 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 Ast.VariableExpression(envSymbol) end + base, index = Ast.VariableExpression(envSymbol), 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)) + 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) - if node.kind == Kind.VariableExpression then return access(node, false) end - if node.kind == Kind.AssignmentVariable then return access(node, true) end + 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 local target = access(node, false) if target then @@ -109,41 +100,30 @@ 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] - factory.body.scope:setParent(scope) - require('prometheus.visitast')(parsed, nil, function(node, data) - if node.kind == Kind.VariableExpression 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) + 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 - scope:addReferenceToHigherScope(self.scope, self.upvaluesProxyFunctionVar) - proxy = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, - self.upvaluesProxyFunctionVar), {Ast.TableConstructorExpression(entries)}) + proxy = Ast.FunctionCallExpression(Ast.VariableExpression(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 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 f7fba7dc..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,87 +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); - scope:addReferenceToHigherScope(self.containerFuncScope, vid); - return Ast.VariableExpression(self.containerFuncScope, vid); + return Ast.VariableExpression(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)); + 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); - scope:addReferenceToHigherScope(self.containerFuncScope, vid); - return Ast.AssignmentVariable(self.containerFuncScope, vid); + return Ast.AssignmentVariable(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)); + 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 @@ -181,83 +176,68 @@ return function(Compiler) self.registers = {}; end - function Compiler:pos(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); - return Ast.VariableExpression(self.containerFuncScope, self.posVar); + function Compiler:pos() + return Ast.VariableExpression(self.posVar); end - function Compiler:posAssignment(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); - return Ast.AssignmentVariable(self.containerFuncScope, self.posVar); + function Compiler:posAssignment() + return Ast.AssignmentVariable(self.posVar); end - function Compiler:args(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.argsVar); - return Ast.VariableExpression(self.containerFuncScope, self.argsVar); + function Compiler:args() + return Ast.VariableExpression(self.argsVar); end - function Compiler:unpack(scope) - scope:addReferenceToHigherScope(self.scope, self.unpackPackedVar); - return Ast.VariableExpression(self.scope, self.unpackPackedVar); + function Compiler:unpack() + return Ast.VariableExpression(self.unpackPackedVar); end - function Compiler:pack(scope, values) - scope:addReferenceToHigherScope(self.scope, self.packVar); - 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) - scope:addReferenceToHigherScope(self.containerFuncScope, self.currentEnvVar); - return Ast.VariableExpression(self.containerFuncScope, self.currentEnvVar); + function Compiler:env() + return Ast.VariableExpression(self.currentEnvVar); end - function Compiler:functionCall(scope, reg, args) - scope:addReferenceToHigherScope(self.scope, self.callFunctionVar); - 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) - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); - 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 - scope:addReferenceToHigherScope(self.containerFuncScope, self.posVar); - return Ast.AssignmentStatement({Ast.AssignmentVariable(self.containerFuncScope, self.posVar)}, {Ast.NilExpression()}); + return Ast.AssignmentStatement({Ast.AssignmentVariable(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()}); + return Ast.AssignmentStatement({Ast.AssignmentVariable(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}); + function Compiler:setReturn(val) + return Ast.AssignmentStatement({Ast.AssignmentVariable(self.returnVar)}, {val}); end - function Compiler:getReturn(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.returnVar); - return Ast.VariableExpression(self.containerFuncScope, self.returnVar); + function Compiler:getReturn() + return Ast.VariableExpression(self.returnVar); end - function Compiler:returnAssignment(scope) - scope:addReferenceToHigherScope(self.containerFuncScope, self.returnVar); - return Ast.AssignmentVariable(self.containerFuncScope, self.returnVar); + function Compiler:returnAssignment() + return Ast.AssignmentVariable(self.returnVar); end - function Compiler:setUpvalueMember(scope, idExpr, valExpr, compoundConstructor) - scope:addReferenceToHigherScope(self.scope, self.upvaluesTable); + 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) - scope:addReferenceToHigherScope(self.scope, self.upvaluesTable); - 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 fba6b458..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 @@ -71,37 +70,36 @@ 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.scope.isGlobal then + if primaryExpr.kind == AstKind.AssignmentVariable or primaryExpr.kind == AstKind.AssignmentGlobalVariable 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); - 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); + 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 87416ec5..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,16 +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 - scope:addReferenceToHigherScope(self.scope, self.freeUpvalueFunc); - 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 059a1305..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,35 +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); - 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); + 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 b2c7173c..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,16 +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 - scope:addReferenceToHigherScope(self.scope, self.freeUpvalueFunc); - 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 3a489321..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,46 +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); - 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); + 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 597fd9d2..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,106 +20,99 @@ 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. -- 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"}, {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 - 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); + 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 cf0f22ff..29588516 100644 --- a/src/prometheus/cff/statements/function_declaration.lua +++ b/src/prometheus/cff/statements/function_declaration.lua @@ -7,28 +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); - 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); + self:addStatement(self:setRegister(tblReg, self:getUpvalueMember(Ast.IndexExpression(Ast.VariableExpression(self.currentUpvaluesVar), Ast.NumberExpression(upvalId)))), {tblReg}, {}, true); end end @@ -37,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 @@ -45,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); @@ -55,27 +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); - 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); + 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 d377afa6..2c62a690 100644 --- a/src/prometheus/cff/statements/local_function_declaration.lua +++ b/src/prometheus/cff/statements/local_function_declaration.lua @@ -7,20 +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); - 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: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 56ece645..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,13 +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); - 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: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 4da955e6..e20d316a 100644 --- a/src/prometheus/cff/statements/repeat_statement.lua +++ b/src/prometheus/cff/statements/repeat_statement.lua @@ -7,31 +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 - 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); + 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 ebdda11d..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,13 +34,12 @@ 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)) + 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 14bf1260..b0a7ac87 100644 --- a/src/prometheus/cff/upvalue.lua +++ b/src/prometheus/cff/upvalue.lua @@ -5,178 +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); - 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))}), + 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); - ifScope:addReferenceToHigherScope(scope, argVar, 3); - scope:addReferenceToHigherScope(self.scope, self.upvaluesReferenceCountsTable, 2); - 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); - scope:addReferenceToHigherScope(self.scope, self.newproxyVar); - - 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); - 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({ + 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); - scope:addReferenceToHigherScope(self.scope, self.currentUpvalId, 4); - scope:addReferenceToHigherScope(self.scope, self.upvaluesReferenceCountsTable, 1); - 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 f863e9e4..bf65bbd7 100644 --- a/src/prometheus/parser.lua +++ b/src/prometheus/parser.lua @@ -16,7 +16,7 @@ local Tokenizer = require("prometheus.tokenizer"); local Enums = require("prometheus.enums"); local util = require("prometheus.util"); local Ast = require("prometheus.ast"); -local Scope = require("prometheus.scope"); +local analyzeBindings = require("prometheus.bindinganalysis"); local logger = require("logger"); local AstKind = Ast.AstKind; @@ -26,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{ @@ -37,6 +64,7 @@ local ASSIGNMENT_NO_WARN_LOOKUP = lookupify{ local CALLABLE_PREFIX_EXPRESSION_LOOKUP = lookupify{ AstKind.VariableExpression, + AstKind.GlobalVariableExpression, AstKind.IndexExpression, AstKind.FunctionCallExpression, AstKind.PassSelfFunctionCallExpression @@ -148,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); @@ -163,13 +191,15 @@ 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 -- 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 @@ -180,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) @@ -197,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 @@ -207,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 @@ -221,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; @@ -229,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); @@ -281,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 @@ -309,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); @@ -319,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); @@ -360,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"); @@ -369,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: @@ -379,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"); @@ -402,12 +430,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 +490,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 @@ -515,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 @@ -542,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; @@ -556,8 +588,7 @@ function Parser:funcName(scope) end return { - scope = baseScope, - id = baseId, + target = target, indices = indices, passSelf = passSelf, token = ident, @@ -741,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"); @@ -766,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 @@ -778,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 @@ -938,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 bfc85e18..2bfcd6b3 100644 --- a/src/prometheus/pipeline.lua +++ b/src/prometheus/pipeline.lua @@ -8,6 +8,8 @@ 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"); @@ -233,6 +235,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 @@ -262,6 +265,7 @@ function Pipeline:unparse(ast) end function Pipeline:renameVariables(ast) + analyzeBindings(ast); local startTime = gettime(); logger:info("Renaming Variables ..."); @@ -278,8 +282,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 3f01b953..00000000 --- a/src/prometheus/scope.lua +++ /dev/null @@ -1,334 +0,0 @@ --- This Script is Part of the Prometheus Obfuscator by levno-710 --- --- scope.lua --- --- This Script provides a class for the Scope of a Lua Script - -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 = {}, - referenceCounts = {}; - variablesLookup = {}, - variablesFromHigherScopes = {}, - skipIdLookup = {}; - name = name or nextName(), - children = {}, - level = parentScope.level and (parentScope.level + 1) or 1; - } - - setmetatable(scope, self); - self.__index = self; - parentScope:addChild(scope); - return scope; -end - --- Create a new Global Scope -function Scope:newGlobal() - local scope = { - isGlobal = true, - parentScope = nil, - variables = {}, - 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 - -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) - 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: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 - 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); - self:addReferenceToHigherScope(scope, id, nil, true); - 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); - self:addReferenceToHigherScope(scope, id, nil, true); - return scope, 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 - --- 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 --- 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; - 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/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..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"); @@ -141,14 +140,12 @@ 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.VariableExpression(wrappers.symbol), Ast.StringExpression(wrapper.index) ), args); else - data.scope:addReferenceToHigherScope(self.rootScope, self.wrapperId); - return Ast.FunctionCallExpression(Ast.VariableExpression(self.rootScope, self.wrapperId), { + return Ast.FunctionCallExpression(Ast.VariableExpression(self.wrapperSymbol), { Ast.NumberExpression(index - self.wrapperOffset); }); end @@ -203,14 +200,12 @@ 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.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; + 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) @@ -275,19 +270,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.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; + 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 - data.scope:removeReferenceToHigherScope(node.scope, node.id); + if(node.name == "LOOKUP_TABLE") then return self:createBase64Lookup(); end end @@ -364,19 +354,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.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; + 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 - data.scope:removeReferenceToHigherScope(node.scope, node.id); + if(node.name == "LOOKUP_TABLE") then return self:createBase85Lookup(); end end @@ -489,24 +474,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.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; + 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 - data.scope:removeReferenceToHigherScope(node.scope, node.id); + if(node.name == "LOOKUP_TABLE_64") then return self:createBase64Lookup(); end - if(node.scope:getVariableName(node.id) == "LOOKUP_TABLE_85") then - data.scope:removeReferenceToHigherScope(node.scope, node.id); + if(node.name == "LOOKUP_TABLE_85") then return self:createBase85Lookup(); end end @@ -620,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", @@ -672,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 @@ -728,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 @@ -744,32 +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 - funcScope:addReferenceToHigherScope(self.rootScope, self.wrapperId); - 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) })); @@ -781,31 +750,27 @@ function ConstantArray:apply(ast, pipeline) local steps = util.shuffle({ -- 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 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) @@ -828,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 7a40c21f..6bd0b823 100644 --- a/src/prometheus/steps/EncryptStrings.lua +++ b/src/prometheus/steps/EncryptStrings.lua @@ -197,37 +197,31 @@ 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"); - 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; + if(node.name == "DECRYPT") then + node.symbol = decryptVar; + node.name = nil; end end - if(node.kind == AstKind.AssignmentVariable or node.kind == AstKind.VariableExpression) 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.VariableExpression or node.kind == AstKind.AssignmentGlobalVariable or node.kind == AstKind.GlobalVariableExpression) then + if(node.name == "STRINGS") then + if node.kind == AstKind.AssignmentVariable or node.kind == AstKind.AssignmentGlobalVariable then + return Ast.AssignmentVariable(stringsVar); + end + return Ast.VariableExpression(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), { + return Ast.IndexExpression(Ast.VariableExpression(stringsVar), Ast.FunctionCallExpression(Ast.VariableExpression(decryptVar), { Ast.StringExpression(encrypted), Ast.NumberExpression(seed), })); end @@ -236,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 138908bb..0d7c3588 100644 --- a/src/prometheus/steps/ProxifyLocals.lua +++ b/src/prometheus/steps/ProxifyLocals.lua @@ -2,310 +2,213 @@ -- -- 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 - parentScope:addReferenceToHigherScope(self.setMetatableVarScope, self.setMetatableVarId); - return Ast.FunctionCallExpression( - Ast.VariableExpression(self.setMetatableVarScope, self.setMetatableVarId), - { - 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) - -- 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(#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; - 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 - 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) 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 - - -- 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); - if(localMetatableInfo) then - table.insert(node.indices, 1, localMetatableInfo.valueName); - end - end - end) + 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 + 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({})), + }) + skipped[emptyFunctionDeclaration] = 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}, {}) + skipped[declaration] = true + local literal = Ast.FunctionLiteralExpression(statement.args, statement.body) + local target = Ast.AssignmentVariable(statement.symbol) + ignored[target] = true + local assignment = Ast.AssignmentStatement({target}, { + self:CreateAssignmentExpression(info, literal), + }) + skipped[assignment] = 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 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) + 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 skipped[node] 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 ignored[node] 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 ignored[node] and not isGlobal(node) then + local info = getLocalMetatableInfo(node.symbol) + if info then + ignored[node] = 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; \ No newline at end of file +return ProxifyLocals diff --git a/src/prometheus/steps/SplitStrings.lua b/src/prometheus/steps/SplitStrings.lua index 060101a3..26fad732 100644 --- a/src/prometheus/steps/SplitStrings.lua +++ b/src/prometheus/steps/SplitStrings.lua @@ -82,8 +82,7 @@ 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}); + return Ast.FunctionCallExpression(Ast.VariableExpression(data.tableConcatSymbol), {tb}); end local function generateStrCatNode(chunks) @@ -165,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; }); @@ -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,33 +181,28 @@ 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 -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; - funcBody.scope:setParent(astScope); - 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; - funcBody.scope:setParent(astScope); - return Ast.LocalVariableDeclaration(data.customFuncScope, {data.customFuncId}, + return Ast.LocalVariableDeclaration({data.customFuncSymbol}, {Ast.FunctionLiteralExpression(funcArgs, funcBody)}); end end @@ -223,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 @@ -249,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, }); @@ -268,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 @@ -297,22 +282,18 @@ 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); + 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; - -- Add Reference for Variable Renaming - data.scope:addReferenceToHigherScope(func.scope, func.id); - 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 @@ -325,14 +306,11 @@ 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"))})); + 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 ce92f0a2..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,28 +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); - functionScope:addReferenceToHigherScope(ast.globalScope, variable); - - 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 07a8a069..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,15 +28,12 @@ function WrapInFunction:apply(ast) for i = 1, self.Iterations, 1 do local body = ast.body; - local scope = Scope:new(ast.globalScope); - body.scope:setParent(scope); - 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 abf06aa8..6396fc6d 100644 --- a/src/prometheus/unparser.lua +++ b/src/prometheus/unparser.lua @@ -12,6 +12,8 @@ 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"); @@ -106,6 +108,23 @@ 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 = {}; + 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][base] or 0; + attempts[scope][base] = count + 1; + return count == 0 and base or base .. "_" .. count; + end, + }); return self:unparseBlock(ast.body); end @@ -188,7 +207,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"; @@ -200,11 +219,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); @@ -245,7 +264,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 @@ -257,7 +276,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(")"); @@ -267,7 +286,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 @@ -276,7 +295,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(")"); @@ -287,11 +306,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()); @@ -305,7 +324,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 +340,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,8 +450,12 @@ 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); + return expression.symbol.name; end if(expression.kind == AstKind.StringExpression) then @@ -596,7 +619,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 +637,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)); @@ -640,7 +663,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/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/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/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..3371dc97 100644 --- a/src/prometheus/vm/flatten.lua +++ b/src/prometheus/vm/flatten.lua @@ -1,33 +1,30 @@ -- 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) 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 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 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 @@ -39,28 +36,46 @@ 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 - local parser = Parser:new({LuaVersion = Enums.LuaVersion.Lua51, AllowInternalIdentifiers = true}) - local unparser = Unparser:new({LuaVersion = Enums.LuaVersion.Lua51}) - local runtimeSource = unparser:unparseExpression(runtime, 0) - local prefix = '' - local arguments = '...' - if #variables > 0 then - prefix = 'local ' .. table.concat(variables, ',') .. '=...;' - arguments = 'select(' .. (#variables + 1) .. ',...)' + -- 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 parameter = Ast.LocalSymbol('runtime_capture_' .. i) + parameters[i], replacements[symbol] = parameter, parameter + forwarded[i] = Ast.VariableExpression(symbol) 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 replacementSource = 'return function(...) return (function(...) ' - .. flattened .. ' end)(' .. forwarded .. ') end' - local replacement = parser:parse(replacementSource).body.statements[1].args[1] + forwarded[#forwarded + 1] = Ast.VarargExpression() + local wrapper = Ast.TopNode(Ast.Block({Ast.ReturnStatement({runtime})})) + visitast(wrapper, nil, function(node) + 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 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 + 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) - -- 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 + analyzeBindings(ast) + return ast end diff --git a/src/prometheus/vm/statements.lua b/src/prometheus/vm/statements.lua index 80bc4e04..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) @@ -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..8a782469 100644 --- a/src/prometheus/vm/strings.lua +++ b/src/prometheus/vm/strings.lua @@ -4,35 +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)) - cacheScope:addReferenceToHigherScope(cacheScope, cache) end - context.scope:addReferenceToHigherScope(cacheScope, variables[id]) - return Ast.VariableExpression(cacheScope, variables[id]) + return Ast.VariableExpression(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)})) + 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 new file mode 100644 index 00000000..262e3a37 --- /dev/null +++ b/tests/harness/ast-bindings.lua @@ -0,0 +1,276 @@ +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) + +-- 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) +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) + +-- 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 + 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..823ec572 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,...', { @@ -308,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') 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)