diff --git a/compiler/Generator.cs b/compiler/Generator.cs index 7bcb8f4..ab88d2f 100644 --- a/compiler/Generator.cs +++ b/compiler/Generator.cs @@ -232,6 +232,20 @@ static inline string *string_from_cstr(char *cstr) return result; } +#define da_append(xs, x) \ + do \ + { \ + if ((xs)->count >= (xs)->capacity) \ + { \ + if ((xs)->capacity == 0) (xs)->capacity = 256; \ + else (xs)->capacity *= 2; \ + (xs)->items = realloc((xs)->items, (xs)->capacity*sizeof(*(xs)->items)); \ + } \ + \ + (xs)->items[(xs)->count++] = (x); \ + } \ + while (0) + """ ); @@ -406,6 +420,9 @@ static inline string *string_from_cstr(char *cstr) case TypedNodeStatementWhile statement: EmitStatementWhile(statement); break; + case TypedNodeStatementFor statement: + EmitStatementFor(statement); + break; case TypedNodeStatementMatch statement: EmitStatementMatch(statement); break; @@ -514,6 +531,21 @@ static inline string *string_from_cstr(char *cstr) writer.WriteLine("}"); } + private void EmitStatementFor(TypedNodeStatementFor statement) + { + var index = Tmp(); + var array = EmitExpression(statement.Array); + writer.WriteLine($"for (size_t {index} = 0; {index} < {array}.count; ++{index})"); + writer.WriteLine("{"); + using (writer.Indent()) + { + var arrayType = (NubTypeArray)statement.Array.Type; + writer.WriteLine($"{CType(arrayType.ElementType, statement.VariableName.Ident)} = {array}.items[{index}];"); + EmitStatement(statement.Body); + } + writer.WriteLine("}"); + } + private void EmitStatementMatch(TypedNodeStatementMatch statement) { var target = EmitExpression(statement.Target); @@ -565,6 +597,7 @@ static inline string *string_from_cstr(char *cstr) TypedNodeExpressionStringLiteral expression => EmitExpressionStringLiteral(expression), TypedNodeExpressionStructLiteral expression => EmitExpressionStructLiteral(expression), TypedNodeExpressionEnumLiteral expression => EmitExpressionEnumLiteral(expression), + TypedNodeExpressionArrayLiteral expression => EmitNodeExpressionArrayLiteral(expression), TypedNodeExpressionStringConstructor expression => EmitExpressionStringConstructor(expression), TypedNodeExpressionStructMemberAccess expression => EmitExpressionMemberAccess(expression), TypedNodeExpressionStringLength expression => EmitExpressionStringLength(expression), @@ -588,7 +621,7 @@ static inline string *string_from_cstr(char *cstr) if (expression.Operation == TypedNodeExpressionBinary.Op.Add && expression.Left.Type is NubTypeString && expression.Right.Type is NubTypeString) { scopes.Peek().DeconstructableNames.Add((name, expression.Type)); - writer.WriteLine($"{CType(NubTypeString.Instance, name)} = nub_core_string_concat({left}, {right});"); + writer.WriteLine($"{CType(NubTypeString.Instance, name)} = string_concat({left}, {right});"); return name; } @@ -693,12 +726,27 @@ static inline string *string_from_cstr(char *cstr) return name; } + private string EmitNodeExpressionArrayLiteral(TypedNodeExpressionArrayLiteral expression) + { + var name = Tmp(); + + writer.WriteLine($"{CType(expression.Type, name)} = {{0}};"); + + foreach (var value in expression.Values) + { + var valueName = EmitExpression(value); + writer.WriteLine($"da_append(&{name}, {valueName});"); + } + + return name; + } + private string EmitExpressionStringConstructor(TypedNodeExpressionStringConstructor expression) { var name = Tmp(); scopes.Peek().DeconstructableNames.Add((name, expression.Type)); var value = EmitExpression(expression.Value); - writer.WriteLine($"{CType(expression.Type, name)} = nub_core_string_from_cstr({value});"); + writer.WriteLine($"{CType(expression.Type, name)} = string_from_cstr({value});"); return name; } diff --git a/compiler/Parser.cs b/compiler/Parser.cs index 3f48c0e..a567fed 100644 --- a/compiler/Parser.cs +++ b/compiler/Parser.cs @@ -263,8 +263,17 @@ public class Parser if (TryExpectKeyword(Keyword.While)) { var condition = ParseExpression(); - var thenBlock = ParseStatement(); - return new NodeStatementWhile(TokensFrom(startIndex), condition, thenBlock); + var block = ParseStatement(); + return new NodeStatementWhile(TokensFrom(startIndex), condition, block); + } + + if (TryExpectKeyword(Keyword.For)) + { + var variableName = ExpectIdent(); + ExpectKeyword(Keyword.In); + var array = ParseExpression(); + var block = ParseStatement(); + return new NodeStatementFor(TokensFrom(startIndex), variableName, array, block); } if (TryExpectKeyword(Keyword.Match)) @@ -363,6 +372,19 @@ public class Parser var target = ParseExpression(); expr = new NodeExpressionUnary(TokensFrom(startIndex), target, NodeExpressionUnary.Op.Negate); } + else if (TryExpectSymbol(Symbol.OpenSquare)) + { + var values = new List(); + + while (!TryExpectSymbol(Symbol.CloseSquare)) + { + var value = ParseExpression(); + values.Add(value); + TryExpectSymbol(Symbol.Comma); + } + + expr = new NodeExpressionArrayLiteral(TokensFrom(startIndex), values); + } else if (TryExpectSymbol(Symbol.OpenCurly)) { var initializers = new List(); @@ -893,6 +915,13 @@ public class NodeStatementWhile(List tokens, NodeExpression condition, No public NodeStatement Body { get; } = body; } +public class NodeStatementFor(List tokens, TokenIdent variableName, NodeExpression array, NodeStatement body) : NodeStatement(tokens) +{ + public TokenIdent VariableName { get; } = variableName; + public NodeExpression Array { get; } = array; + public NodeStatement Body { get; } = body; +} + public class NodeStatementMatch(List tokens, NodeExpression target, List cases) : NodeStatement(tokens) { public NodeExpression Target { get; } = target; @@ -941,6 +970,11 @@ public class NodeExpressionEnumLiteral(List tokens, NodeTypeNamed type, N public NodeExpression? Value { get; } = value; } +public class NodeExpressionArrayLiteral(List tokens, List values) : NodeExpression(tokens) +{ + public List Values { get; } = values; +} + public class NodeExpressionStringConstructor(List tokens, NodeExpression value) : NodeExpression(tokens) { public NodeExpression Value { get; } = value; diff --git a/compiler/Tokenizer.cs b/compiler/Tokenizer.cs index c6d3431..e2bab08 100644 --- a/compiler/Tokenizer.cs +++ b/compiler/Tokenizer.cs @@ -405,6 +405,8 @@ public class Tokenizer "if" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.If), "else" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Else), "while" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.While), + "for" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.For), + "in" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.In), "return" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Return), "module" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Module), "export" => new TokenKeyword(line, startColumn, column - startColumn, Keyword.Export), @@ -557,6 +559,8 @@ public enum Keyword If, Else, While, + For, + In, Return, Module, Export, @@ -627,6 +631,8 @@ public static class TokenExtensions Keyword.If => "if", Keyword.Else => "else", Keyword.While => "while", + Keyword.For => "for", + Keyword.In => "in", Keyword.Return => "return", Keyword.Module => "module", Keyword.Export => "export", diff --git a/compiler/TypeChecker.cs b/compiler/TypeChecker.cs index 89a5c41..1eaf701 100644 --- a/compiler/TypeChecker.cs +++ b/compiler/TypeChecker.cs @@ -99,6 +99,7 @@ public class TypeChecker NodeStatementReturn statement => CheckStatementReturn(statement), NodeStatementVariableDeclaration statement => CheckStatementVariableDeclaration(statement), NodeStatementWhile statement => CheckStatementWhile(statement), + NodeStatementFor statement => CheckStatementFor(statement), NodeStatementMatch statement => CheckStatementMatch(statement), _ => throw new ArgumentOutOfRangeException(nameof(node)) }; @@ -204,6 +205,22 @@ public class TypeChecker } } + private TypedNodeStatementFor CheckStatementFor(NodeStatementFor statement) + { + var array = CheckExpression(statement.Array, null); + if (array.Type is not NubTypeArray arrayType) + throw BasicError($"Cannot iterate over non-array type '{array.Type}'", statement.Array); + + TypedNodeStatement body; + using (EnterScope()) + { + DeclareLocalIdentifier(statement.VariableName, arrayType.ElementType); + body = CheckStatement(statement.Body); + } + + return new TypedNodeStatementFor(statement.Tokens, statement.VariableName, array, body); + } + private TypedNodeStatementMatch CheckStatementMatch(NodeStatementMatch statement) { var target = CheckExpression(statement.Target, null); @@ -264,6 +281,7 @@ public class TypeChecker NodeExpressionStructLiteral expression => CheckExpressionStructLiteral(expression, expectedType), NodeExpressionEnumLiteral expression => CheckExpressionEnumLiteral(expression, expectedType), NodeExpressionStringConstructor expression => CheckExpressionStringConstructor(expression, expectedType), + NodeExpressionArrayLiteral expression => CheckExpressionArrayLiteral(expression, expectedType), _ => throw new ArgumentOutOfRangeException(nameof(node)) }; } @@ -675,6 +693,31 @@ public class TypeChecker return new TypedNodeExpressionStringConstructor(expression.Tokens, NubTypeString.Instance, value); } + private TypedNodeExpressionArrayLiteral CheckExpressionArrayLiteral(NodeExpressionArrayLiteral expression, NubType? expectedType) + { + NubType? elementType = null; + if (expectedType is NubTypeArray arrayType) + elementType = arrayType.ElementType; + + var values = new List(); + + foreach (var value in expression.Values) + { + var checkedValue = CheckExpression(value, elementType); + elementType ??= checkedValue.Type; + + if (!checkedValue.Type.IsAssignableTo(elementType)) + throw BasicError($"Type '{checkedValue.Type}' is not assignable to type of element '{elementType}'", checkedValue); + + values.Add(checkedValue); + } + + if (elementType is null) + throw BasicError("Unable to infer type of array element", expression); + + return new TypedNodeExpressionArrayLiteral(expression.Tokens, NubTypeArray.Get(elementType), values); + } + private NubType ResolveType(NodeType node) { return node switch @@ -908,6 +951,13 @@ public class TypedNodeStatementWhile(List tokens, TypedNodeExpression con public TypedNodeStatement Body { get; } = body; } +public class TypedNodeStatementFor(List tokens, TokenIdent variableName, TypedNodeExpression array, TypedNodeStatement body) : TypedNodeStatement(tokens) +{ + public TokenIdent VariableName { get; } = variableName; + public TypedNodeExpression Array { get; } = array; + public TypedNodeStatement Body { get; } = body; +} + public class TypedNodeStatementMatch(List tokens, TypedNodeExpression target, List cases) : TypedNodeStatement(tokens) { public TypedNodeExpression Target { get; } = target; @@ -962,6 +1012,11 @@ public class TypedNodeExpressionStringConstructor(List tokens, NubType ty public TypedNodeExpression Value { get; } = value; } +public class TypedNodeExpressionArrayLiteral(List tokens, NubType type, List values) : TypedNodeExpression(tokens, type) +{ + public List Values { get; } = values; +} + public class TypedNodeExpressionStructMemberAccess(List tokens, NubType type, TypedNodeExpression target, TokenIdent name) : TypedNodeExpression(tokens, type) { public TypedNodeExpression Target { get; } = target; diff --git a/examples/program/main.nub b/examples/program/main.nub index 6ea92d3..15b2f1b 100644 --- a/examples/program/main.nub +++ b/examples/program/main.nub @@ -11,18 +11,11 @@ enum Message { } func main(): i32 { - let message = getMessage() + let names = ["a", "b", "c", "d"] - match message { - Quit {} - Say msg { - core::println(msg) - } + for name in names { + core::println(name) } return 0 -} - -func getMessage(): Message { - return new Message::Say("testæøå") } \ No newline at end of file