Skip to content

Commit 5b30657

Browse files
committed
Upgraded to 0.8.1.1
1 parent 28d2a98 commit 5b30657

File tree

86 files changed

+4486
-669
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+4486
-669
lines changed

Underanalyzer/Underanalyzer/Compiler/Bytecode/BytecodeContext.cs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ internal sealed class BytecodeContext : ISubCompileContext
3030
/// </summary>
3131
public IASTNode RootNode { get; }
3232

33-
/// <summary>
34-
/// Set of global functions defined locally in the code entry being compiled.
35-
/// </summary>
36-
public HashSet<string>? LocalGlobalFunctions { get; }
37-
3833
/// <summary>
3934
/// Current list of written instructions, in order by address.
4035
/// </summary>
@@ -103,7 +98,6 @@ public BytecodeContext(CompileContext context, IASTNode rootNode, FunctionScope
10398
RootNode = rootNode;
10499
CurrentScope = rootScope;
105100
RootScope = rootScope;
106-
LocalGlobalFunctions = localGlobalFunctions;
107101

108102
rootScope.ControlFlowContexts = new(8);
109103
_gameContext = context.GameContext;
@@ -155,7 +149,7 @@ public static void PatchInstructions(CompileContext context, InstructionPatches
155149
{
156150
entry = functionPatch.FunctionEntry;
157151
}
158-
else if (functionPatch.FunctionScope!.TryGetDeclaredFunction(functionPatch.FunctionName!, out FunctionEntry? found))
152+
else if (functionPatch.FunctionScope!.TryGetDeclaredFunction(context.GameContext, functionPatch.FunctionName!, out FunctionEntry? found))
159153
{
160154
entry = found;
161155
}
@@ -280,7 +274,7 @@ public IGMInstruction Emit(ExtendedOpcode extendedOpcode)
280274
}
281275

282276
/// <summary>
283-
/// Emits an instruction with the given extended opcode, at the current position.
277+
/// Emits an instruction with the given extended opcode and local function, at the current position.
284278
/// </summary>
285279
public IGMInstruction Emit(ExtendedOpcode extendedOpcode, LocalFunctionPatch function)
286280
{
@@ -294,6 +288,21 @@ public IGMInstruction Emit(ExtendedOpcode extendedOpcode, LocalFunctionPatch fun
294288
return instr;
295289
}
296290

291+
/// <summary>
292+
/// Emits an instruction with the given extended opcode and function, at the current position.
293+
/// </summary>
294+
public IGMInstruction Emit(ExtendedOpcode extendedOpcode, FunctionPatch function)
295+
{
296+
IGMInstruction instr = _codeBuilder.CreateInstruction(Position, extendedOpcode, 0);
297+
Instructions.Add(instr);
298+
Position += 8;
299+
300+
function.Instruction = instr;
301+
Patches.FunctionPatches!.Add(function);
302+
303+
return instr;
304+
}
305+
297306
/// <summary>
298307
/// Emits an instruction with the given extended opcode and integer value, at the current position.
299308
/// </summary>
@@ -604,12 +613,6 @@ public void GenerateControlFlowCleanup()
604613
/// </summary>
605614
public bool IsGlobalFunctionName(string name)
606615
{
607-
// Check if it's a locally-declared global function
608-
if (LocalGlobalFunctions?.Contains(name) ?? false)
609-
{
610-
return true;
611-
}
612-
613616
// Check builtin functions
614617
if (_gameContext.Builtins.LookupBuiltinFunction(name) is not null)
615618
{
@@ -683,21 +686,21 @@ public bool IsFunctionDeclaredInCurrentScope(string name)
683686
case CompileScriptKind.GlobalScript:
684687
case CompileScriptKind.RoomCreationCode:
685688
// Global scripts and room creation code have foresight of future function declarations in the script
686-
return CurrentScope.IsFunctionDeclared(name);
689+
return CurrentScope.IsFunctionDeclared(CompileContext.GameContext, name);
687690

688691
case CompileScriptKind.ObjectEvent:
689692
// Object events only have foresight of future functions in certain versions
690693
if (CompileContext.GameContext.UsingObjectFunctionForesight)
691694
{
692-
return CurrentScope.IsFunctionDeclared(name);
695+
return CurrentScope.IsFunctionDeclared(CompileContext.GameContext, name);
693696
}
694697

695698
// No foresight; attempt to retrieve function entry
696-
return CurrentScope.TryGetDeclaredFunction(name, out _);
699+
return CurrentScope.TryGetDeclaredFunction(CompileContext.GameContext, name, out _);
697700

698701
default:
699702
// No foresight; attempt to retrieve function entry
700-
return CurrentScope.TryGetDeclaredFunction(name, out _);
703+
return CurrentScope.TryGetDeclaredFunction(CompileContext.GameContext, name, out _);
701704
}
702705
}
703706

Underanalyzer/Underanalyzer/Compiler/CompileContext.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ public sealed class CompileContext(string code, CompileScriptKind scriptKind, st
8181
/// </summary>
8282
public FunctionScope? OutputRootScope { get; private set; } = null;
8383

84+
/// <summary>
85+
/// Collection of global function names, only available between parsing and code generation.
86+
/// </summary>
87+
/// <remarks>
88+
/// To retrieve global function names after code generation, use <see cref="OutputFunctionEntries"/> instead.
89+
/// </remarks>
90+
public IReadOnlyCollection<string>? OutputGlobalFunctionNames => _parseGlobalFunctions;
91+
8492
/// <summary>
8593
/// List of sub-function entries, set after produced by code generation.
8694
/// </summary>

Underanalyzer/Underanalyzer/Compiler/FunctionScope.cs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ namespace Underanalyzer.Compiler;
1414
/// <summary>
1515
/// Structure used to track data at the level of a specific function/event/script scope.
1616
/// </summary>
17-
public sealed class FunctionScope(bool isFunction)
17+
public sealed class FunctionScope(FunctionScope? parent, bool isFunction)
1818
{
19+
/// <summary>
20+
/// Parent scope of this function scope, or <see langword="null"/> if none.
21+
/// </summary>
22+
public FunctionScope? Parent { get; } = parent;
23+
1924
/// <summary>
2025
/// Whether this scope is for specifically a function, and not a script or event.
2126
/// </summary>
@@ -41,6 +46,11 @@ public sealed class FunctionScope(bool isFunction)
4146
/// </summary>
4247
internal bool GeneratingStaticBlock { get; set; } = false;
4348

49+
/// <summary>
50+
/// Whether bytecode is currently being generated for a function call, where the function being called ends in a dot variable access.
51+
/// </summary>
52+
internal bool GeneratingDotVariableCall { get; set; } = false;
53+
4454
/// <summary>
4555
/// Whether currently post-processing a statement that requires extra logic for break/continue during code rewriting.
4656
/// </summary>
@@ -169,10 +179,13 @@ internal void AssignFunctionEntry(string name, FunctionEntry entry)
169179
/// <summary>
170180
/// Attempts to look up a function entry with the given name in this function scope.
171181
/// </summary>
182+
/// <remarks>
183+
/// If <see cref="IGameContext.UsingNewFunctionResolution"/> is <see langword="true"/>, this will check parent scopes as well.
184+
/// </remarks>
172185
/// <param name="name">Name of the function to look up.</param>
173186
/// <param name="entry">Output function entry, if the lookup is successful.</param>
174187
/// <returns><see langword="true"/> if a function entry was found; <see langword="false"/> otherwise.</returns>
175-
public bool TryGetDeclaredFunction(string name, [NotNullWhen(true)] out FunctionEntry? entry)
188+
public bool TryGetDeclaredFunction(IGameContext context, string name, [NotNullWhen(true)] out FunctionEntry? entry)
176189
{
177190
if (_declaredFunctions.TryGetValue(name, out entry))
178191
{
@@ -182,13 +195,45 @@ public bool TryGetDeclaredFunction(string name, [NotNullWhen(true)] out Function
182195
}
183196
return true;
184197
}
198+
if (context.UsingNewFunctionResolution)
199+
{
200+
if (Parent is not null)
201+
{
202+
return Parent.TryGetDeclaredFunction(context, name, out entry);
203+
}
204+
}
185205
return false;
186206
}
187207

188208
/// <summary>
189209
/// Returns whether or not a function with the given name is declared in this function scope.
190210
/// </summary>
191-
public bool IsFunctionDeclared(string name)
211+
/// <remarks>
212+
/// If <see cref="IGameContext.UsingNewFunctionResolution"/> is <see langword="true"/>, this will check parent scopes as well.
213+
/// </remarks>
214+
public bool IsFunctionDeclared(IGameContext context, string name)
215+
{
216+
if (_declaredFunctions.ContainsKey(name))
217+
{
218+
return true;
219+
}
220+
if (context.UsingNewFunctionResolution)
221+
{
222+
if (Parent is not null && Parent.IsFunctionDeclared(context, name))
223+
{
224+
return true;
225+
}
226+
}
227+
return false;
228+
}
229+
230+
/// <summary>
231+
/// Returns whether or not a function with the given name is declared within this immediate function scope.
232+
/// </summary>
233+
/// <remarks>
234+
/// Unlike <see cref="IsFunctionDeclared(IGameContext, string)"/>, this will never check parent scopes.
235+
/// </remarks>
236+
public bool IsFunctionDeclaredImmediately(string name)
192237
{
193238
return _declaredFunctions.ContainsKey(name);
194239
}

Underanalyzer/Underanalyzer/Compiler/Lexer/Token.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,14 @@ public TokenVariable(TokenString str)
420420
BuiltinVariable = Context.CompileContext.GameContext.Builtins.LookupBuiltinVariable(str.Value);
421421
}
422422

423+
public TokenVariable(TokenKeyword keyword)
424+
{
425+
Context = keyword.Context;
426+
TextPosition = keyword.TextPosition;
427+
Text = keyword.ToString();
428+
BuiltinVariable = Context.CompileContext.GameContext.Builtins.LookupBuiltinVariable(Text);
429+
}
430+
423431
public override string ToString()
424432
{
425433
return Text;

Underanalyzer/Underanalyzer/Compiler/Nodes/AccessorNode.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ internal sealed class AccessorNode : IAssignableASTNode
4242
/// <inheritdoc/>
4343
public IToken? NearbyToken { get; }
4444

45+
/// <summary>
46+
/// Whether this is an accessor node currently on the leftmost side of a <see cref="DotVariableNode"/>.
47+
/// </summary>
48+
public bool LeftmostSideOfDot { get; set; } = false;
49+
4550
/// <summary>
4651
/// Kinds of accessors.
4752
/// </summary>
@@ -127,7 +132,8 @@ public AccessorNode Convert2DArrayToTwoAccessors()
127132
public IASTNode PostProcess(ParseContext context)
128133
{
129134
// Compiler quirk with rewriting constants in dot nodes earlier in some cases
130-
if (Expression is DotVariableNode { LeftExpression: NumberNode numberNode } dotVariableNode)
135+
if (Expression is DotVariableNode { LeftExpression: NumberNode numberNode } dotVariableNode &&
136+
(context.CompileContext.GameContext.UsingSelfToBuiltin || numberNode.ConstantName == "global"))
131137
{
132138
dotVariableNode.LeftExpression = numberNode.PostProcess(context);
133139
}
@@ -182,7 +188,7 @@ public IASTNode Duplicate(ParseContext context)
182188
{
183189
// Change instance type to builtin (weird compiler quirk), when either a function call,
184190
// or in newer GML versions when not on the RHS of a dot variable.
185-
if (simpleVariable.IsFunctionCall || (!simpleVariable.CollapsedFromDot && context.CompileContext.GameContext.UsingSelfToBuiltin))
191+
if (simpleVariable.IsFunctionCall || (!LeftmostSideOfDot && !simpleVariable.CollapsedFromDot && context.CompileContext.GameContext.UsingSelfToBuiltin))
186192
{
187193
stackInstanceType = InstanceType.Builtin;
188194
}

Underanalyzer/Underanalyzer/Compiler/Nodes/DotVariableNode.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,15 @@ public IASTNode PostProcess(ParseContext context)
120120
}
121121
}
122122

123-
// Mark simple variables on the leftmost side as such
123+
// Mark simple variables and array accessors on the leftmost side as such
124124
if (LeftExpression is SimpleVariableNode simpleVariable)
125125
{
126126
simpleVariable.LeftmostSideOfDot = true;
127127
}
128+
else if (LeftExpression is AccessorNode accessorNode)
129+
{
130+
accessorNode.LeftmostSideOfDot = true;
131+
}
128132

129133
return this;
130134
}

Underanalyzer/Underanalyzer/Compiler/Nodes/FunctionCallNode.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,10 @@ FunctionCallNode or SimpleFunctionCallNode or
163163
inChain = true;
164164

165165
// Push arguments to stack
166+
bool prevGeneratingDotVariableCall = context.CurrentScope.GeneratingDotVariableCall;
167+
context.CurrentScope.GeneratingDotVariableCall = true;
166168
GenerateArguments(context);
169+
context.CurrentScope.GeneratingDotVariableCall = prevGeneratingDotVariableCall;
167170

168171
// Push left expression
169172
if (dotVar.LeftExpression is FunctionCallNode funcCall)
@@ -319,7 +322,17 @@ FunctionCallNode or SimpleFunctionCallNode or
319322
if (!inChain)
320323
{
321324
// Push arguments to stack
322-
GenerateArguments(context);
325+
if (Expression is DotVariableNode)
326+
{
327+
bool prevGeneratingDotVariableCall = context.CurrentScope.GeneratingDotVariableCall;
328+
context.CurrentScope.GeneratingDotVariableCall = true;
329+
GenerateArguments(context);
330+
context.CurrentScope.GeneratingDotVariableCall = prevGeneratingDotVariableCall;
331+
}
332+
else
333+
{
334+
GenerateArguments(context);
335+
}
323336

324337
// Swap instance and arguments around on stack, and duplicate instance
325338
context.EmitDupSwap(DataType.Variable, (byte)Arguments.Count, 1);

Underanalyzer/Underanalyzer/Compiler/Nodes/FunctionDeclNode.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,17 @@ private FunctionDeclNode(FunctionScope scope, IToken? token, string? functionNam
8585
/// <summary>
8686
/// Generates a default argument value check and assignment.
8787
/// </summary>
88-
private static IfNode GenerateDefaultCheckAndAssign(ParseContext context, string argumentName, IASTNode value)
88+
private static IfNode GenerateDefaultCheckAndAssign(ParseContext context, int argumentIndex, IASTNode value)
8989
{
9090
// Create condition
9191
SimpleVariableNode undefined = SimpleVariableNode.CreateUndefined(context);
92-
BinaryChainNode condition = new(value.NearbyToken, [new SimpleVariableNode(argumentName, null), undefined], [BinaryChainNode.BinaryOperation.CompareEqual]);
92+
bool useBuiltinInstanceType = context.CompileContext.GameContext.UsingBuiltinDefaultArguments;
93+
IAssignableASTNode argumentVar = SimpleVariableNode.CreateArgumentVariable(context, value.NearbyToken, argumentIndex, useBuiltinInstanceType);
94+
BinaryChainNode condition = new(value.NearbyToken, [argumentVar, undefined], [BinaryChainNode.BinaryOperation.CompareEqual]);
9395

9496
// Create assignment statement
95-
AssignNode assign = new(AssignNode.AssignKind.Normal, new SimpleVariableNode(argumentName, null), value);
97+
argumentVar = SimpleVariableNode.CreateArgumentVariable(context, value.NearbyToken, argumentIndex, useBuiltinInstanceType);
98+
AssignNode assign = new(AssignNode.AssignKind.Normal, argumentVar, value);
9699

97100
// Return final if statement check
98101
return new IfNode(value.NearbyToken, condition, assign, null);
@@ -119,7 +122,7 @@ private static IfNode GenerateDefaultCheckAndAssign(ParseContext context, string
119122

120123
// Enter a new function scope
121124
FunctionScope oldScope = context.CurrentScope;
122-
FunctionScope newScope = new(true);
125+
FunctionScope newScope = new(oldScope, true);
123126
context.CurrentScope = newScope;
124127

125128
// Parse arguments and default values
@@ -136,7 +139,6 @@ private static IfNode GenerateDefaultCheckAndAssign(ParseContext context, string
136139
argumentNames.Add(argumentName);
137140
context.Position++;
138141

139-
140142
// Check for default value
141143
if (context.IsCurrentToken(OperatorKind.Assign) || context.IsCurrentToken(OperatorKind.Assign2))
142144
{
@@ -147,7 +149,7 @@ private static IfNode GenerateDefaultCheckAndAssign(ParseContext context, string
147149
{
148150
// Generate code for checking/assigning default value for this argument
149151
defaultValueBlock ??= BlockNode.CreateEmpty(tokenKeyword, 16);
150-
defaultValueBlock.Children.Add(GenerateDefaultCheckAndAssign(context, tokenVariable.Text, defaultValueExpr));
152+
defaultValueBlock.Children.Add(GenerateDefaultCheckAndAssign(context, argumentNames.Count - 1, defaultValueExpr));
151153
}
152154
else
153155
{
@@ -266,7 +268,7 @@ public static SimpleFunctionCallNode ParseStruct(ParseContext context, IToken to
266268
{
267269
// Enter a new function scope
268270
FunctionScope oldScope = context.CurrentScope;
269-
FunctionScope newScope = new(true);
271+
FunctionScope newScope = new(oldScope, true);
270272
context.CurrentScope = newScope;
271273

272274
// Create new function declaration node
@@ -279,7 +281,7 @@ public static SimpleFunctionCallNode ParseStruct(ParseContext context, IToken to
279281
{
280282
if (context.Tokens[context.Position] is not TokenVariable variable)
281283
{
282-
// Failed to find a variable here... check if a constant/asset reference or string
284+
// Failed to find a variable here... check if a constant/asset reference, string, or keyword
283285
if (context.Tokens[context.Position] is TokenAssetReference assetReference)
284286
{
285287
variable = new TokenVariable(assetReference);
@@ -292,6 +294,10 @@ public static SimpleFunctionCallNode ParseStruct(ParseContext context, IToken to
292294
{
293295
variable = new TokenVariable(str);
294296
}
297+
else if (context.Tokens[context.Position] is TokenKeyword keyword)
298+
{
299+
variable = new TokenVariable(keyword);
300+
}
295301
else
296302
{
297303
// Nothing matched; stop parsing
@@ -476,7 +482,7 @@ public void GenerateCode(BytecodeContext context)
476482
if (InheritanceCall is SimpleFunctionCallNode inheritCall)
477483
{
478484
// Generate actual call
479-
if (oldScope.IsFunctionDeclared(inheritCall.FunctionName))
485+
if (oldScope.IsFunctionDeclared(context.CompileContext.GameContext, inheritCall.FunctionName))
480486
{
481487
// Override scope to be the outer scope for this call (but NOT its arguments), and generate a direct call
482488
inheritCall.GenerateDirectCode(context, oldScope);

0 commit comments

Comments
 (0)