using System.Collections.Generic;
using Phenix.Script.CodeAnalysis.Binding;
using Phenix.Script.CodeAnalysis.Syntax;
using Phenix.Script.CodeAnalysis;
using System.Collections;
internal class Interpreter
private static string[] _GetFormatted(string[][] input_array, int padding = 1)
int[] column_maxes = new int[input_array[0].Length];
foreach (string[] row in input_array)
for (int column_number = 0; column_number < row.Length; ++column_number)
if (row[column_number].Length > column_maxes[column_number])
column_maxes[column_number] = row[column_number].Length;
List<string> return_array = new List<string>();
foreach (string[] row in input_array)
StringBuilder builder = new StringBuilder();
for (int column_number = 0; column_number < row.Length; ++column_number)
builder.Append(row[column_number].PadRight(column_maxes[column_number] + padding));
return_array.Add(builder.ToString() + "\n");
return return_array.ToArray();
private static void _PrintTree(SyntaxNode expression, string indent="", bool is_last=true)
string marker = is_last ? "└───" : "├───";
Console.Write(indent + marker + "(");
Console.ForegroundColor = ConsoleColor.Green;
Console.Write(expression.Type);
Console.ForegroundColor = ConsoleColor.White;
if(expression is SyntaxToken t && t.Value != null)
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write($" {t.Value}");
Console.ForegroundColor = ConsoleColor.White;
indent += is_last ? " " : "│ ";
SyntaxNode last_child = expression.GetChildren().LastOrDefault();
foreach(SyntaxNode child in expression.GetChildren())
_PrintTree(child, indent, child == last_child);
internal static void Main(string[] args)
bool print_tokens = false;
string input = Console.ReadLine();
else if(input == "#tree")
print_tree = !print_tree;
else if(input == "#tokens")
print_tokens = !print_tokens;
else if(input == "#clear")
SyntaxTree tree = SyntaxTree.Parse(input);
Compilation compilation = new Compilation(tree);
EvaluationResult result = compilation.Evaluate();
_PrintTree(tree.RootNode);
List<string[]> array = new List<string[]>();
for (int _index = 0; _index < tree.Tokens.Length; _index++)
string[] _array = new string[]
$"[0]{tree.Tokens[_index].Type}",
$"[1]{tree.Tokens[_index].StartPosition}",
$"[2]{tree.Tokens[_index].EndPosition}",
$"[3]\"{tree.Tokens[_index].Text}\""
string[] formatted = Interpreter._GetFormatted(array.ToArray(), 2);
for (int _index = 0; _index < formatted.Length; _index++)
for(int __index = 0; __index < formatted[_index].Length; __index++)
if(formatted[_index].Substring(__index, (__index + 3 >= formatted[_index].Length ? 0 : 3)) == "[0]")
Console.ForegroundColor = ConsoleColor.Green;
else if(formatted[_index].Substring(__index, (__index + 3 >= formatted[_index].Length ? 0 : 3)) == "[1]")
Console.ForegroundColor = ConsoleColor.Blue;
else if(formatted[_index].Substring(__index, (__index + 3 >= formatted[_index].Length ? 0 : 3)) == "[2]")
Console.ForegroundColor = ConsoleColor.Blue;
else if(formatted[_index].Substring(__index, (__index + 3 >= formatted[_index].Length ? 0 : 3)) == "[3]")
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write(formatted[_index][__index]);
Console.ForegroundColor = ConsoleColor.White;
if(result.Diagnostics.Any())
foreach(Diagnostic diagnostic in result.Diagnostics)
string type = string.Empty;
case(DiagnosticType.Suggestion):
case(DiagnosticType.Error):
case(DiagnosticType.Warning):
string prefix = input.Substring(0, diagnostic.Span.Start);
string error = input.Substring(diagnostic.Span.Start, diagnostic.Span.Length);
string suffix = input.Substring(diagnostic.Span.End);
string feedback = $"{type}[{diagnostic.Span.Start}:{diagnostic.Span.End}]: {diagnostic.Message}.";
Console.ForegroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.White;
Console.Write("Result: (");
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.Write(result.Value);
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine($")!");
Console.ForegroundColor = ConsoleColor.White;
namespace Phenix.Script.CodeAnalysis.Syntax
internal static class SyntaxFacts
internal static int GetBinaryOperatorPrecedence(this SyntaxType type)
case(SyntaxType.RootExtractionOperator):
case(SyntaxType.ExponentationOperator):
case(SyntaxType.MultiplicationOperator):
case(SyntaxType.DivisionOperator):
case(SyntaxType.ModulusOperator):
case(SyntaxType.AdditionOperator):
case(SyntaxType.SubtractionOperator):
case(SyntaxType.NotEqualToOperator):
case(SyntaxType.EqualToOperator):
case(SyntaxType.AndOperator):
case(SyntaxType.OrOperator):
internal static int GetUnaryOperatorPrecedence(this SyntaxType type)
case(SyntaxType.AdditionOperator):
case(SyntaxType.SubtractionOperator):
case(SyntaxType.NotOperator):
internal static SyntaxType GetKeywordType(string text)
return SyntaxType.TrueKeyword;
return SyntaxType.FalseKeyword;
return SyntaxType.Identifier;
internal sealed class SyntaxToken : SyntaxNode
internal override SyntaxType Type { get; }
internal int StartPosition { get; private set; }
internal int EndPosition { get; private set; }
internal object Value { get; private set; }
internal string Text { get; private set; }
internal TextSpan Span { get; private set; }
internal SyntaxToken(SyntaxType type, TextSpan span, string text, object value)
internal override IEnumerable<SyntaxNode> GetChildren()
return Enumerable.Empty<SyntaxNode>();
internal abstract class SyntaxNode
internal abstract SyntaxType Type { get; }
internal abstract IEnumerable<SyntaxNode> GetChildren();
internal abstract class SyntaxExpression : SyntaxNode
internal sealed class LiteralExpression : SyntaxExpression
internal override SyntaxType Type => SyntaxType.LiteralExpression;
internal SyntaxToken Literal { get; private set; }
internal object Value { get; private set; }
internal LiteralExpression(SyntaxToken literal, object value)
internal override IEnumerable<SyntaxNode> GetChildren()
yield return this.Literal;
internal sealed class BinaryExpression : SyntaxExpression
internal override SyntaxType Type => SyntaxType.BinaryExpression;
internal SyntaxToken OperatorToken { get; private set; }
internal SyntaxExpression Right { get; private set; }
internal SyntaxExpression Left { get; private set; }
internal BinaryExpression(SyntaxExpression left, SyntaxToken operator_token, SyntaxExpression right)
this.OperatorToken = operator_token;
internal override IEnumerable<SyntaxNode> GetChildren()
yield return this.OperatorToken;
internal sealed class UnaryExpression : SyntaxExpression
internal override SyntaxType Type => SyntaxType.UnaryExpression;
internal SyntaxToken OperatorToken { get; private set; }
internal SyntaxExpression Operand { get; private set; }
internal UnaryExpression(SyntaxExpression operand, SyntaxToken operator_token)
this.OperatorToken = operator_token;
internal override IEnumerable<SyntaxNode> GetChildren()
yield return this.OperatorToken;
yield return this.Operand;
internal sealed class ParenthesizedExpression : SyntaxExpression
internal override SyntaxType Type => SyntaxType.ParenthesizedExpression;
internal SyntaxToken OpenParenthesisToken { get; private set; }
internal SyntaxExpression Expression { get; private set; }
internal SyntaxToken CloseParenthesisToken { get; private set; }
internal ParenthesizedExpression(SyntaxToken open, SyntaxExpression expression, SyntaxToken close)
this.CloseParenthesisToken = close;
this.OpenParenthesisToken = open;
this.Expression = expression;
internal override IEnumerable<SyntaxNode> GetChildren()
yield return this.OpenParenthesisToken;
yield return this.Expression;
yield return this.CloseParenthesisToken;
internal sealed class NameExpression : SyntaxExpression
internal override SyntaxType Type => SyntaxType.NameExpression;
internal SyntaxToken Identifier { get; private set; }
internal NameExpression(SyntaxToken identifier)
internal override IEnumerable<SyntaxNode> GetChildren()
yield return this.Identifier;
internal sealed class AssignmentExpression : SyntaxExpression
internal override SyntaxType Type => SyntaxType.AssignmentExpression;
internal SyntaxExpression Expression { get; private set; }
internal SyntaxToken EqualsToken { get; private set; }
internal SyntaxToken Identifier { get; private set; }
internal AssignmentExpression(SyntaxToken identifier, SyntaxToken equals_token, SyntaxExpression expression)
this.Identifier = identifier;
this.EqualsToken = equals_token;
this.Expression = expression;
internal override IEnumerable<SyntaxNode> GetChildren()
yield return this.Identifier;
yield return this.EqualsToken;
yield return this.Expression;
internal sealed class SyntaxTree
internal List<Diagnostic> Diagnostics { get; private set; }
internal SyntaxToken[] Tokens { get; private set; }
internal SyntaxExpression RootNode { get; private set; }
internal SyntaxToken EndOfFile { get; private set; }
internal SyntaxTree(SyntaxExpression root, SyntaxToken end_of_file, List<Diagnostic> diagnostics, SyntaxToken[] tokens)
this.Diagnostics = diagnostics;
this.EndOfFile = end_of_file;
internal static SyntaxTree Parse(string input)
Lexer lexer = new Lexer(input);
SyntaxToken[] tokens = lexer.GetTokens();
Parser parser = new Parser(tokens);
return parser.Parse(lexer.Diagnostics);
internal sealed class Lexer
public DiagnosticList Diagnostics => _Diagnostics;
private readonly string _Input;
private DiagnosticList _Diagnostics = new DiagnosticList();
private int _Position = 0;
internal Lexer(string input)
internal SyntaxToken[] GetTokens()
List<SyntaxToken> token_list = new List<SyntaxToken>();
SyntaxToken current = _GetNextToken();
if (current.Type == SyntaxType.EndOfFile)
return token_list.ToArray();
private char _GetCurrentCharactor()
return this._Input[this._Position];
private char _PeakCharacter(int index)
if (index < this._Input.Length)
return this._Input[index];
private string _PeakToNextWhiteSpace()
StringBuilder value = new StringBuilder();
for (int index = this._Position; true; index++)
char current = _PeakCharacter(index);
if(index == this._Input.Length || current == ' ')
private SyntaxToken _GetNextToken()
if (this._Position >= this._Input.Length)
return new SyntaxToken(SyntaxType.EndOfFile, new TextSpan(_Position, 1), "\0", null);
else if (char.IsDigit(_GetCurrentCharactor()))
if (_PeakToNextWhiteSpace().Contains('.'))
int start = this._Position;
while (char.IsDigit(_PeakCharacter(this._Position)) || _PeakCharacter(this._Position) == '.')
int length = end - start;
string text = _Input.Substring(start, length);
if(!float.TryParse(text, out float value))
_Diagnostics.ReportInvalidDecimal(DiagnosticType.Error, new TextSpan(start, length), text);
return new SyntaxToken(SyntaxType.DecimalType, new TextSpan(start, length), text, value);
while (char.IsDigit(_PeakCharacter(_Position)))
int length = end - start;
string text = _Input.Substring(start, length);
if(!int.TryParse(text, out int value))
_Diagnostics.ReportInvalidInteger(DiagnosticType.Error, new TextSpan(start, length), text);
return new SyntaxToken(SyntaxType.IntegerType, new TextSpan(start, length), text, value);
else if (char.IsWhiteSpace(_GetCurrentCharactor()))
while (char.IsWhiteSpace(_PeakCharacter(_Position)))
int length = end - start;
string text = _Input.Substring(start, length);
return new SyntaxToken(SyntaxType.WhiteSpace, new TextSpan(start, length), text, null);
else if (char.IsLetter(_GetCurrentCharactor()))
while (char.IsLetter(_PeakCharacter(_Position)))
int length = end - start;
string text = _Input.Substring(start, length);
SyntaxType type = SyntaxFacts.GetKeywordType(text);
return new SyntaxToken(type, new TextSpan(start, length), text, null);
if (_GetCurrentCharactor() == '+')
return new SyntaxToken(SyntaxType.AdditionOperator, new TextSpan(_Position++, 1), "+", null);
else if (_GetCurrentCharactor() == '-')
return new SyntaxToken(SyntaxType.SubtractionOperator, new TextSpan(_Position++, 1), "-", null);
else if (_GetCurrentCharactor() == '*')
return new SyntaxToken(SyntaxType.MultiplicationOperator, new TextSpan(_Position++, 1), "*", null);
else if (_GetCurrentCharactor() == '/')
return new SyntaxToken(SyntaxType.DivisionOperator, new TextSpan(_Position++, 1), "/", null);
else if (_GetCurrentCharactor() == '(')
return new SyntaxToken(SyntaxType.OpenParenthesis, new TextSpan(_Position++, 1), "(", null);
else if (_GetCurrentCharactor() == ')')
return new SyntaxToken(SyntaxType.CloseParenthesis, new TextSpan(_Position++, 1), ")", null);
else if(_GetCurrentCharactor() == '!')
return new SyntaxToken(SyntaxType.NotOperator, new TextSpan(_Position++, 1), "!", null);
else if(_GetCurrentCharactor() == '&' && _PeakCharacter(_Position + 1) == '&')
int start = this._Position;
return new SyntaxToken(SyntaxType.AndOperator, new TextSpan(start, 2), "&&", null);
else if(_GetCurrentCharactor() == '|' && _PeakCharacter(_Position + 1) == '|')
int start = this._Position;
return new SyntaxToken(SyntaxType.OrOperator, new TextSpan(start, 2), "||", null);
else if(_GetCurrentCharactor() == '!' && _PeakCharacter(_Position + 1) == '=')
int start = this._Position;
return new SyntaxToken(SyntaxType.NotEqualToOperator, new TextSpan(start, 2), "!=", null);
else if(_GetCurrentCharactor() == '=' && _PeakCharacter(_Position + 1) == '=')
int start = this._Position;
return new SyntaxToken(SyntaxType.EqualToOperator, new TextSpan(start, 2), "==", null);
this._Diagnostics.ReportInvalidCharactor(DiagnosticType.Error, new TextSpan(_Position++, 1), this._PeakCharacter(this._Position - 1));
return new SyntaxToken(SyntaxType.Invalid, new TextSpan(_Position++, 1), this._PeakCharacter(_Position-1).ToString(), null);
internal sealed class Parser
public DiagnosticList Diagnostics => this._Diagnostics;
private readonly SyntaxToken[] __Tokens;
private readonly SyntaxToken[] _Tokens;
private DiagnosticList _Diagnostics = new DiagnosticList();
private int _Position = 0;
internal Parser(SyntaxToken[] tokens)
List<SyntaxToken> token_array = new List<SyntaxToken>();
foreach(SyntaxToken token in tokens)
if(token.Type != SyntaxType.WhiteSpace && token.Type != SyntaxType.Invalid)
this._Tokens = token_array.ToArray();
private SyntaxToken _GetCurrentToken()
return this._PeekToken(0);
private SyntaxToken _PeekToken(int offset)
int index = this._Position + offset;
if(index >= this._Tokens.Length)
return this._Tokens[this._Tokens.Length - 1];
return this._Tokens[index];
private SyntaxToken _ConsumeToken()
SyntaxToken current = this._GetCurrentToken();
private SyntaxToken _MatchToken(SyntaxType expected_type)
if(this._GetCurrentToken().Type == expected_type)
return this._ConsumeToken();
int start = this._GetCurrentToken().StartPosition;
int end = this._GetCurrentToken().EndPosition;
int length = this._GetCurrentToken().StartPosition - end;
this._Diagnostics.ReportUnexpectedToken(DiagnosticType.Error, this._GetCurrentToken().Span, this._GetCurrentToken().Type, expected_type);
return new SyntaxToken(expected_type, new TextSpan(start, length), null, null);
private SyntaxExpression _ParsePrimaryExpression()
switch (_GetCurrentToken().Type)
case (SyntaxType.OpenParenthesis):
SyntaxToken open = _ConsumeToken();
SyntaxExpression expression = _ParseBinaryExpression();
SyntaxToken close = _MatchToken(SyntaxType.CloseParenthesis);
return new ParenthesizedExpression(open, expression, close);
case (SyntaxType.Identifier):
SyntaxToken identifier_token = _ConsumeToken();
return new NameExpression(identifier_token);
case (SyntaxType.FalseKeyword):
case (SyntaxType.TrueKeyword):
SyntaxToken token = _ConsumeToken();
bool value = token.Type == SyntaxType.TrueKeyword;
return new LiteralExpression(token, value);
SyntaxToken integer_token = _MatchToken(SyntaxType.IntegerType);
return new LiteralExpression(integer_token, integer_token.Value);
private SyntaxExpression _ParseAssigmentExpression()
if(_PeekToken(0).Type == SyntaxType.Identifier && _PeekToken(1).Type == SyntaxType.EqualsOperator)
SyntaxToken identifier_token = _ConsumeToken();
SyntaxToken operator_token = _ConsumeToken();
SyntaxExpression right = _ParseAssigmentExpression();
return new AssignmentExpression(identifier_token, operator_token, right);
return _ParseBinaryExpression();
private SyntaxExpression _ParseExpression()
return _ParseAssigmentExpression();
private SyntaxExpression _ParseBinaryExpression(int parent_precedence=0)
int unary_precedence = _GetCurrentToken().Type.GetUnaryOperatorPrecedence();
if(unary_precedence != 0 && unary_precedence > parent_precedence)
SyntaxToken operator_token = _ConsumeToken();
SyntaxExpression operand = _ParsePrimaryExpression();
left = new UnaryExpression(operand, operator_token);
left = _ParsePrimaryExpression();
int precedence = _GetCurrentToken().Type.GetBinaryOperatorPrecedence();
if(precedence == 0 || precedence <= parent_precedence)
SyntaxToken operator_token = this._ConsumeToken();
SyntaxExpression right = _ParseBinaryExpression(precedence);
left = new BinaryExpression(left, operator_token, right);
internal SyntaxTree Parse(DiagnosticList diagnostics)
SyntaxExpression root = _ParseExpression();
SyntaxToken end_of_file = _MatchToken(SyntaxType.EndOfFile);
return new SyntaxTree(root, end_of_file, this.Diagnostics.GetAddRange(diagnostics), this.__Tokens);
namespace Phenix.Script.CodeAnalysis
internal sealed class EvaluationResult
internal IReadOnlyList<Diagnostic> Diagnostics { get; private set; }
internal object Value { get; private set; }
internal EvaluationResult(IEnumerable<Diagnostic> diagnostics, object value)
this.Diagnostics = diagnostics.ToArray();
internal sealed class Compilation
public SyntaxTree Tree { get; set; }
internal Compilation(SyntaxTree tree)
internal EvaluationResult Evaluate()
Binder binder = new Binder();
BoundExpression root = binder.BindExpression(Tree.RootNode);
Evaluator evaluator = new Evaluator(root);
object value = evaluator.Evaluate();
IReadOnlyList<Diagnostic> diagnostics = binder.Diagnostics.GetAddRange(Tree.Diagnostics);
return new EvaluationResult(diagnostics, null);
return new EvaluationResult(Array.Empty<Diagnostic>(), value);
internal sealed class Evaluator
private readonly BoundExpression _Root;
internal Evaluator(BoundExpression root)
private object _EvaluateExpression(BoundExpression root)
if(root is BoundLiteralExpression l)
if(root is BoundUnaryExpression u)
object operand = _EvaluateExpression(u.Operand);
if(u.Operator.OperatorType == BoundUnaryOperatorType.Positive)
else if(u.Operator.OperatorType == BoundUnaryOperatorType.Negative)
else if(u.Operator.OperatorType == BoundUnaryOperatorType.LogicalNot)
return !((bool) operand);
throw new Exception($"FATAL ERROR: Unexpected unary operator <{u.Operator.OperatorType}>!");
if(root is BoundBinaryExpression b)
object left = _EvaluateExpression(b.Left);
object right = _EvaluateExpression(b.Right);
if(b.Operator.OperatorType == BoundBinaryOperatorType.Addition)
return ((int) left) + ((int) right);
else if(b.Operator.OperatorType == BoundBinaryOperatorType.Subtraction)
return ((int) left) - ((int) right);
else if(b.Operator.OperatorType == BoundBinaryOperatorType.Multiplication)
return ((int) left) * ((int) right);
else if(b.Operator.OperatorType == BoundBinaryOperatorType.Division)
return ((int) left) / ((int) right);
else if(b.Operator.OperatorType == BoundBinaryOperatorType.LogicalAnd)
return ((bool) left) && ((bool) right);
else if(b.Operator.OperatorType == BoundBinaryOperatorType.LogicalOr)
return ((bool) left) || ((bool) right);
else if(b.Operator.OperatorType == BoundBinaryOperatorType.EqualTo)
return Equals(left, right);
else if(b.Operator.OperatorType == BoundBinaryOperatorType.NotEqualTo)
return !Equals(left, right);
throw new Exception($"FATAL ERROR: Unexpected binary operator <{b.Operator.OperatorType}>!");
throw new Exception($"FATAL ERROR: Unexpected expression <{root.NodeType}>!");
internal object Evaluate()
return _EvaluateExpression(_Root);
internal int Start { get; private set; }
internal int Length { get; private set; }
internal int End { get; private set; }
internal TextSpan(int start, int length)
this.End = start + length;
internal enum DiagnosticType
internal sealed class Diagnostic
internal DiagnosticType Type { get; private set; }
internal TextSpan Span { get; private set; }
internal string Message { get; private set; }
internal Diagnostic(DiagnosticType type, TextSpan text, string message)
internal sealed class DiagnosticList : IEnumerable<Diagnostic>
private readonly List<Diagnostic> _Diagnostics = new List<Diagnostic>();
public IEnumerator<Diagnostic> GetEnumerator() => this._Diagnostics.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
private void _Report(DiagnosticType type, TextSpan span, string message)
Diagnostic diagnostic = new Diagnostic(type, span, message);
this._Diagnostics.Add(diagnostic);
internal void ReportInvalidInteger(DiagnosticType diagnostic_type, TextSpan text_span, string text)
this._Report(diagnostic_type, text_span, $"Value, \"{text}\", is not a valid <System.Integer>");
internal void ReportInvalidDecimal(DiagnosticType diagnostic_type, TextSpan text_span, string text)
this._Report(diagnostic_type, text_span, $"Value, \"{text}\", is not a valid <System.Decimal>");
internal void ReportInvalidCharactor(DiagnosticType diagnostic_type, TextSpan text_span, char text)
this._Report(diagnostic_type, text_span, $"Charactor, \"{text}\", is invalid");
internal void ReportUnexpectedToken(DiagnosticType diagnostic_type, TextSpan text_span, SyntaxType type, SyntaxType expected_type)
this._Report(diagnostic_type, text_span, $"Unexpected token, \"{type}\", expected, \"{expected_type}\"");
internal void ReportUndefinedUnaryOperator(DiagnosticType diagnostic_type, TextSpan text_span, string operator_text, Type expected_type)
this._Report(diagnostic_type, text_span, $"Unary operator, \"{operator_text}\", is undefined for, <{expected_type}>");
internal void ReportUndefinedBinaryOperator(DiagnosticType diagnostic_type, TextSpan text_span, string operator_text, Type left_type, Type right_type)
this._Report(diagnostic_type, text_span, $"Binary operator, \"{operator_text}\", is undefined for, <{left_type}>, and, <{right_type}>");
internal List<Diagnostic> GetAddRange(DiagnosticList diagnostics)
this._Diagnostics.AddRange(diagnostics._Diagnostics);
return this._Diagnostics;
internal List<Diagnostic> GetAddRange(List<Diagnostic> diagnostics)
this._Diagnostics.AddRange(diagnostics);
return this._Diagnostics;
namespace Phenix.Script.CodeAnalysis.Binding
internal enum BoundNodeType
internal enum BoundUnaryOperatorType
internal enum BoundBinaryOperatorType
internal abstract class BoundNode
internal abstract BoundNodeType NodeType { get; }
internal abstract class BoundExpression : BoundNode
internal abstract Type OperandType { get; }
internal sealed class BoundLiteralExpression : BoundExpression
internal override BoundNodeType NodeType => BoundNodeType.LiteralExpression;
internal override Type OperandType => Value.GetType();
internal object Value { get; private set; }
internal BoundLiteralExpression(object value)
internal sealed class BoundUnaryExpression : BoundExpression
internal override BoundNodeType NodeType => BoundNodeType.UnaryExpression;
internal override Type OperandType => Operand.OperandType;
internal BoundUnaryOperator Operator { get; private set; }
internal BoundExpression Operand { get; private set; }
internal BoundUnaryExpression(BoundExpression operand, BoundUnaryOperator _operator)
this.Operator = _operator;
internal sealed class BoundBinaryExpression : BoundExpression
internal override BoundNodeType NodeType => BoundNodeType.BinaryExpression;
internal override Type OperandType => this.Operator.ResultType;
internal BoundBinaryOperator Operator { get; private set; }
internal BoundExpression Left { get; private set; }
internal BoundExpression Right { get; private set; }
internal BoundBinaryExpression(BoundExpression left, BoundBinaryOperator _operator, BoundExpression right)
this.Operator = _operator;
internal sealed class BoundUnaryOperator
internal SyntaxType Type { get; private set; }
internal BoundUnaryOperatorType OperatorType { get; private set; }
internal Type OperandType { get; private set; }
internal Type ResultType { get; private set; }
private BoundUnaryOperator(SyntaxType type, BoundUnaryOperatorType operator_type, Type operand_type) :
this(type, operator_type, operand_type, operand_type)
private BoundUnaryOperator(SyntaxType type, BoundUnaryOperatorType operator_type, Type operand_type, Type result_type)
this.ResultType = result_type;
this.OperatorType = operator_type;
this.OperandType = operand_type;
private static BoundUnaryOperator[] _Operators =
new BoundUnaryOperator(SyntaxType.NotOperator, BoundUnaryOperatorType.LogicalNot, typeof(bool)),
new BoundUnaryOperator(SyntaxType.AdditionOperator, BoundUnaryOperatorType.Positive, typeof(int)),
new BoundUnaryOperator(SyntaxType.SubtractionOperator, BoundUnaryOperatorType.Negative, typeof(int))
internal static BoundUnaryOperator Bind(SyntaxType type, Type operand_type)
foreach(BoundUnaryOperator _operator in BoundUnaryOperator._Operators)
if(_operator.Type == type && _operator.OperandType == operand_type)
internal sealed class BoundBinaryOperator
internal SyntaxType Type { get; private set; }
internal BoundBinaryOperatorType OperatorType { get; private set; }
internal Type LeftType { get; private set; }
internal Type RightType { get; private set; }
internal Type ResultType { get; private set; }
private BoundBinaryOperator(SyntaxType type, BoundBinaryOperatorType operator_type, Type operand_type) :
this(type, operator_type, operand_type, operand_type, operand_type)
private BoundBinaryOperator(SyntaxType type, BoundBinaryOperatorType operator_type, Type operand_type, Type result_type) :
this(type, operator_type, operand_type, operand_type, result_type)
private BoundBinaryOperator(SyntaxType type, BoundBinaryOperatorType operator_type, Type left_type, Type right_type, Type result_type)
this.OperatorType = operator_type;
this.RightType = right_type;
this.LeftType = left_type;
this.ResultType = result_type;
private static BoundBinaryOperator[] _Operators =
new BoundBinaryOperator(SyntaxType.AndOperator, BoundBinaryOperatorType.LogicalAnd, typeof(bool)),
new BoundBinaryOperator(SyntaxType.OrOperator, BoundBinaryOperatorType.LogicalOr, typeof(bool)),
new BoundBinaryOperator(SyntaxType.AdditionOperator, BoundBinaryOperatorType.Addition, typeof(int)),
new BoundBinaryOperator(SyntaxType.SubtractionOperator, BoundBinaryOperatorType.Subtraction, typeof(int)),
new BoundBinaryOperator(SyntaxType.MultiplicationOperator, BoundBinaryOperatorType.Multiplication, typeof(int)),
new BoundBinaryOperator(SyntaxType.DivisionOperator, BoundBinaryOperatorType.Division, typeof(int)),
new BoundBinaryOperator(SyntaxType.NotEqualToOperator, BoundBinaryOperatorType.NotEqualTo, typeof(int), typeof(bool)),
new BoundBinaryOperator(SyntaxType.EqualToOperator, BoundBinaryOperatorType.EqualTo, typeof(int), typeof(bool)),
new BoundBinaryOperator(SyntaxType.NotEqualToOperator, BoundBinaryOperatorType.NotEqualTo, typeof(bool)),
new BoundBinaryOperator(SyntaxType.EqualToOperator, BoundBinaryOperatorType.EqualTo, typeof(bool)),
internal static BoundBinaryOperator Bind(SyntaxType type, Type left_type, Type right_type)
foreach(BoundBinaryOperator _operator in BoundBinaryOperator._Operators)
if(_operator.Type == type && _operator.LeftType == left_type && _operator.RightType == right_type)
internal sealed class Binder
private DiagnosticList _Diagnostics = new DiagnosticList();
internal DiagnosticList Diagnostics => _Diagnostics;
private BoundExpression _BindLiteralExpression(LiteralExpression expression)
object value = expression.Value ?? 0;
return new BoundLiteralExpression(value);
private BoundExpression _BindUnaryExpression(UnaryExpression expression)
BoundExpression operand = BindExpression(expression.Operand);
BoundUnaryOperator _operator = BoundUnaryOperator.Bind(expression.OperatorToken.Type, operand.OperandType);
_Diagnostics.ReportUndefinedUnaryOperator(DiagnosticType.Error, expression.OperatorToken.Span, expression.OperatorToken.Text, operand.OperandType);
return new BoundUnaryExpression(operand, _operator);
private BoundExpression _BindBinaryExpression(BinaryExpression expression)
BoundExpression left = BindExpression(expression.Left);
BoundExpression right = BindExpression(expression.Right);
BoundBinaryOperator _operator = BoundBinaryOperator.Bind(expression.OperatorToken.Type, left.OperandType, right.OperandType);
TextSpan span = expression.OperatorToken.Span;
_Diagnostics.ReportUndefinedBinaryOperator(DiagnosticType.Error, span, expression.OperatorToken.Text, left.OperandType, right.OperandType);
return new BoundBinaryExpression(left, _operator, right);
internal BoundExpression BindExpression(SyntaxExpression expression)
case(SyntaxType.LiteralExpression):
return _BindLiteralExpression((LiteralExpression) expression);
case(SyntaxType.BinaryExpression):
return _BindBinaryExpression((BinaryExpression) expression);
case(SyntaxType.UnaryExpression):
return _BindUnaryExpression((UnaryExpression) expression);
case (SyntaxType.NameExpression)
throw new Exception($"Unexpected syntax <{expression.Type}>.");
using System.Collections.Generic;
using Phenix.Script.CodeAnalysis;
internal static string[] GetFormatted(string[][] input_array, int padding = 1)
int[] column_maxes = new int[input_array[0].Length];
foreach (string[] row in input_array)
for (int column_number = 0; column_number < row.Length; ++column_number)
if (row[column_number].Length > column_maxes[column_number])
column_maxes[column_number] = row[column_number].Length;
List<string> return_array = new List<string>();
foreach (string[] row in input_array)
StringBuilder builder = new StringBuilder();
for (int column_number = 0; column_number < row.Length; ++column_number)
builder.Append(row[column_number].PadRight(column_maxes[column_number] + padding));
return_array.Add(builder.ToString() + "\n");
return return_array.ToArray();
internal static void Main(string[] args)
string input = Console.ReadLine();
Lexer lexer = new Lexer(input);
Token[] tokens = lexer.GetTokens();
List<string[]> array = new List<string[]>();
for (int _index = 0; _index < tokens.Length; _index++)
string[] _array = new string[]
$"{tokens[_index].Type}",
$"{tokens[_index].StartPosition} - {tokens[_index].EndPosition}",
$"\"{tokens[_index].Text}\"",
string[] formatted = Testing.GetFormatted(array.ToArray(), 2);
for (int _index = 0; _index < formatted.Length; _index++)
Console.Write(formatted[_index]);
namespace Phenix.Script.CodeAnalysis
internal sealed class Token : Child
internal int StartPosition { get; private set; }
internal int EndPosition { get; private set; }
internal object Value { get; private set; }
internal TokenType Type { get; private set; }
internal string Text { get; private set; }
internal Token(TokenType type, int start, int end, string text, object value)
internal override IEnumerable<Child> GetChildren()
return Enumerable.Empty<Child>();
internal abstract class Child
internal abstract IEnumerable<Child> GetChildren();
internal abstract class Node : Child
internal abstract TokenType Type { get; }
internal abstract class Expression : Node
internal sealed class LiteralExpression : Expression
internal override TokenType Type => TokenType.LiteralExpression;
internal Token Literal { get; private set; }
internal LiteralExpression(Token literal)
internal override IEnumerable<Child> GetChildren()
yield return this.Literal;
internal sealed class BinaryExpression : Expression
internal override TokenType Type => TokenType.BinaryExpression;
internal Token OperatorToken { get; private set; }
internal Expression RightNode { get; private set; }
internal Expression LeftNode { get; private set; }
internal BinaryExpression(Expression left_node, Token operator_token, Expression right_node)
this.OperatorToken = operator_token;
this.RightNode = right_node;
this.LeftNode = left_node;
internal override IEnumerable<Child> GetChildren()
yield return this.LeftNode;
yield return this.OperatorToken;
yield return this.RightNode;
internal sealed class Lexer
private readonly string _Input;
private int _Position = 0;
internal Lexer(string input)
internal Token[] GetTokens()
List<Token> token_list = new List<Token>();
Token current = GetNextToken();
if (current.Type == TokenType.EndOfFile)
return token_list.ToArray();
private char GetCurrentCharactor()
return this._Input[this._Position];
private char PeakCharacter(int index)
if (index < this._Input.Length)
return this._Input[index];
private string PeakString(int start, int length)
if ((start + length) < this._Input.Length)
return this._Input.Substring(start, length);
private string PeakToNextWhiteSpace()
StringBuilder value = new StringBuilder();
for (int index = this._Position; true; index++)
char current = PeakCharacter(index);
if(index == this._Input.Length || current == ' ')
Console.WriteLine(value.ToString());
private Token GetNextToken()
if (this._Position == this._Input.Length)
return new Token(TokenType.EndOfFile, _Position, _Position, "\0", null);
else if (char.IsDigit(GetCurrentCharactor()))
if (PeakToNextWhiteSpace().Contains('.'))
int start = this._Position;
while (char.IsDigit(PeakCharacter(this._Position)) || PeakCharacter(this._Position) == '.')
int length = end - start;
string text = _Input.Substring(start, length);
float.TryParse(text, out float value);
return new Token(TokenType.DecimalType, start, end, text, value);
while (char.IsDigit(PeakCharacter(_Position)))
int length = end - start;
string text = _Input.Substring(start, length);
int.TryParse(text, out int value);
return new Token(TokenType.IntegerType, start, end, text, value);
else if (char.IsWhiteSpace(GetCurrentCharactor()))
while (char.IsWhiteSpace(PeakCharacter(_Position)))
int length = end - start;
string text = _Input.Substring(start, length);
return new Token(TokenType.WhiteSpace, start, end, text, null);
if (GetCurrentCharactor() == '+')
return new Token(TokenType.AdditionOperator, _Position, _Position++, "+", null);
else if (GetCurrentCharactor() == '-')
return new Token(TokenType.SubtractionOperator, _Position, _Position++, "-", null);
else if (GetCurrentCharactor() == '*')
return new Token(TokenType.MultiplicationOperator, _Position, _Position++, "*", null);
else if (GetCurrentCharactor() == '/')
return new Token(TokenType.DivisionOperator, _Position, _Position++, "/", null);
else if (GetCurrentCharactor() == '(')
return new Token(TokenType.OpenParenthesis, _Position, _Position++, "(", null);
else if (GetCurrentCharactor() == ')')
return new Token(TokenType.CloseParenthesis, _Position, _Position++, ")", null);
return new Token(TokenType.Invalid, _Position, _Position++, this.PeakCharacter(_Position-1).ToString(), null);
internal sealed class Parser
private readonly Token[] _Tokens;
private int _Position = 0;
internal Parser(Token[] tokens)
private Token _GetCurrentToken()
return this._PeekToken(0);
private Token _PeekToken(int offset)
int index = this._Position + offset;
if(index >= this._Tokens.Length)
return this._Tokens[this._Tokens.Length - 1];
return this._Tokens[index];
private Token _GetCurrentTokenAndMoveNext()
Token current = this._GetCurrentToken();
private Token _MatchToken(TokenType expected_type)
if(this._GetCurrentToken().Type == expected_type)
return this._GetCurrentTokenAndMoveNext();
return new Token(expected_type, this._GetCurrentToken().StartPosition, this._GetCurrentToken().EndPosition, null, null);
private Expression _ParsePrimaryExpression()
var integer_token = _MatchToken(TokenType.IntegerType);
return new LiteralExpression(integer_token);
internal Expression Parse()
Expression left = _ParsePrimaryExpression();
while(this._GetCurrentToken().Type == TokenType.AdditionOperator || this._GetCurrentToken().Type == TokenType.SubtractionOperator)
Token operator_token = this._GetCurrentTokenAndMoveNext();
Expression right = _ParsePrimaryExpression();
left = new BinaryExpression(left, operator_token, right);