diff --git a/Nub.Lang/.idea/.idea.Nub.Lang/.idea/encodings.xml b/Nub.Lang/.idea/.idea.Nub.Lang/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/Nub.Lang/.idea/.idea.Nub.Lang/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Nub.Lang/.idea/.idea.Nub.Lang/.idea/indexLayout.xml b/Nub.Lang/.idea/.idea.Nub.Lang/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/Nub.Lang/.idea/.idea.Nub.Lang/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Nub.Lang/.idea/.idea.Nub.Lang/.idea/vcs.xml b/Nub.Lang/.idea/.idea.Nub.Lang/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/Nub.Lang/.idea/.idea.Nub.Lang/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang.sln b/Nub.Lang/Nub.Lang.sln new file mode 100644 index 0000000..c5692ff --- /dev/null +++ b/Nub.Lang/Nub.Lang.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nub.Lang", "Nub.Lang\Nub.Lang.csproj", "{5047E21F-590D-4CB3-AFF3-064316485009}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5047E21F-590D-4CB3-AFF3-064316485009}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5047E21F-590D-4CB3-AFF3-064316485009}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Nub.Lang/Nub.Lang/Input/program.nub b/Nub.Lang/Nub.Lang/Input/program.nub new file mode 100644 index 0000000..5d03e25 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Input/program.nub @@ -0,0 +1,14 @@ +let SYS_WRITE = 1; +let STD_IN = 0; +let STD_OUT = 1; +let STD_ERR = 2; + +func main() { + write("test"); + syscall(SYS_WRITE, STD_OUT, msg); + return 12; +} + +func write(msg: void) { + syscall(SYS_WRITE, STD_OUT, msg); +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Lexing/IdentifierToken.cs b/Nub.Lang/Nub.Lang/Lexing/IdentifierToken.cs new file mode 100644 index 0000000..6fae8d2 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Lexing/IdentifierToken.cs @@ -0,0 +1,6 @@ +namespace Nub.Lang.Lexing; + +public class IdentifierToken(string value) : Token +{ + public string Value { get; } = value; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Lexing/Lexer.cs b/Nub.Lang/Nub.Lang/Lexing/Lexer.cs new file mode 100644 index 0000000..c23e3f3 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Lexing/Lexer.cs @@ -0,0 +1,131 @@ +using Nub.Lib; + +namespace Nub.Lang.Lexing; + +public class Lexer +{ + private static readonly Dictionary Keywords = new() + { + ["func"] = Symbol.Func, + ["return"] = Symbol.Return, + ["let"] = Symbol.Let, + }; + + private static readonly Dictionary Chars = new() + { + [';'] = Symbol.Semicolon, + [':'] = Symbol.Colon, + ['('] = Symbol.OpenParen, + [')'] = Symbol.CloseParen, + ['{'] = Symbol.OpenBrace, + ['}'] = Symbol.CloseBrace, + ['['] = Symbol.OpenBracket, + [']'] = Symbol.CloseBracket, + [','] = Symbol.Comma, + ['.'] = Symbol.Period, + ['='] = Symbol.Assign, + }; + + private readonly string _src; + private int _index; + + public Lexer(string src) + { + _src = src; + } + + public IEnumerable Lex() + { + _index = 0; + List tokens = []; + while (Peek().HasValue) + { + tokens.Add(ParseToken()); + } + return tokens; + } + + private Token ParseToken() + { + var current = Peek(); + + if (char.IsLetter(current.Value) || current.Value == '_') + { + var buffer = string.Empty; + + while (current.HasValue && (char.IsLetterOrDigit(current.Value) || current.Value == '_')) + { + buffer += current.Value; + Next(); + current = Peek(); + } + + if (Keywords.TryGetValue(buffer, out var keywordSymbol)) + { + return new SymbolToken(keywordSymbol); + } + + return new IdentifierToken(buffer); + } + + if (char.IsDigit(current.Value)) + { + var buffer = string.Empty; + + while (current.HasValue && char.IsDigit(current.Value)) + { + buffer += current.Value; + Next(); + current = Peek(); + } + + return new LiteralToken(Type.Int32, buffer); + } + + if (Chars.TryGetValue(current.Value, out var charSymbol)) + { + Next(); + return new SymbolToken(charSymbol); + } + + if (current.Value == '"') + { + Next(); + var buffer = string.Empty; + + while (true) + { + current = Peek(); + Next(); + if (!current.HasValue) throw new Exception("Unclosed string literal"); + if (current.Value == '"') break; + buffer += current.Value; + } + + return new LiteralToken(Type.Pointer, buffer); + } + + if (char.IsWhiteSpace(current.Value)) + { + Next(); + return new SymbolToken(Symbol.Whitespace); + } + + throw new Exception($"Unknown character {current.Value}"); + } + + private Optional Peek() + { + if (_index < _src.Length) + { + return _src[_index]; + } + + return Optional.Empty(); + } + + private void Next() + { + _index++; + } +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Lexing/LiteralToken.cs b/Nub.Lang/Nub.Lang/Lexing/LiteralToken.cs new file mode 100644 index 0000000..8d5b6f1 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Lexing/LiteralToken.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Lexing; + +public class LiteralToken(Type type, string value) : Token +{ + public Type Type { get; } = type; + public string Value { get; } = value; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Lexing/SymbolToken.cs b/Nub.Lang/Nub.Lang/Lexing/SymbolToken.cs new file mode 100644 index 0000000..3a4d2cc --- /dev/null +++ b/Nub.Lang/Nub.Lang/Lexing/SymbolToken.cs @@ -0,0 +1,25 @@ +namespace Nub.Lang.Lexing; + +public class SymbolToken(Symbol symbol) : Token +{ + public Symbol Symbol { get; } = symbol; +} + +public enum Symbol +{ + Whitespace, + Func, + Return, + Let, + Semicolon, + Colon, + OpenParen, + CloseParen, + OpenBrace, + CloseBrace, + OpenBracket, + CloseBracket, + Comma, + Period, + Assign +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Lexing/Token.cs b/Nub.Lang/Nub.Lang/Lexing/Token.cs new file mode 100644 index 0000000..7e4da5f --- /dev/null +++ b/Nub.Lang/Nub.Lang/Lexing/Token.cs @@ -0,0 +1,3 @@ +namespace Nub.Lang.Lexing; + +public abstract class Token; \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Nub.Lang.csproj b/Nub.Lang/Nub.Lang/Nub.Lang.csproj new file mode 100644 index 0000000..e995cfa --- /dev/null +++ b/Nub.Lang/Nub.Lang/Nub.Lang.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/Nub.Lang/Nub.Lang/Parsing/BlockNode.cs b/Nub.Lang/Nub.Lang/Parsing/BlockNode.cs new file mode 100644 index 0000000..efafc77 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/BlockNode.cs @@ -0,0 +1,6 @@ +namespace Nub.Lang.Parsing; + +public class BlockNode(IEnumerable statements) : StatementNode +{ + public IEnumerable Statements { get; } = statements; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/DefinitionNode.cs b/Nub.Lang/Nub.Lang/Parsing/DefinitionNode.cs new file mode 100644 index 0000000..85e10d6 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/DefinitionNode.cs @@ -0,0 +1,3 @@ +namespace Nub.Lang.Parsing; + +public abstract class DefinitionNode : Node; \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/ExpressionNode.cs b/Nub.Lang/Nub.Lang/Parsing/ExpressionNode.cs new file mode 100644 index 0000000..b7ac992 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/ExpressionNode.cs @@ -0,0 +1,11 @@ +namespace Nub.Lang.Parsing; + +public abstract class ExpressionNode : Node +{ + private Type? _type; + public Type Type + { + get => _type ?? throw new Exception("Tried to access expression type before type was populated"); + set => _type = value; + } +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs b/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs new file mode 100644 index 0000000..1ac761b --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/FuncDefinitionNode.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Parsing; + +public class FuncDefinitionNode(string name, IEnumerable parameters) : DefinitionNode +{ + public string Name { get; } = name; + public IEnumerable Parameters { get; } = parameters; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/FuncParameter.cs b/Nub.Lang/Nub.Lang/Parsing/FuncParameter.cs new file mode 100644 index 0000000..5a36e79 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/FuncParameter.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Parsing; + +public class FuncParameter(string name, Type type) +{ + public string Name { get; } = name; + public Type Type { get; } = type; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/GlobalVariableDefinitionNode.cs b/Nub.Lang/Nub.Lang/Parsing/GlobalVariableDefinitionNode.cs new file mode 100644 index 0000000..1f4c5d5 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/GlobalVariableDefinitionNode.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Parsing; + +public class GlobalVariableDefinitionNode(string name, ExpressionNode value) : DefinitionNode +{ + public string Name { get; } = name; + public ExpressionNode Value { get; } = value; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/Node.cs b/Nub.Lang/Nub.Lang/Parsing/Node.cs new file mode 100644 index 0000000..f23e16c --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/Node.cs @@ -0,0 +1,3 @@ +namespace Nub.Lang.Parsing; + +public abstract class Node; \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/Parser.cs b/Nub.Lang/Nub.Lang/Parsing/Parser.cs new file mode 100644 index 0000000..22e1378 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/Parser.cs @@ -0,0 +1,95 @@ +using Nub.Lang.Lexing; +using Nub.Lib; + +namespace Nub.Lang.Parsing; + +public class Parser +{ + private readonly Token[] _tokens; + private int _index; + + public Parser(IEnumerable tokens) + { + _tokens = tokens.ToArray(); + } + + public IEnumerable Parse() + { + List definitions = []; + while (Peek().HasValue) + { + definitions.Add(ParseDefinition()); + } + return definitions; + } + + private DefinitionNode ParseDefinition() + { + throw new NotImplementedException(); + } + + private Token ExpectToken() + { + var token = Peek(); + if (!token.HasValue) + { + throw new Exception("Reached end of tokens"); + } + + return token.Value; + } + + private SymbolToken ExpectSymbol() + { + var token = ExpectToken(); + if (token is not SymbolToken symbol) + { + throw new Exception($"Expected {nameof(SymbolToken)} but got {token.GetType().Name}"); + } + return symbol; + } + + private void ExpectSymbol(Symbol symbol) + { + var token = ExpectSymbol(); + if (token.Symbol != symbol) + { + throw new Exception($"Expected symbol {symbol} but got {token.Symbol}"); + } + } + + private IdentifierToken ExpectIdentifier() + { + var token = ExpectToken(); + if (token is not IdentifierToken identifier) + { + throw new Exception($"Expected {nameof(IdentifierToken)} but got {token.GetType().Name}"); + } + return identifier; + } + + private LiteralToken ExpectLiteral() + { + var token = ExpectToken(); + if (token is not LiteralToken literal) + { + throw new Exception($"Expected {nameof(LiteralToken)} but got {token.GetType().Name}"); + } + return literal; + } + + private Optional Peek() + { + if (_index < _tokens.Length) + { + return _tokens[_index]; + } + + return Optional.Empty(); + } + + private void Next() + { + _index++; + } +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/StatementNode.cs b/Nub.Lang/Nub.Lang/Parsing/StatementNode.cs new file mode 100644 index 0000000..d2c8544 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/StatementNode.cs @@ -0,0 +1,3 @@ +namespace Nub.Lang.Parsing; + +public abstract class StatementNode : Node; \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/SyscallNode.cs b/Nub.Lang/Nub.Lang/Parsing/SyscallNode.cs new file mode 100644 index 0000000..34223d0 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/SyscallNode.cs @@ -0,0 +1,11 @@ +namespace Nub.Lang.Parsing; + +public class ESyscallNode(IEnumerable parameters) : ExpressionNode +{ + public IEnumerable Parameters { get; } = parameters; +} + +public class SSyscallNode(IEnumerable parameters) : StatementNode +{ + public IEnumerable Parameters { get; } = parameters; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Parsing/VariableAssignmentNode.cs b/Nub.Lang/Nub.Lang/Parsing/VariableAssignmentNode.cs new file mode 100644 index 0000000..0805b53 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Parsing/VariableAssignmentNode.cs @@ -0,0 +1,7 @@ +namespace Nub.Lang.Parsing; + +public class VariableAssignmentNode(string name, ExpressionNode value) : StatementNode +{ + public string Name { get; } = name; + public ExpressionNode Value { get; } = value; +} \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Program.cs b/Nub.Lang/Nub.Lang/Program.cs new file mode 100644 index 0000000..35d3617 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Program.cs @@ -0,0 +1,6 @@ +using Nub.Lang.Lexing; + +var src = File.ReadAllText(args[0]); + +var lexer = new Lexer(src); +var tokens = lexer.Lex(); \ No newline at end of file diff --git a/Nub.Lang/Nub.Lang/Type.cs b/Nub.Lang/Nub.Lang/Type.cs new file mode 100644 index 0000000..d10f759 --- /dev/null +++ b/Nub.Lang/Nub.Lang/Type.cs @@ -0,0 +1,26 @@ +namespace Nub.Lang; + +public class Type(string name) +{ + public static Type Bool => new("bool"); + public static Type Char => new("char"); + + 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 string Name = name; +} \ No newline at end of file