diff --git a/example/program.nub b/example/program.nub index fd7875b..ef2fbd3 100644 --- a/example/program.nub +++ b/example/program.nub @@ -3,9 +3,9 @@ namespace main /// # Documentation /// ## Documentation subtitle export func main(args: []string) { - i = 0 + let i: i64 - x = math::add(1, 1) + let x = math::add(1, 1) c::printf("%d\n", args.count) diff --git a/src/lang/Nub.Lang.CLI/Program.cs b/src/lang/Nub.Lang.CLI/Program.cs index a5552dd..fefced7 100644 --- a/src/lang/Nub.Lang.CLI/Program.cs +++ b/src/lang/Nub.Lang.CLI/Program.cs @@ -6,8 +6,8 @@ using Nub.Lang.Frontend.Typing; if (args.Length != 1) { - Console.Error.WriteLine("Usage: nub "); - Console.Error.WriteLine("Example: nub src"); + Console.Error.WriteLine("Usage: nub "); + Console.Error.WriteLine("Example: nub src"); return 1; } diff --git a/src/lang/Nub.Lang/Backend/Generator.cs b/src/lang/Nub.Lang/Backend/Generator.cs index 0f48816..d1871f4 100644 --- a/src/lang/Nub.Lang/Backend/Generator.cs +++ b/src/lang/Nub.Lang/Backend/Generator.cs @@ -296,8 +296,6 @@ public class Generator _builder.AppendLine($"({string.Join(", ", parameterStrings)}) {{"); _builder.AppendLine("@start"); - _builder.AppendLine(" # Variable allocation"); - foreach (var parameter in node.Parameters) { var parameterName = parameter.Name; @@ -334,9 +332,6 @@ public class Generator }; } - _builder.AppendLine(" # End variable allocation"); - _builder.AppendLine(); - GenerateBlock(node.Body); if (node.Body.Statements.LastOrDefault() is not ReturnNode) @@ -386,6 +381,9 @@ public class Generator case VariableAssignmentNode variableAssignment: GenerateVariableAssignment(variableAssignment); break; + case VariableDeclarationNode variableDeclaration: + GenerateVariableDeclaration(variableDeclaration); + break; case WhileNode whileStatement: GenerateWhile(whileStatement); break; @@ -505,23 +503,32 @@ public class Generator private void GenerateVariableAssignment(VariableAssignmentNode variableAssignment) { var result = GenerateExpression(variableAssignment.Value); + _builder.AppendLine($" storel {result}, {_variables[variableAssignment.Name].Pointer}"); + } - if (_variables.TryGetValue(variableAssignment.Name, out var existingVariable)) + private void GenerateVariableDeclaration(VariableDeclarationNode variableDeclaration) + { + var pointerName = GenVarName(); + + var type = variableDeclaration.ExplicitType.Value ?? variableDeclaration.Value.Value?.Type!; + + _builder.AppendLine($" %{pointerName} ={SQT(type)} alloc8 {QbeTypeSize(type)}"); + + if (variableDeclaration.Value.HasValue) { - _builder.AppendLine($" storel {result}, {existingVariable.Pointer}"); + var result = GenerateExpression(variableDeclaration.Value.Value); + _builder.AppendLine($" storel {result}, %{pointerName}"); } else { - var pointerName = GenVarName(); - _builder.AppendLine($" %{pointerName} ={SQT(variableAssignment.Value.Type)} alloc8 {QbeTypeSize(variableAssignment.Value.Type)}"); - _builder.AppendLine($" storel {result}, %{pointerName}"); - - _variables[variableAssignment.Name] = new Variable - { - Pointer = $"%{pointerName}", - Type = variableAssignment.Value.Type - }; + _builder.AppendLine($" storel 0, %{pointerName}"); } + + _variables[variableDeclaration.Name] = new Variable + { + Pointer = $"%{pointerName}", + Type = type + }; } private void GenerateWhile(WhileNode whileStatement) diff --git a/src/lang/Nub.Lang/Frontend/Lexing/Lexer.cs b/src/lang/Nub.Lang/Frontend/Lexing/Lexer.cs index 891b496..1cd21ae 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/Lexer.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/Lexer.cs @@ -17,6 +17,7 @@ public class Lexer ["return"] = Symbol.Return, ["new"] = Symbol.New, ["struct"] = Symbol.Struct, + ["let"] = Symbol.Let, }; private static readonly Dictionary Modifiers = new() diff --git a/src/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs b/src/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs index c5cd882..dc82a9a 100644 --- a/src/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs +++ b/src/lang/Nub.Lang/Frontend/Lexing/SymbolToken.cs @@ -44,4 +44,5 @@ public enum Symbol Ampersand, DoubleColon, Namespace, + Let } \ No newline at end of file diff --git a/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs b/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs index ce4d182..e5ea791 100644 --- a/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs +++ b/src/lang/Nub.Lang/Frontend/Parsing/Parser.cs @@ -192,11 +192,12 @@ public class Parser Symbol.Return => ParseReturn(startIndex), Symbol.If => ParseIf(startIndex), Symbol.While => ParseWhile(startIndex), + Symbol.Let => ParseVariableDeclaration(startIndex), Symbol.Break => new BreakNode(GetTokensForNode(startIndex)), Symbol.Continue => new ContinueNode(GetTokensForNode(startIndex)), _ => throw new ParseException(Diagnostic .Error($"Unexpected symbol '{symbol.Symbol}' at start of statement") - .WithHelp("Expected identifier, 'return', 'if', 'while', 'break', or 'continue'") + .WithHelp("Expected identifier, 'let', 'return', 'if', 'while', 'break', or 'continue'") .At(symbol) .Build()) }; @@ -258,14 +259,7 @@ public class Parser case Symbol.Assign: { var value = ParseExpression(); - return new VariableAssignmentNode(GetTokensForNode(startIndex), identifier.Value, Optional.Empty(), value); - } - case Symbol.Colon: - { - var type = ParseType(); - ExpectSymbol(Symbol.Assign); - var value = ParseExpression(); - return new VariableAssignmentNode(GetTokensForNode(startIndex), identifier.Value, type, value); + return new VariableAssignmentNode(GetTokensForNode(startIndex), identifier.Value, value); } default: { @@ -278,6 +272,24 @@ public class Parser } } + private VariableDeclarationNode ParseVariableDeclaration(int startIndex) + { + var name = ExpectIdentifier().Value; + var type = Optional.Empty(); + if (TryExpectSymbol(Symbol.Colon)) + { + type = ParseType(); + } + + var value = Optional.Empty(); + if (TryExpectSymbol(Symbol.Assign)) + { + value = ParseExpression(); + } + + return new VariableDeclarationNode(GetTokensForNode(startIndex), name, type, value); + } + private ReturnNode ParseReturn(int startIndex) { var value = Optional.Empty(); diff --git a/src/lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs b/src/lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs index a9daa16..93a3304 100644 --- a/src/lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs +++ b/src/lang/Nub.Lang/Frontend/Parsing/VariableAssignmentNode.cs @@ -3,9 +3,8 @@ using Nub.Lang.Frontend.Typing; namespace Nub.Lang.Frontend.Parsing; -public class VariableAssignmentNode(IReadOnlyList tokens, string name, Optional explicitType, ExpressionNode value) : StatementNode(tokens) +public class VariableAssignmentNode(IReadOnlyList tokens, string name, ExpressionNode value) : StatementNode(tokens) { public string Name { get; } = name; - public Optional ExplicitType { get; } = explicitType; public ExpressionNode Value { get; } = value; } \ No newline at end of file diff --git a/src/lang/Nub.Lang/Frontend/Parsing/VariableDeclarationNode.cs b/src/lang/Nub.Lang/Frontend/Parsing/VariableDeclarationNode.cs new file mode 100644 index 0000000..ff27e73 --- /dev/null +++ b/src/lang/Nub.Lang/Frontend/Parsing/VariableDeclarationNode.cs @@ -0,0 +1,11 @@ +using Nub.Lang.Frontend.Lexing; +using Nub.Lang.Frontend.Typing; + +namespace Nub.Lang.Frontend.Parsing; + +public class VariableDeclarationNode(IReadOnlyList tokens, string name, Optional explicitType, Optional value) : StatementNode(tokens) +{ + public string Name { get; } = name; + public Optional ExplicitType { get; } = explicitType; + public Optional Value { get; } = value; +} \ No newline at end of file diff --git a/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs b/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs index 7c4f32c..7593545 100644 --- a/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs +++ b/src/lang/Nub.Lang/Frontend/Typing/TypeChecker.cs @@ -28,7 +28,7 @@ public class TypeChecker { ReportError($"Extern function '{funcName}' has been declared more than once", funcName); } - + var exportedLocalFuncDefinitions = _sourceFiles .SelectMany(f => f.Definitions) .OfType() @@ -39,7 +39,7 @@ public class TypeChecker { ReportError($"Exported function '{funcName}' has been declared more than once", funcName); } - + foreach (var structDef in _sourceFiles.SelectMany(f => f.Definitions).OfType()) { TypeCheckStructDef(structDef); @@ -49,7 +49,7 @@ public class TypeChecker { TypeCheckFuncDef(funcDef); } - + return new DiagnosticsResult(_diagnostics); } @@ -108,8 +108,11 @@ public class TypeChecker { switch (statement) { - case VariableAssignmentNode varAssign: - TypeCheckVariableAssignment(varAssign); + case VariableAssignmentNode variableAssignment: + TypeCheckVariableAssignment(variableAssignment); + break; + case VariableDeclarationNode variableDeclaration: + TypeCheckVariableVariableDeclaration(variableDeclaration); break; case FuncCallStatementNode funcCall: TypeCheckFuncCall(funcCall.FuncCall, funcCall); @@ -132,45 +135,59 @@ public class TypeChecker } } - private void TypeCheckVariableAssignment(VariableAssignmentNode varAssign) + private void TypeCheckVariableAssignment(VariableAssignmentNode variableAssignment) { - var valueType = TypeCheckExpression(varAssign.Value); + var valueType = TypeCheckExpression(variableAssignment.Value); if (valueType == null) return; - if (_variables.TryGetValue(varAssign.Name, out var existingVariable)) + if (!_variables.TryGetValue(variableAssignment.Name, out var existingVariable)) { - if (varAssign.ExplicitType.HasValue) - { - if (!NubType.IsCompatibleWith(existingVariable, varAssign.ExplicitType.Value)) - { - ReportError($"Explicit type '{varAssign.ExplicitType.Value}' on variable '{varAssign.Name}' is not compatible with declared type '{existingVariable}'", varAssign); - return; - } - } - - if (!NubType.IsCompatibleWith(valueType, existingVariable)) - { - ReportError($"Cannot assign expression of type '{valueType}' to variable '{varAssign.Name}' of type '{existingVariable}'", varAssign); - } + ReportError($"Variable '{variableAssignment.Name}' is not declared", variableAssignment); + return; } - else - { - if (varAssign.ExplicitType.HasValue) - { - var explicitType = varAssign.ExplicitType.Value; - if (!NubType.IsCompatibleWith(valueType, explicitType)) - { - ReportError($"Cannot assign expression of type '{valueType}' to variable '{varAssign.Name}' of type '{explicitType}'", varAssign); - return; - } - _variables[varAssign.Name] = explicitType; - } - else + if (!NubType.IsCompatibleWith(variableAssignment.Value.Type, existingVariable)) + { + ReportError($"Cannot assign expression of type '{variableAssignment.Value.Type}' to variable '{variableAssignment.Name}' with type '{existingVariable}'", variableAssignment); + } + } + + private void TypeCheckVariableVariableDeclaration(VariableDeclarationNode variableDeclaration) + { + NubType? type = null; + + if (_variables.TryGetValue(variableDeclaration.Name, out var existingVariable)) + { + ReportError($"Cannot redeclare variable '{existingVariable}'", variableDeclaration); + } + + if (variableDeclaration.Value.HasValue) + { + var valueType = TypeCheckExpression(variableDeclaration.Value.Value); + if (valueType == null) return; + type = valueType; + } + + if (variableDeclaration.ExplicitType.HasValue) + { + type = variableDeclaration.ExplicitType.Value; + } + + if (variableDeclaration.ExplicitType.HasValue && variableDeclaration.Value.HasValue) + { + if (!NubType.IsCompatibleWith(variableDeclaration.ExplicitType.Value, variableDeclaration.Value.Value.Type)) { - _variables[varAssign.Name] = valueType; + ReportError($"Cannot assign expression of type '{variableDeclaration.Value.Value.Type}' to variable '{variableDeclaration.Name}' with type '{variableDeclaration.ExplicitType.Value}'", variableDeclaration); } } + + if (type == null) + { + ReportError($"Cannot implicitly get type of variable '{variableDeclaration.Name}'", variableDeclaration); + return; + } + + _variables[variableDeclaration.Name] = type; } private NubType? TypeCheckDereference(DereferenceNode dereference) @@ -196,7 +213,7 @@ public class TypeChecker if (parameterType == null) return null; parameterTypes.Add(parameterType); } - + var funcDefinition = LookupFuncSignature(funcCall.Namespace, funcCall.Name, parameterTypes); if (funcDefinition == null) {