Skip to content

Commit

Permalink
object initializers
Browse files Browse the repository at this point in the history
* initializer code

* docs

* changelog

---------

Co-authored-by: Chris Hanna <[email protected]>
  • Loading branch information
cdhanna and chrisbeamable authored Mar 7, 2025
1 parent 79447e2 commit 2bf7452
Show file tree
Hide file tree
Showing 18 changed files with 915 additions and 8 deletions.
6 changes: 6 additions & 0 deletions FadeBasic/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.38] - 2025-03-07

### Added
- The `default` keyword
- Object initializer pattern

## [0.0.37] - 2025-03-02

### Changed
Expand Down
13 changes: 13 additions & 0 deletions FadeBasic/FadeBasic/Ast/ExpressionNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,19 @@ public override IEnumerable<IAstVisitable> IterateChildNodes()
}
}

public class DefaultValueExpression : AstNode, ILiteralNode
{
protected override string GetString()
{
return "default";
}

public override IEnumerable<IAstVisitable> IterateChildNodes()
{
yield break;
}
}

public class LiteralIntExpression : AstNode, ILiteralNode
{
public int value;
Expand Down
22 changes: 22 additions & 0 deletions FadeBasic/FadeBasic/Ast/InitializerExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Linq;

namespace FadeBasic.Ast
{
public class InitializerExpression : AstNode, IExpressionNode
{
public List<AssignmentStatement> assignments = new List<AssignmentStatement>();


protected override string GetString()
{
return $"init ({string.Join(",", assignments.Select(x => x.ToString()))})";
}

public override IEnumerable<IAstVisitable> IterateChildNodes()
{
foreach (var x in assignments)
yield return x;
}
}
}
114 changes: 114 additions & 0 deletions FadeBasic/FadeBasic/Ast/Visitors/InitializerSugarVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Collections.Generic;

namespace FadeBasic.Ast.Visitors
{
public static class InitializerSugarVisitor
{

static void ApplyStatements(List<IStatementNode> statements)
{
for (var i = 0; i < statements.Count; i++)
{
var statement = statements[i];

switch (statement)
{
case DeclarationStatement decl:
ApplyDecl(decl, i, statements);
break;
case AssignmentStatement assignment:
ApplyAssign(assignment, i, statements);
break;
}

}
}

static (IVariableNode outputLeft, IVariableNode outputRight) ReBalance(IVariableNode left, IVariableNode right)
{
switch (left)
{
case StructFieldReference leftStructRef:

var subLeft = leftStructRef.left;
var subRight = leftStructRef.right;

var (balancedLeft, balancedRight) = ReBalance(subLeft, subRight);

var newRight = new StructFieldReference
{
startToken = subRight.StartToken,
endToken = right.EndToken,
left = balancedRight,
right = right
};
return (balancedLeft, newRight);

break;

Check warning on line 47 in FadeBasic/FadeBasic/Ast/Visitors/InitializerSugarVisitor.cs

View workflow job for this annotation

GitHub Actions / release

Unreachable code detected

Check warning on line 47 in FadeBasic/FadeBasic/Ast/Visitors/InitializerSugarVisitor.cs

View workflow job for this annotation

GitHub Actions / release

Unreachable code detected
default:
return (left, right);
}
}

static void ApplyAssign(AssignmentStatement assignment, int index, List<IStatementNode> statements)
{
if (!(assignment.expression is InitializerExpression init)) return;

assignment.expression = new DefaultValueExpression
{
startToken = assignment.startToken,
endToken = assignment.endToken
};

for (var i = init.assignments.Count - 1; i >= 0; i--)
{
var subAssignment = init.assignments[i];


// need to re-balance. if left is already a struct-field-reference, then must dig in.

var (left, right) = ReBalance(assignment.variable, subAssignment.variable);

subAssignment.variable = new StructFieldReference
{
startToken = subAssignment.startToken,
endToken = subAssignment.endToken,
Errors = subAssignment.Errors,

// TODO: this probably isn't right?
// left = new VariableRefNode(assignment.startToken, subAssignment.variable),
// right = assignment.variable
left = left,
right = right
};
statements.Insert(index + 1, subAssignment);
}
}

static void ApplyDecl(DeclarationStatement decl, int index, List<IStatementNode> statements)
{
if (!(decl.initializerExpression is InitializerExpression init)) return;
decl.initializerExpression = null;
for (var i = init.assignments.Count - 1; i >= 0; i--)
{
var assignment = init.assignments[i];
assignment.variable = new StructFieldReference
{
startToken = assignment.startToken,
endToken = assignment.endToken,
Errors = assignment.Errors,

// TODO: this probably isn't right?
left = new VariableRefNode(decl.startToken, decl.variable),
right = assignment.variable
};
statements.Insert(index + 1, assignment);
}
}

public static void AddInitializerSugar(this ProgramNode node)
{
ApplyStatements(node.statements);
}
}
}
26 changes: 26 additions & 0 deletions FadeBasic/FadeBasic/Ast/Visitors/ScopeErrorVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ public static void AddScopeRelatedErrors(this ProgramNode program, ParseOptions

}

foreach (var def in scope.defaultValueExpressions)
{
if (def.ParsedType.type == VariableType.Void)
{
def.Errors.Add(new ParseError(def, ErrorCodes.DefaultExpressionUnknownType));
}
}

scope.DoDelayedTypeChecks();
}

Expand Down Expand Up @@ -230,6 +238,11 @@ static void CheckStatements(this List<IStatementNode> statements, Scope scope, E

if (decl.initializerExpression != null)
{
if (decl.initializerExpression is DefaultValueExpression defExpr)
{
defExpr.ParsedType = decl.ParsedType;
}

scope.EnforceTypeAssignment(decl.initializerExpression,
decl.initializerExpression.ParsedType, decl.ParsedType, false, out _);
}
Expand All @@ -242,6 +255,7 @@ static void CheckStatements(this List<IStatementNode> statements, Scope scope, E

// and THEN register LHS of the assignemnt (otherwise you can get self-referential stuff)
scope.AddAssignment(assignment, ctx, out var implicitDecl);

if (implicitDecl != null)
{
statements.Insert(i, implicitDecl);
Expand All @@ -257,6 +271,11 @@ static void CheckStatements(this List<IStatementNode> statements, Scope scope, E
default:
break;
}

if (assignment.expression is DefaultValueExpression defExpr2 && assignment.variable.ParsedType.type != VariableType.Void)
{
defExpr2.ParsedType = assignment.variable.ParsedType;
}

break;
case SwitchStatement switchStatement:
Expand Down Expand Up @@ -501,6 +520,13 @@ public static void EnsureVariablesAreDefined(this IExpressionNode expr, Scope sc
{
switch (expr)
{
case DefaultValueExpression defExpr:
scope.AddDefaultExpression(defExpr);
break;
case InitializerExpression initExpr:
// initializers are not allowed to appear here; they are syntax sugar and should be removed by now.
initExpr.Errors.Add(new ParseError(initExpr.startToken, ErrorCodes.InitializerNotAllowed));
break;
case BinaryOperandExpression binaryOpExpr:
binaryOpExpr.lhs.EnsureVariablesAreDefined(scope, ctx);
binaryOpExpr.rhs.EnsureVariablesAreDefined(scope, ctx);
Expand Down
2 changes: 2 additions & 0 deletions FadeBasic/FadeBasic/Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ public static class ErrorCodes
public static readonly ErrorCode UnknownType = "[0210] Type is not defined";
public static readonly ErrorCode ArrayRankMustBeInteger = "[0211] Array rank expression must be an integer";
public static readonly ErrorCode ImplicitArrayDeclaration = "[0212] Implicit array declarations are not allowed";
public static readonly ErrorCode InitializerNotAllowed = "[0213] Initializer is not allowed here";
public static readonly ErrorCode DefaultExpressionUnknownType = "[0214] Default expression has unknown type";

// 300 series represents type issues
public static readonly ErrorCode SymbolAlreadyDeclared = "[0300] Symbol already declared";
Expand Down
4 changes: 4 additions & 0 deletions FadeBasic/FadeBasic/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ public enum LexemType
OpBitwiseXor,
ParenOpen,
ParenClose,
BracketOpen,
BracketClose,
LiteralReal,
LiteralInt,
LiteralString,
Expand Down Expand Up @@ -154,6 +156,8 @@ public class Lexer
new Lexem(-10,LexemType.WhiteSpace, new Regex("^(\\s|\\t|\\n)+")),
new Lexem(LexemType.ParenOpen, new Regex("^\\(")),
new Lexem(LexemType.ParenClose, new Regex("^\\)")),
new Lexem(LexemType.BracketOpen, new Regex("^\\{")),
new Lexem(LexemType.BracketClose, new Regex("^\\}")),
new Lexem(LexemType.OpPlus, new Regex("^\\+")),
new Lexem(LexemType.OpMinus, new Regex("^\\-")),
new Lexem(LexemType.OpMultiply, new Regex("^\\*")),
Expand Down
89 changes: 85 additions & 4 deletions FadeBasic/FadeBasic/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public class Scope
public Dictionary<string, Symbol> functionSymbolTable = new Dictionary<string, Symbol>();
public Dictionary<string, FunctionStatement> functionTable = new Dictionary<string, FunctionStatement>();
public Dictionary<string, List<TypeInfo>> functionReturnTypeTable = new Dictionary<string, List<TypeInfo>>();

public List<DefaultValueExpression> defaultValueExpressions = new List<DefaultValueExpression>();
List<DelayedTypeCheck> delayedTypeChecks = new List<DelayedTypeCheck>();

private int allowExitCounter;
Expand Down Expand Up @@ -397,6 +397,11 @@ public void AddAssignment(AssignmentStatement assignment, EnsureTypeContext ctx,
// declr is optional...
if (TryGetSymbol(variableRef.variableName, out var existingSymbol))
{
if (assignment.expression is DefaultValueExpression defExpr)
{
defExpr.ParsedType = existingSymbol.typeInfo;
}

EnforceTypeAssignment(variableRef, assignment.expression.ParsedType, existingSymbol.typeInfo, false, out _);
variableRef.DeclaredFromSymbol = existingSymbol;
}
Expand All @@ -409,7 +414,17 @@ public void AddAssignment(AssignmentStatement assignment, EnsureTypeContext ctx,
};
var rightType = assignment.expression.ParsedType;

EnforceTypeAssignment(variableRef, rightType, defaultTypeInfo, true, out var foundType);
TypeInfo foundType = default;
if (assignment.expression is DefaultValueExpression defExpr)
{
defExpr.ParsedType = defaultTypeInfo;
variableRef.ParsedType = defaultTypeInfo;
foundType = defaultTypeInfo;
}
else
{
EnforceTypeAssignment(variableRef, rightType, defaultTypeInfo, true, out foundType);
}


var locals = GetVariables(DeclarationScopeType.Local);
Expand Down Expand Up @@ -456,7 +471,14 @@ public void AddAssignment(AssignmentStatement assignment, EnsureTypeContext ctx,
}
}

EnforceTypeAssignment(indexRef, assignment.expression.ParsedType, nonArrayVersion, false, out _);
if (assignment.expression is DefaultValueExpression defExpr)
{
defExpr.ParsedType = nonArrayVersion;
}
else
{
EnforceTypeAssignment(indexRef, assignment.expression.ParsedType, nonArrayVersion, false, out _);
}
indexRef.DeclaredFromSymbol = existingArrSymbol;
}
// EnforceTypeAssignment(variableRef, rightType, defaultTypeInfo, true, out var foundType);
Expand Down Expand Up @@ -806,6 +828,11 @@ class DelayedTypeCheck
{
public IAstNode source, right, left;
}

public void AddDefaultExpression(DefaultValueExpression defExpr)
{
defaultValueExpressions.Add(defExpr);
}
}

public class ParseOptions
Expand Down Expand Up @@ -867,6 +894,7 @@ public ProgramNode ParseProgram(ParseOptions options = null)
program.endToken = _stream.Current;

// program.AddTypeInfo();
program.AddInitializerSugar();
program.AddScopeRelatedErrors(options);

return program;
Expand Down Expand Up @@ -2970,7 +2998,60 @@ private bool TryParseWikiTerm(out IExpressionNode outputExpression, out ProgramR
recovery = null;
switch (token.type)
{

case LexemType.KeywordCaseDefault:
_stream.Advance();
outputExpression = new DefaultValueExpression
{
startToken = token, endToken = token
};
break;
case LexemType.BracketOpen:
_stream.Advance(); // consume open bracket
outputExpression = null;

var lookingForClose = true;
var subStatements = new List<IStatementNode>();
var assignments = new List<AssignmentStatement>();
while (lookingForClose)
{
switch (_stream.Peek.type)
{
case LexemType.EOF:
// TODO: add error
// errors.Add(new ParseError(start, ErrorCodes.TypeDefMissingEndType));
lookingForClose = false;
break;

case LexemType.EndStatement:
_stream.Advance();
break;
case LexemType.BracketClose:
lookingForClose = false;
_stream.Advance();
break;
default:
var statement = ParseStatement(subStatements);
subStatements.Add(statement);

switch (statement)
{
case AssignmentStatement assignment:
assignments.Add(assignment);
break;
default:
// TODO: add error saying only assignments are allowed
break;
}
break;
}

}

outputExpression = new InitializerExpression
{
startToken = token, endToken = _stream.Current, assignments = assignments
};
break;
case LexemType.CommandWord:
_stream.Advance();

Expand Down
Loading

0 comments on commit 2bf7452

Please sign in to comment.