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