diff --git a/compiler/NubLang/Ast/Node.cs b/compiler/NubLang/Ast/Node.cs index 132824a..685e303 100644 --- a/compiler/NubLang/Ast/Node.cs +++ b/compiler/NubLang/Ast/Node.cs @@ -80,6 +80,8 @@ public abstract record LValueExpressionNode(List Tokens, NubType Type) : public abstract record RValueExpressionNode(List Tokens, NubType Type) : ExpressionNode(Tokens, Type); +public abstract record IntermediateExpression(List Tokens) : ExpressionNode(Tokens, new NubVoidType()); + public record StringLiteralNode(List Tokens, string Value) : RValueExpressionNode(Tokens, new NubStringType()); public record CStringLiteralNode(List Tokens, string Value) : RValueExpressionNode(Tokens, new NubCStringType()); @@ -142,4 +144,6 @@ public record SizeBuiltinNode(List Tokens, NubType Type, NubType TargetTy public record FloatToIntBuiltinNode(List Tokens, NubType Type, ExpressionNode Value, NubFloatType ValueType, NubIntType TargetType) : RValueExpressionNode(Tokens, Type); +public record EnumReferenceIntermediateNode(List Tokens, string Module, string Name) : IntermediateExpression(Tokens); + #endregion \ No newline at end of file diff --git a/compiler/NubLang/Ast/TypeChecker.cs b/compiler/NubLang/Ast/TypeChecker.cs index 41ee037..2e35fd8 100644 --- a/compiler/NubLang/Ast/TypeChecker.cs +++ b/compiler/NubLang/Ast/TypeChecker.cs @@ -557,55 +557,56 @@ public sealed class TypeChecker return new FuncCallNode(expression.Tokens, funcType.ReturnType, accessor, parameters); } + private ExpressionNode CheckIdentifier(ExpressionSyntax expression, string moduleName, string name) + { + if (!_importedModules.TryGetValue(moduleName, out var module)) + { + throw new TypeCheckerException(Diagnostic + .Error($"Module {moduleName} not found") + .WithHelp($"import \"{moduleName}\"") + .At(expression) + .Build()); + } + + var function = module.Functions(IsCurretModule(moduleName)).FirstOrDefault(x => x.Name == name); + if (function != null) + { + using (BeginRootScope(moduleName)) + { + var parameters = function.Prototype.Parameters.Select(x => ResolveType(x.Type)).ToList(); + var type = new NubFuncType(parameters, ResolveType(function.Prototype.ReturnType)); + return new FuncIdentifierNode(expression.Tokens, type, moduleName, name, function.Prototype.ExternSymbol); + } + } + + var enumDef = module.Enums(IsCurretModule(moduleName)).FirstOrDefault(x => x.Name == name); + if (enumDef != null) + { + return new EnumReferenceIntermediateNode(expression.Tokens, moduleName, name); + } + + throw new TypeCheckerException(Diagnostic + .Error($"No exported symbol {name} not found in module {moduleName}") + .At(expression) + .Build()); + } + private ExpressionNode CheckLocalIdentifier(LocalIdentifierSyntax expression, NubType? _) { + // note(nub31): Local identifiers can be variables or a symbol in a module var scopeIdent = Scope.LookupVariable(expression.Name); if (scopeIdent != null) { return new VariableIdentifierNode(expression.Tokens, scopeIdent.Type, expression.Name); } - var module = _importedModules[Scope.Module]; - var function = module.Functions(true).FirstOrDefault(x => x.Name == expression.Name); - - if (function != null) - { - var parameters = function.Prototype.Parameters.Select(x => ResolveType(x.Type)).ToList(); - var type = new NubFuncType(parameters, ResolveType(function.Prototype.ReturnType)); - return new FuncIdentifierNode(expression.Tokens, type, Scope.Module, expression.Name, function.Prototype.ExternSymbol); - } - - throw new TypeCheckerException(Diagnostic.Error($"Symbol {expression.Name} not found").At(expression).Build()); + return CheckIdentifier(expression, Scope.Module, expression.Name); } private ExpressionNode CheckModuleIdentifier(ModuleIdentifierSyntax expression, NubType? _) { - if (!_importedModules.TryGetValue(expression.Module, out var module)) - { - throw new TypeCheckerException(Diagnostic - .Error($"Module {expression.Module} not found") - .WithHelp($"import \"{expression.Module}\"") - .At(expression) - .Build()); - } - - var includePrivate = expression.Module == Scope.Module; - - var function = module.Functions(includePrivate).FirstOrDefault(x => x.Name == expression.Name); - if (function != null) - { - using (BeginRootScope(expression.Module)) - { - var parameters = function.Prototype.Parameters.Select(x => ResolveType(x.Type)).ToList(); - var type = new NubFuncType(parameters, ResolveType(function.Prototype.ReturnType)); - return new FuncIdentifierNode(expression.Tokens, type, expression.Module, expression.Name, function.Prototype.ExternSymbol); - } - } - - throw new TypeCheckerException(Diagnostic - .Error($"No exported symbol {expression.Name} not found in module {expression.Module}") - .At(expression) - .Build()); + // note(nub31): Unlike local identifiers, module identifiers does not look for local variables + return CheckIdentifier(expression, expression.Module, expression.Name); } private ExpressionNode CheckStringLiteral(StringLiteralSyntax expression, NubType? expectedType) @@ -668,29 +669,56 @@ public sealed class TypeChecker private ExpressionNode CheckMemberAccess(MemberAccessSyntax expression, NubType? _) { var target = CheckExpression(expression.Target); - switch (target.Type) - { - case NubStructType structType: - { - var field = structType.Fields.FirstOrDefault(x => x.Name == expression.Member); - if (field == null) - { - throw new TypeCheckerException(Diagnostic - .Error($"Struct {target.Type} does not have a field with the name {expression.Member}") - .At(expression) - .Build()); - } - return new StructFieldAccessNode(expression.Tokens, field.Type, target, expression.Member); - } - default: + if (target is EnumReferenceIntermediateNode enumReferenceIntermediate) + { + var enumDef = _importedModules[enumReferenceIntermediate.Module] + .Enums(IsCurretModule(enumReferenceIntermediate.Module)) + .First(x => x.Name == enumReferenceIntermediate.Name); + + var field = enumDef.Fields.FirstOrDefault(x => x.Name == expression.Member); + if (field == null) { throw new TypeCheckerException(Diagnostic - .Error($"Cannot access struct member on non-struct type {target.Type}") + .Error($"Enum {Scope.Module}::{enumReferenceIntermediate.Name} does not have a field named {expression.Member}") + .At(enumDef) + .Build()); + } + + var enumType = enumDef.Type != null ? ResolveType(enumDef.Type) : new NubIntType(false, 64); + if (enumType is not NubIntType enumIntType) + { + throw new TypeCheckerException(Diagnostic.Error("Enum type must be an int type").At(enumDef.Type).Build()); + } + + return enumIntType.Width switch + { + 8 => enumIntType.Signed ? new I8LiteralNode(expression.Tokens, (sbyte)field.Value) : new U8LiteralNode(expression.Tokens, (byte)field.Value), + 16 => enumIntType.Signed ? new I16LiteralNode(expression.Tokens, (short)field.Value) : new U16LiteralNode(expression.Tokens, (ushort)field.Value), + 32 => enumIntType.Signed ? new I32LiteralNode(expression.Tokens, (int)field.Value) : new U32LiteralNode(expression.Tokens, (uint)field.Value), + 64 => enumIntType.Signed ? new I64LiteralNode(expression.Tokens, field.Value) : new U64LiteralNode(expression.Tokens, (ulong)field.Value), + _ => throw new ArgumentOutOfRangeException() + }; + } + + if (target.Type is NubStructType structType) + { + var field = structType.Fields.FirstOrDefault(x => x.Name == expression.Member); + if (field == null) + { + throw new TypeCheckerException(Diagnostic + .Error($"Struct {target.Type} does not have a field with the name {expression.Member}") .At(expression) .Build()); } + + return new StructFieldAccessNode(expression.Tokens, field.Type, target, expression.Member); } + + throw new TypeCheckerException(Diagnostic + .Error($"Cannot access struct member {expression.Member} on type {target.Type}") + .At(expression) + .Build()); } private StructInitializerNode CheckStructInitializer(StructInitializerSyntax expression, NubType? expectedType) @@ -831,35 +859,39 @@ public sealed class TypeChecker private NubType ResolveCustomType(CustomTypeSyntax customType) { - var key = (customType.Module ?? Scope.Module, customType.Name); - - if (_typeCache.TryGetValue(key, out var cachedType)) + if (!_importedModules.TryGetValue(customType.Module ?? Scope.Module, out var module)) { - return cachedType; + throw new TypeCheckerException(Diagnostic + .Error($"Module {customType.Module ?? Scope.Module} not found") + .WithHelp($"import \"{customType.Module ?? Scope.Module}\"") + .At(customType) + .Build()); } - if (!_resolvingTypes.Add(key)) + var enumDef = module.Enums(IsCurretModule(customType.Module)).FirstOrDefault(x => x.Name == customType.Name); + if (enumDef != null) { - var placeholder = new NubStructType(customType.Module ?? Scope.Module, customType.Name, []); - _typeCache[key] = placeholder; - return placeholder; + return enumDef.Type != null ? ResolveType(enumDef.Type) : new NubIntType(false, 64); } - try + var structDef = module.Structs(IsCurretModule(customType.Module)).FirstOrDefault(x => x.Name == customType.Name); + if (structDef != null) { - if (!_importedModules.TryGetValue(customType.Module ?? Scope.Module, out var module)) + var key = (customType.Module ?? Scope.Module, customType.Name); + + if (_typeCache.TryGetValue(key, out var cachedType)) { - throw new TypeCheckerException(Diagnostic - .Error($"Module {customType.Module} not found") - .WithHelp($"import \"{customType.Module}\"") - .At(customType) - .Build()); + return cachedType; } - var includePrivate = customType.Module == Scope.Module; + if (!_resolvingTypes.Add(key)) + { + var placeholder = new NubStructType(customType.Module ?? Scope.Module, customType.Name, []); + _typeCache[key] = placeholder; + return placeholder; + } - var structDef = module.Structs(includePrivate).FirstOrDefault(x => x.Name == customType.Name); - if (structDef != null) + try { var result = new NubStructType(customType.Module ?? Scope.Module, structDef.Name, []); _typeCache[key] = result; @@ -871,16 +903,26 @@ public sealed class TypeChecker result.Fields.AddRange(fields); return result; } + finally + { + _resolvingTypes.Remove(key); + } + } - throw new TypeCheckerException(Diagnostic - .Error($"Type {customType.Name} not found in module {customType.Module}") - .At(customType) - .Build()); - } - finally + throw new TypeCheckerException(Diagnostic + .Error($"Type {customType.Name} not found in module {customType.Module ?? Scope.Module}") + .At(customType) + .Build()); + } + + private bool IsCurretModule(string? module) + { + if (module == null) { - _resolvingTypes.Remove(key); + return true; } + + return module == Scope.Module; } } diff --git a/compiler/NubLang/Generation/Generator.cs b/compiler/NubLang/Generation/Generator.cs index 0b3ece9..c61352f 100644 --- a/compiler/NubLang/Generation/Generator.cs +++ b/compiler/NubLang/Generation/Generator.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Text; using NubLang.Ast; using NubLang.Syntax; @@ -255,6 +256,11 @@ public class Generator private string EmitExpression(ExpressionNode expressionNode) { + if (expressionNode is IntermediateExpression) + { + throw new UnreachableException("Type checker fucked up"); + } + var expr = expressionNode switch { ArrayIndexAccessNode arrayIndexAccessNode => EmitArrayIndexAccess(arrayIndexAccessNode), diff --git a/compiler/NubLang/Syntax/Module.cs b/compiler/NubLang/Syntax/Module.cs index 38188c5..b5776ae 100644 --- a/compiler/NubLang/Syntax/Module.cs +++ b/compiler/NubLang/Syntax/Module.cs @@ -36,4 +36,12 @@ public sealed class Module .Where(x => x.Exported || includePrivate) .ToList(); } + + public List Enums(bool includePrivate) + { + return _definitions + .OfType() + .Where(x => x.Exported || includePrivate) + .ToList(); + } } \ No newline at end of file diff --git a/compiler/NubLang/Syntax/Parser.cs b/compiler/NubLang/Syntax/Parser.cs index dae5710..e54bfca 100644 --- a/compiler/NubLang/Syntax/Parser.cs +++ b/compiler/NubLang/Syntax/Parser.cs @@ -78,9 +78,10 @@ public sealed class Parser { Symbol.Func => ParseFunc(startIndex, exported, null), Symbol.Struct => ParseStruct(startIndex, exported), + Symbol.Enum => ParseEnum(startIndex, exported), _ => throw new ParseException(Diagnostic - .Error($"Expected 'func', 'struct', 'import' or 'module' but found '{keyword.Symbol}'") - .WithHelp("Valid top level statements are 'func', 'struct', 'import' and 'module'") + .Error($"Expected 'func', 'struct', 'enum', 'import' or 'module' but found '{keyword.Symbol}'") + .WithHelp("Valid top level statements are 'func', 'struct', 'enum', 'import' and 'module'") .At(keyword) .Build()) }; @@ -176,6 +177,54 @@ public sealed class Parser return new StructSyntax(GetTokens(startIndex), name.Value, exported, fields); } + private EnumSyntax ParseEnum(int startIndex, bool exported) + { + var name = ExpectIdentifier(); + + TypeSyntax? type = null; + + if (TryExpectSymbol(Symbol.Colon)) + { + type = ParseType(); + } + + List fields = []; + + ExpectSymbol(Symbol.OpenBrace); + + long value = -1; + + while (!TryExpectSymbol(Symbol.CloseBrace)) + { + var memberStartIndex = _tokenIndex; + var fieldName = ExpectIdentifier().Value; + long fieldValue; + + if (TryExpectSymbol(Symbol.Assign)) + { + if (!TryExpectIntLiteral(out var intLiteralToken)) + { + throw new ParseException(Diagnostic + .Error("Value of enum field must be an integer literal") + .At(CurrentToken) + .Build()); + } + + fieldValue = Convert.ToInt64(intLiteralToken.Value, intLiteralToken.Base); + value = fieldValue; + } + else + { + fieldValue = value + 1; + value = fieldValue; + } + + fields.Add(new EnumFieldSyntax(GetTokens(memberStartIndex), fieldName, fieldValue)); + } + + return new EnumSyntax(GetTokens(startIndex), name.Value, exported, type, fields); + } + private StatementSyntax ParseStatement() { var startIndex = _tokenIndex; diff --git a/compiler/NubLang/Syntax/Syntax.cs b/compiler/NubLang/Syntax/Syntax.cs index 360e981..40e28af 100644 --- a/compiler/NubLang/Syntax/Syntax.cs +++ b/compiler/NubLang/Syntax/Syntax.cs @@ -16,6 +16,10 @@ public record StructFieldSyntax(List Tokens, string Name, TypeSyntax Type public record StructSyntax(List Tokens, string Name, bool Exported, List Fields) : DefinitionSyntax(Tokens, Name, Exported); +public record EnumFieldSyntax(List Tokens, string Name, long Value) : SyntaxNode(Tokens); + +public record EnumSyntax(List Tokens, string Name, bool Exported, TypeSyntax? Type, List Fields) : DefinitionSyntax(Tokens, Name, Exported); + public enum UnaryOperatorSyntax { Negate, diff --git a/compiler/NubLang/Syntax/Token.cs b/compiler/NubLang/Syntax/Token.cs index e99beaf..9572297 100644 --- a/compiler/NubLang/Syntax/Token.cs +++ b/compiler/NubLang/Syntax/Token.cs @@ -17,11 +17,14 @@ public enum Symbol // Declaration Func, Struct, - + Enum, + Import, + Module, + // Modifier Extern, Export, - + Colon, DoubleColon, OpenParen, @@ -53,10 +56,8 @@ public enum Symbol Pipe, And, Or, - Module, - Import, At, - QuestionMark + QuestionMark, } public abstract record Token(SourceSpan Span); diff --git a/compiler/NubLang/Syntax/Tokenizer.cs b/compiler/NubLang/Syntax/Tokenizer.cs index f1fe662..166fede 100644 --- a/compiler/NubLang/Syntax/Tokenizer.cs +++ b/compiler/NubLang/Syntax/Tokenizer.cs @@ -20,6 +20,7 @@ public sealed class Tokenizer ["export"] = Symbol.Export, ["import"] = Symbol.Import, ["defer"] = Symbol.Defer, + ["enum"] = Symbol.Enum, }; private static readonly Dictionary Symbols = new() diff --git a/examples/raylib/main.nub b/examples/raylib/main.nub index 4e6f0c0..2c3dfdb 100644 --- a/examples/raylib/main.nub +++ b/examples/raylib/main.nub @@ -4,7 +4,7 @@ module "main" extern "main" func main(argc: i64, argv: [?]cstring): i64 { - raylib::SetConfigFlags(4 | 64) + raylib::SetConfigFlags(raylib::ConfigFlags.FLAG_VSYNC_HINT | raylib::ConfigFlags.FLAG_WINDOW_RESIZABLE) raylib::InitWindow(1600, 900, "Hi from nub-lang") defer raylib::CloseWindow()