Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
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
Loading