From 3f1a8bbf3212981bf8537f7e8bb6c8ee0849a26a Mon Sep 17 00:00:00 2001 From: nub31 Date: Sun, 26 Jan 2025 15:52:30 +0100 Subject: [PATCH] Delegates --- .../Nub.Lang/{Parsing => }/FuncParameter.cs | 4 +- Nub.Lang/Nub.Lang/Input/program.nub | 2 +- Nub.Lang/Nub.Lang/Lexing/Lexer.cs | 8 +- Nub.Lang/Nub.Lang/Lexing/SymbolToken.cs | 2 + .../Parsing/{FuncCallNode.cs => FuncCall.cs} | 4 +- .../Parsing/FuncCallExpressionNode.cs | 8 + .../Nub.Lang/Parsing/FuncCallStatementNode.cs | 6 + .../Nub.Lang/Parsing/FuncDefinitionNode.cs | 9 +- Nub.Lang/Nub.Lang/Parsing/IdentifierNode.cs | 5 + Nub.Lang/Nub.Lang/Parsing/LiteralNode.cs | 5 +- Nub.Lang/Nub.Lang/Parsing/Parser.cs | 62 ++++++- .../Parsing/{SyscallNode.cs => Syscall.cs} | 2 +- .../Nub.Lang/Parsing/SyscallExpressionNode.cs | 6 + .../Nub.Lang/Parsing/SyscallStatementNode.cs | 6 + Nub.Lang/Nub.Lang/Program.cs | 4 +- Nub.Lang/Nub.Lang/Type.cs | 72 +++++--- Nub.Lang/Nub.Lang/Typing/ExpressionTyper.cs | 164 ++++++++++++++++++ 17 files changed, 328 insertions(+), 41 deletions(-) rename Nub.Lang/Nub.Lang/{Parsing => }/FuncParameter.cs (60%) rename Nub.Lang/Nub.Lang/Parsing/{FuncCallNode.cs => FuncCall.cs} (52%) create mode 100644 Nub.Lang/Nub.Lang/Parsing/FuncCallExpressionNode.cs create mode 100644 Nub.Lang/Nub.Lang/Parsing/FuncCallStatementNode.cs rename Nub.Lang/Nub.Lang/Parsing/{SyscallNode.cs => Syscall.cs} (56%) create mode 100644 Nub.Lang/Nub.Lang/Parsing/SyscallExpressionNode.cs create mode 100644 Nub.Lang/Nub.Lang/Parsing/SyscallStatementNode.cs create mode 100644 Nub.Lang/Nub.Lang/Typing/ExpressionTyper.cs diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncParameter.cs b/Nub.Lang/Nub.Lang/FuncParameter.cs similarity index 60% rename from Nub.Lang/Nub.Lang/Parsing/FuncParameter.cs rename to Nub.Lang/Nub.Lang/FuncParameter.cs index 5a36e79..cfbb556 100644 --- a/Nub.Lang/Nub.Lang/Parsing/FuncParameter.cs +++ b/Nub.Lang/Nub.Lang/FuncParameter.cs @@ -1,7 +1,9 @@ -namespace Nub.Lang.Parsing; +namespace Nub.Lang; public class FuncParameter(string name, Type type) { public string Name { get; } = name; public Type Type { get; } = type; + + public override string ToString() => $"{Name}: {Type}"; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Input/program.nub b/Nub.Lang/Nub.Lang/Input/program.nub index 4a965bd..7eb5570 100644 --- a/Nub.Lang/Nub.Lang/Input/program.nub +++ b/Nub.Lang/Nub.Lang/Input/program.nub @@ -7,6 +7,6 @@ func main() { write("test"); } -func write(msg: void) { +func write(msg: Func) { syscall(SYS_WRITE, STD_OUT, msg); } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Lexing/Lexer.cs b/Nub.Lang/Nub.Lang/Lexing/Lexer.cs index c23e3f3..1dfd7de 100644 --- a/Nub.Lang/Nub.Lang/Lexing/Lexer.cs +++ b/Nub.Lang/Nub.Lang/Lexing/Lexer.cs @@ -24,6 +24,8 @@ public class Lexer [','] = Symbol.Comma, ['.'] = Symbol.Period, ['='] = Symbol.Assign, + ['<'] = Symbol.LessThan, + ['>'] = Symbol.GreaterThan, }; private readonly string _src; @@ -34,7 +36,7 @@ public class Lexer _src = src; } - public IEnumerable Lex() + public IReadOnlyCollection Lex() { _index = 0; List tokens = []; @@ -79,7 +81,7 @@ public class Lexer current = Peek(); } - return new LiteralToken(Type.Int32, buffer); + return new LiteralToken(new PrimitiveType(PrimitiveTypeKind.Int64), buffer); } if (Chars.TryGetValue(current.Value, out var charSymbol)) @@ -102,7 +104,7 @@ public class Lexer buffer += current.Value; } - return new LiteralToken(Type.Pointer, buffer); + return new LiteralToken(new PointerType(), buffer); } if (char.IsWhiteSpace(current.Value)) diff --git a/Nub.Lang/Nub.Lang/Lexing/SymbolToken.cs b/Nub.Lang/Nub.Lang/Lexing/SymbolToken.cs index 3a4d2cc..4ce058b 100644 --- a/Nub.Lang/Nub.Lang/Lexing/SymbolToken.cs +++ b/Nub.Lang/Nub.Lang/Lexing/SymbolToken.cs @@ -19,6 +19,8 @@ public enum Symbol CloseBrace, OpenBracket, CloseBracket, + LessThan, + GreaterThan, Comma, Period, Assign diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncCallNode.cs b/Nub.Lang/Nub.Lang/Parsing/FuncCall.cs similarity index 52% rename from Nub.Lang/Nub.Lang/Parsing/FuncCallNode.cs rename to Nub.Lang/Nub.Lang/Parsing/FuncCall.cs index 66a9d96..f9774b9 100644 --- a/Nub.Lang/Nub.Lang/Parsing/FuncCallNode.cs +++ b/Nub.Lang/Nub.Lang/Parsing/FuncCall.cs @@ -1,7 +1,9 @@ namespace Nub.Lang.Parsing; -public class FuncCallNode(string name, IEnumerable parameters) : StatementNode +public class FuncCall(string name, IEnumerable parameters) { public string Name { get; } = name; public IEnumerable Parameters { get; } = parameters; + + public override string ToString() => $"{Name}()"; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncCallExpressionNode.cs b/Nub.Lang/Nub.Lang/Parsing/FuncCallExpressionNode.cs new file mode 100644 index 0000000..9837fea --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/FuncCallExpressionNode.cs @@ -0,0 +1,8 @@ +namespace Nub.Lang.Parsing; + +public class FuncCallExpressionNode(FuncCall funcCall) : ExpressionNode +{ + public FuncCall FuncCall { get; } = funcCall; + + public override string ToString() => FuncCall.ToString(); +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncCallStatementNode.cs b/Nub.Lang/Nub.Lang/Parsing/FuncCallStatementNode.cs new file mode 100644 index 0000000..6d24695 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/FuncCallStatementNode.cs @@ -0,0 +1,6 @@ +namespace Nub.Lang.Parsing; + +public class FuncCallStatementNode(FuncCall funcCall) : StatementNode +{ + public FuncCall FuncCall { get; } = funcCall; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs b/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs index 0cc0fd7..03329cc 100644 --- a/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs +++ b/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs @@ -1,8 +1,13 @@ -namespace Nub.Lang.Parsing; +using Nub.Lib; -public class FuncDefinitionNode(string name, IEnumerable parameters, BlockNode body) : DefinitionNode +namespace Nub.Lang.Parsing; + +public class FuncDefinitionNode(string name, IEnumerable parameters, BlockNode body, Optional returnType) : DefinitionNode { public string Name { get; } = name; public IEnumerable Parameters { get; } = parameters; public BlockNode Body { get; } = body; + public Optional ReturnType { get; } = returnType; + + public override string ToString() => $"{Name}({Parameters.Select(p => p.ToString())}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}"; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/IdentifierNode.cs b/Nub.Lang/Nub.Lang/Parsing/IdentifierNode.cs index 7bb87de..c196572 100644 --- a/Nub.Lang/Nub.Lang/Parsing/IdentifierNode.cs +++ b/Nub.Lang/Nub.Lang/Parsing/IdentifierNode.cs @@ -3,4 +3,9 @@ public class IdentifierNode(string identifier) : ExpressionNode { public string Identifier { get; } = identifier; + + public override string ToString() + { + return Identifier; + } } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/LiteralNode.cs b/Nub.Lang/Nub.Lang/Parsing/LiteralNode.cs index 892b897..d3eccb3 100644 --- a/Nub.Lang/Nub.Lang/Parsing/LiteralNode.cs +++ b/Nub.Lang/Nub.Lang/Parsing/LiteralNode.cs @@ -1,6 +1,7 @@ namespace Nub.Lang.Parsing; -public class LiteralNode(string value) : ExpressionNode +public class LiteralNode(string literal, Type type) : ExpressionNode { - public string Value { get; } = value; + public string Literal { get; } = literal; + public Type LiteralType { get; } = type; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/Parser.cs b/Nub.Lang/Nub.Lang/Parsing/Parser.cs index a3063da..7e1a8d9 100644 --- a/Nub.Lang/Nub.Lang/Parsing/Parser.cs +++ b/Nub.Lang/Nub.Lang/Parsing/Parser.cs @@ -13,7 +13,7 @@ public class Parser _tokens = tokens.ToArray(); } - public IEnumerable Parse() + public IReadOnlyCollection Parse() { _index = 0; List definitions = []; @@ -59,9 +59,15 @@ public class Parser } } + var returnType = Optional.Empty(); + if (TryExpectSymbol(Symbol.Colon)) + { + returnType = ParseType(); + } + var body = ParseBlock(); - return new FuncDefinitionNode(name.Value, parameters, body); + return new FuncDefinitionNode(name.Value, parameters, body, returnType); } private FuncParameter ParseFuncParameter() @@ -96,10 +102,10 @@ public class Parser if (identifier.Value == "syscall") { - return new SyscallNode(parameters); + return new SyscallStatementNode(new Syscall(parameters)); } - return new FuncCallNode(identifier.Value, parameters); + return new FuncCallStatementNode(new FuncCall(identifier.Value, parameters)); } case Symbol.Assign: throw new NotImplementedException(); @@ -117,12 +123,34 @@ public class Parser var token = ExpectToken(); return token switch { - LiteralToken literal => new LiteralNode(literal.Value), - IdentifierToken identifier => new IdentifierNode(identifier.Value), + LiteralToken literal => new LiteralNode(literal.Value, literal.Type), + IdentifierToken identifier => ParseExpressionIdentifier(identifier), _ => throw new Exception($"Unexpected token type {token.GetType().Name}") }; } + private ExpressionNode ParseExpressionIdentifier(IdentifierToken identifier) + { + if (TryExpectSymbol(Symbol.OpenParen)) + { + List parameters = []; + while (!TryExpectSymbol(Symbol.CloseParen)) + { + parameters.Add(ParseExpression()); + TryExpectSymbol(Symbol.Comma); + } + + if (identifier.Value == "syscall") + { + return new SyscallExpressionNode(new Syscall(parameters)); + } + + return new FuncCallExpressionNode(new FuncCall(identifier.Value, parameters)); + } + + return new IdentifierNode(identifier.Value); + } + private BlockNode ParseBlock() { ExpectSymbol(Symbol.OpenBrace); @@ -137,8 +165,26 @@ public class Parser private Type ParseType() { - var name = ExpectIdentifier(); - return new Type(name.Value); + var name = ExpectIdentifier().Value; + if (name == "Func") + { + List typeArguments = []; + if (TryExpectSymbol(Symbol.LessThan)) + { + while (!TryExpectSymbol(Symbol.GreaterThan)) + { + var type = ParseType(); + typeArguments.Add(type); + TryExpectSymbol(Symbol.Comma); + } + } + + var returnType = Optional.OfNullable(typeArguments.LastOrDefault()); + + return new DelegateType(typeArguments.Take(typeArguments.Count - 1), returnType); + } + + return PrimitiveType.Parse(name); } private Token ExpectToken() diff --git a/Nub.Lang/Nub.Lang/Parsing/SyscallNode.cs b/Nub.Lang/Nub.Lang/Parsing/Syscall.cs similarity index 56% rename from Nub.Lang/Nub.Lang/Parsing/SyscallNode.cs rename to Nub.Lang/Nub.Lang/Parsing/Syscall.cs index 7d7ca5f..bf17ff1 100644 --- a/Nub.Lang/Nub.Lang/Parsing/SyscallNode.cs +++ b/Nub.Lang/Nub.Lang/Parsing/Syscall.cs @@ -1,6 +1,6 @@ namespace Nub.Lang.Parsing; -public class SyscallNode(IEnumerable parameters) : StatementNode +public class Syscall(IEnumerable parameters) { public IEnumerable Parameters { get; } = parameters; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/SyscallExpressionNode.cs b/Nub.Lang/Nub.Lang/Parsing/SyscallExpressionNode.cs new file mode 100644 index 0000000..e4f5949 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/SyscallExpressionNode.cs @@ -0,0 +1,6 @@ +namespace Nub.Lang.Parsing; + +public class SyscallExpressionNode(Syscall syscall) : ExpressionNode +{ + public Syscall Syscall { get; } = syscall; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/SyscallStatementNode.cs b/Nub.Lang/Nub.Lang/Parsing/SyscallStatementNode.cs new file mode 100644 index 0000000..a48f182 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/SyscallStatementNode.cs @@ -0,0 +1,6 @@ +namespace Nub.Lang.Parsing; + +public class SyscallStatementNode(Syscall syscall) : StatementNode +{ + public Syscall Syscall { get; } = syscall; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Program.cs b/Nub.Lang/Nub.Lang/Program.cs index b200c9c..f77b6fe 100644 --- a/Nub.Lang/Nub.Lang/Program.cs +++ b/Nub.Lang/Nub.Lang/Program.cs @@ -1,5 +1,6 @@ using Nub.Lang.Lexing; using Nub.Lang.Parsing; +using Nub.Lang.Typing; var src = File.ReadAllText(args[0]); @@ -9,4 +10,5 @@ var tokens = lexer.Lex(); var parser = new Parser(tokens); var definitions = parser.Parse(); -Console.Read(); \ No newline at end of file +var typer = new ExpressionTyper(definitions); +typer.Populate(); \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Type.cs b/Nub.Lang/Nub.Lang/Type.cs index d10f759..f154280 100644 --- a/Nub.Lang/Nub.Lang/Type.cs +++ b/Nub.Lang/Nub.Lang/Type.cs @@ -1,26 +1,56 @@ -namespace Nub.Lang; +using Nub.Lib; -public class Type(string name) +namespace Nub.Lang; + +public abstract class Type; + +public class PrimitiveType(PrimitiveTypeKind kind) : Type { - public static Type Bool => new("bool"); - public static Type Char => new("char"); + public static PrimitiveType Parse(string value) + { + var kind = value switch + { + "bool" => PrimitiveTypeKind.Bool, + "char" => PrimitiveTypeKind.Char, + "int8" => PrimitiveTypeKind.Int8, + "uint8" => PrimitiveTypeKind.UInt8, + "int16" => PrimitiveTypeKind.Int16, + "uint16" => PrimitiveTypeKind.UInt16, + "int32" => PrimitiveTypeKind.Int32, + "uint32" => PrimitiveTypeKind.UInt32, + "int64" => PrimitiveTypeKind.Int64, + "uint64" => PrimitiveTypeKind.UInt64, + "float" => PrimitiveTypeKind.Float, + "double" => PrimitiveTypeKind.Double, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + + return new PrimitiveType(kind); + } - public static Type Int8 => new("int8"); - public static Type UInt8 => new("uint8"); - - public static Type Int16 => new("int16"); - public static Type UInt16 => new("uint16"); - - public static Type Int32 => new("int32"); - public static Type UInt32 => new("uint32"); - - public static Type Int64 => new("int64"); - public static Type UInt64 => new("uint64"); - - public static Type Float => new("char"); - public static Type Double => new("double"); - - public static Type Pointer => new("pointer"); + public PrimitiveTypeKind Kind { get; } = kind; +} - public string Name = name; +public enum PrimitiveTypeKind +{ + Bool, + Char, + Int8, + UInt8, + Int16, + UInt16, + Int32, + UInt32, + Int64, + UInt64, + Float, + Double, +} + +public class PointerType : Type; + +public class DelegateType(IEnumerable parameters, Optional returnType) : Type +{ + public IEnumerable Parameters { get; } = parameters; + public Optional ReturnType { get; } = returnType; } \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Typing/ExpressionTyper.cs b/Nub.Lang/Nub.Lang/Typing/ExpressionTyper.cs new file mode 100644 index 0000000..737c6ea --- /dev/null +++ b/Nub.Lang/Nub.Lang/Typing/ExpressionTyper.cs @@ -0,0 +1,164 @@ +using Nub.Lang.Parsing; + +namespace Nub.Lang.Typing; + +public class ExpressionTyper +{ + private readonly IReadOnlyCollection _functions; + private readonly IReadOnlyCollection _variableDefinitions; + private readonly Stack _variables; + + public ExpressionTyper(IReadOnlyCollection definitions) + { + _functions = definitions.OfType().ToList(); + _variableDefinitions = definitions.OfType().ToList(); + _variables = new Stack(); + } + + public void Populate() + { + _variables.Clear(); + + foreach (var variable in _variableDefinitions) + { + PopulateExpression(variable.Value); + _variables.Push(new Variable(variable.Name, variable.Value.Type)); + } + + foreach (var function in _functions) + { + foreach (var parameter in function.Parameters) + { + _variables.Push(new Variable(parameter.Name, parameter.Type)); + } + PopulateBlock(function.Body); + for (var i = 0; i < function.Parameters.Count(); i++) + { + _variables.Pop(); + } + } + } + + private void PopulateBlock(BlockNode block) + { + var variableCount = _variables.Count; + foreach (var statement in block.Statements) + { + PopulateStatement(statement); + } + while (_variables.Count > variableCount) + { + _variables.Pop(); + } + } + + private void PopulateStatement(StatementNode statement) + { + switch (statement) + { + case FuncCallStatementNode funcCall: + PopulateFuncCallStatement(funcCall); + break; + case SyscallStatementNode syscall: + PopulateSyscallStatement(syscall); + break; + case VariableAssignmentNode variableAssignment: + PopulateVariableAssignment(variableAssignment); + break; + default: + throw new ArgumentOutOfRangeException(nameof(statement)); + } + } + + private void PopulateFuncCallStatement(FuncCallStatementNode funcCall) + { + foreach (var parameter in funcCall.FuncCall.Parameters) + { + PopulateExpression(parameter); + } + } + + private void PopulateSyscallStatement(SyscallStatementNode syscall) + { + foreach (var parameter in syscall.Syscall.Parameters) + { + PopulateExpression(parameter); + } + } + + private void PopulateVariableAssignment(VariableAssignmentNode variableAssignment) + { + PopulateExpression(variableAssignment.Value); + } + + private void PopulateExpression(ExpressionNode expression) + { + switch (expression) + { + case FuncCallExpressionNode funcCall: + PopulateFuncCallExpression(funcCall); + break; + case IdentifierNode identifier: + PopulateIdentifier(identifier); + break; + case LiteralNode literal: + PopulateLiteral(literal); + break; + case SyscallExpressionNode syscall: + PopulateSyscallExpression(syscall); + break; + default: + throw new ArgumentOutOfRangeException(nameof(expression)); + } + } + + private void PopulateFuncCallExpression(FuncCallExpressionNode funcCall) + { + foreach (var parameter in funcCall.FuncCall.Parameters) + { + PopulateExpression(parameter); + } + + var function = _functions.FirstOrDefault(f => f.Name == funcCall.FuncCall.Name); + if (function == null) + { + throw new Exception($"Func {funcCall} is not defined"); + } + if (!function.ReturnType.HasValue) + { + throw new Exception($"Func {funcCall} must have a return type when used in an expression"); + } + funcCall.Type = function.ReturnType.Value; + } + + private void PopulateIdentifier(IdentifierNode identifier) + { + var type = _variables.FirstOrDefault(v => v.Name == identifier.Identifier)?.Type; + if (type == null) + { + throw new Exception($"Identifier {identifier} is not defined"); + } + identifier.Type = type; + } + + private static void PopulateLiteral(LiteralNode literal) + { + literal.Type = literal.LiteralType; + } + + private void PopulateSyscallExpression(SyscallExpressionNode syscall) + { + foreach (var parameter in syscall.Syscall.Parameters) + { + PopulateExpression(parameter); + } + + syscall.Type = new PrimitiveType(PrimitiveTypeKind.Int64); + } + + private class Variable(string name, Type type) + { + public string Name { get; } = name; + public Type Type { get; } = type; + } +} \ No newline at end of file