From 6dd68031b5d484e4916938df4a1aa27b9414cc07 Mon Sep 17 00:00:00 2001 From: fajiao <1519100073@qq.com> Date: Mon, 5 Dec 2022 14:52:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20RabbitFunc=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RabbitFunc/Compiler/CompilerException.cs | 9 + EC.Helper/RabbitFunc/Compiler/IndexSpan.cs | 57 +++ EC.Helper/RabbitFunc/Compiler/Parser.cs | 258 +++++++++++ .../RabbitFunc/Compiler/SourceLocation.cs | 97 +++++ .../RabbitFunc/Compiler/TokenWithSpan.cs | 20 + EC.Helper/RabbitFunc/Compiler/Tokenizer.cs | 403 ++++++++++++++++++ .../Expressions/BinaryExpression.cs | 119 ++++++ .../Expressions/ConditionalExpression.cs | 32 ++ .../Expressions/ConstantExpression.cs | 29 ++ .../RabbitFunc/Expressions/Expression.cs | 238 +++++++++++ .../Expressions/ExpressionCompiler.cs | 146 +++++++ .../RabbitFunc/Expressions/ExpressionType.cs | 65 +++ .../Expressions/LambdaCompilerException.cs | 9 + .../Expressions/LambdaExpression.cs | 75 ++++ .../Expressions/MemberExpression.cs | 30 ++ .../Expressions/MethodCallExpression.cs | 88 ++++ .../Expressions/ParameterExpression.cs | 45 ++ .../Expressions/SystemLambdaExpression.cs | 19 + .../RabbitFunc/Expressions/UnaryExpression.cs | 48 +++ .../RabbitFunc/Extern/AbsLambdaExpression.cs | 21 + .../RabbitFunc/Extern/AcosLambdaExpression.cs | 21 + .../RabbitFunc/Extern/AsinLambdaExpression.cs | 21 + .../RabbitFunc/Extern/AtanLambdaExpression.cs | 21 + .../Extern/CeilingLambdaExpression.cs | 21 + .../RabbitFunc/Extern/CosLambdaExpression.cs | 21 + .../RabbitFunc/Extern/CoshLambdaExpression.cs | 21 + .../RabbitFunc/Extern/ExpLambdaExpression.cs | 21 + .../Extern/ExternLambdaAttribute.cs | 5 + .../Extern/FloorLambdaExpression.cs | 21 + .../RabbitFunc/Extern/LogLambdaExpression.cs | 24 ++ .../RabbitFunc/Extern/MaxLambdaExpression.cs | 24 ++ .../RabbitFunc/Extern/MinLambdaExpression.cs | 24 ++ .../Extern/RoundLambdaExpression.cs | 24 ++ .../RabbitFunc/Extern/SinLambdaExpression.cs | 21 + .../RabbitFunc/Extern/SinhLambdaExpression.cs | 21 + .../RabbitFunc/Extern/SqrtLambdaExpression.cs | 21 + .../RabbitFunc/Extern/TanLambdaExpression.cs | 21 + .../RabbitFunc/Extern/TanhLambdaExpression.cs | 21 + EC.Helper/RabbitFunc/Rabbit.cs | 84 ++++ EC.Helper/RabbitFunc/RabbitDomain.cs | 83 ++++ EC.Helper/RabbitFunc/RabbitFuncUtil.cs | 45 ++ .../Runtime/MemberAccessException.cs | 23 + .../Runtime/MissingMemberException.cs | 23 + .../Runtime/MissingMethodException.cs | 23 + .../RabbitFunc/Runtime/RuntimeContext.cs | 91 ++++ .../RabbitFunc/Runtime/RuntimeException.cs | 23 + .../RabbitFunc/Runtime/SystemException.cs | 23 + EC.Helper/RabbitFunc/Syntax/CommentToken.cs | 14 + EC.Helper/RabbitFunc/Syntax/ConstantToken.cs | 16 + EC.Helper/RabbitFunc/Syntax/ErrorToken.cs | 16 + .../RabbitFunc/Syntax/IdentifierToken.cs | 14 + EC.Helper/RabbitFunc/Syntax/OperatorToken.cs | 18 + EC.Helper/RabbitFunc/Syntax/SymbolToken.cs | 14 + EC.Helper/RabbitFunc/Syntax/Token.cs | 55 +++ EC.Helper/RabbitFunc/Syntax/TokenKind.cs | 113 +++++ EC.Helper/RabbitFunc/Syntax/Tokens.cs | 32 ++ 56 files changed, 2892 insertions(+) create mode 100644 EC.Helper/RabbitFunc/Compiler/CompilerException.cs create mode 100644 EC.Helper/RabbitFunc/Compiler/IndexSpan.cs create mode 100644 EC.Helper/RabbitFunc/Compiler/Parser.cs create mode 100644 EC.Helper/RabbitFunc/Compiler/SourceLocation.cs create mode 100644 EC.Helper/RabbitFunc/Compiler/TokenWithSpan.cs create mode 100644 EC.Helper/RabbitFunc/Compiler/Tokenizer.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/BinaryExpression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/ConditionalExpression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/ConstantExpression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/Expression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/ExpressionCompiler.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/ExpressionType.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/LambdaCompilerException.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/LambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/MemberExpression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/MethodCallExpression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/ParameterExpression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/SystemLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Expressions/UnaryExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/AbsLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/AcosLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/AsinLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/AtanLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/CeilingLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/CosLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/CoshLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/ExpLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/ExternLambdaAttribute.cs create mode 100644 EC.Helper/RabbitFunc/Extern/FloorLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/LogLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/MaxLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/MinLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/RoundLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/SinLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/SinhLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/SqrtLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/TanLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Extern/TanhLambdaExpression.cs create mode 100644 EC.Helper/RabbitFunc/Rabbit.cs create mode 100644 EC.Helper/RabbitFunc/RabbitDomain.cs create mode 100644 EC.Helper/RabbitFunc/RabbitFuncUtil.cs create mode 100644 EC.Helper/RabbitFunc/Runtime/MemberAccessException.cs create mode 100644 EC.Helper/RabbitFunc/Runtime/MissingMemberException.cs create mode 100644 EC.Helper/RabbitFunc/Runtime/MissingMethodException.cs create mode 100644 EC.Helper/RabbitFunc/Runtime/RuntimeContext.cs create mode 100644 EC.Helper/RabbitFunc/Runtime/RuntimeException.cs create mode 100644 EC.Helper/RabbitFunc/Runtime/SystemException.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/CommentToken.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/ConstantToken.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/ErrorToken.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/IdentifierToken.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/OperatorToken.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/SymbolToken.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/Token.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/TokenKind.cs create mode 100644 EC.Helper/RabbitFunc/Syntax/Tokens.cs diff --git a/EC.Helper/RabbitFunc/Compiler/CompilerException.cs b/EC.Helper/RabbitFunc/Compiler/CompilerException.cs new file mode 100644 index 0000000..4212528 --- /dev/null +++ b/EC.Helper/RabbitFunc/Compiler/CompilerException.cs @@ -0,0 +1,9 @@ +namespace EC.Helper.RabbitFunc.Compiler; + +[Serializable] +public class CompilerException : Exception +{ + public CompilerException(SourceLocation location, string message) + : base(string.Format("line:{0} column:{1}: {2}", location.Line, location.Column, message)) + { } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Compiler/IndexSpan.cs b/EC.Helper/RabbitFunc/Compiler/IndexSpan.cs new file mode 100644 index 0000000..bfa7f14 --- /dev/null +++ b/EC.Helper/RabbitFunc/Compiler/IndexSpan.cs @@ -0,0 +1,57 @@ +namespace EC.Helper.RabbitFunc.Compiler; + +[Serializable] +public readonly struct IndexSpan : IEquatable +{ + private readonly int start; + private readonly int length; + + public IndexSpan(int start, int length) + { + if (start < 0) + throw new ArgumentOutOfRangeException(nameof(start)); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length)); + + this.start = start; + this.length = length; + } + + public int Start => start; + + public int End => start + length; + + public int Length => length; + + public bool IsEmpty => length == 0; + + public static bool operator ==(IndexSpan left, IndexSpan right) + { + return left.start == right.start && left.length == right.length; + } + + public static bool operator !=(IndexSpan left, IndexSpan right) + { + return left.start != right.start || left.length != right.length; + } + + public bool Equals(IndexSpan other) + { + return other.start == start && other.length == length; + } + + public override bool Equals(object obj) + { + return obj is IndexSpan other && Equals(other); + } + + public override int GetHashCode() + { + return (start.GetHashCode() << 16) ^ length.GetHashCode(); + } + + public override string ToString() + { + return string.Format("{{start:{0} length:{1}}}", start, length); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Compiler/Parser.cs b/EC.Helper/RabbitFunc/Compiler/Parser.cs new file mode 100644 index 0000000..ec91d1a --- /dev/null +++ b/EC.Helper/RabbitFunc/Compiler/Parser.cs @@ -0,0 +1,258 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Syntax; + +namespace EC.Helper.RabbitFunc.Compiler; + +internal class Parser +{ + private readonly Tokenizer tokenizer; + private TokenWithSpan lookahead; + private TokenWithSpan token; + + public Parser(Tokenizer tokenizer) + { + if (tokenizer == null) + throw new ArgumentNullException(nameof(tokenizer)); + + this.tokenizer = tokenizer; + Initialize(); + } + + private void Initialize() + { + FetchLookahead(); + } + + private Token NextToken() + { + token = lookahead; + FetchLookahead(); + return token.Token; + } + + private Token PeekToken() + { + return this.lookahead.Token; + } + + private void FetchLookahead() + { + lookahead = new TokenWithSpan(tokenizer.NextToken(), tokenizer.TokenSpan); + } + + private bool PeekToken(TokenKind kind) + { + return PeekToken().Kind == kind; + } + + private bool PeekToken(Token check) + { + return PeekToken() == check; + } + + private bool MaybeEat(TokenKind kind) + { + if (PeekToken().Kind == kind) + { + NextToken(); + return true; + } + return false; + } + + private LambdaExpression ParseStatement() + { + while (true) + { + switch (PeekToken().Kind) + { + case TokenKind.Comment: + case TokenKind.NewLine: + NextToken(); + continue; + case TokenKind.EndOfFile: + return null; + + default: + return ParseLambdaExpression(); + } + } + } + + private ExpressionType GetBinaryOperator(TokenKind kind) + { + return kind switch + { + TokenKind.Add => ExpressionType.Add, + TokenKind.Subtract => ExpressionType.Subtract, + TokenKind.Multiply => ExpressionType.Multiply, + TokenKind.Divide => ExpressionType.Divide, + TokenKind.Mod => ExpressionType.Modulo, + TokenKind.Power => ExpressionType.Power, + TokenKind.LessThan => ExpressionType.LessThan, + TokenKind.LessThanOrEqual => ExpressionType.LessThanOrEqual, + TokenKind.Equal => ExpressionType.Equal, + TokenKind.GreaterThanOrEqual => ExpressionType.GreaterThanOrEqual, + TokenKind.GreaterThan => ExpressionType.GreaterThan, + TokenKind.NotEqual => ExpressionType.NotEqual, + _ => throw new CompilerException(tokenizer.Position, string.Format("operator TokenKind:{0} error!", kind.ToString())), + }; + } + + private IList ParseArguments() + { + List list = new List(); + if (PeekToken().Kind != TokenKind.RightParen) + { + do + { + list.Add(ParseExpression()); + } while (MaybeEat(TokenKind.Comma)); + } + + return list; + } + + private Expression ParseTerm() + { + Expression expr = null; + Token token = NextToken(); + switch (token.Kind) + { + case TokenKind.Constant: + expr = Expression.Constant(token.Value); + break; + + case TokenKind.Subtract: + expr = Expression.Negate(ParseExpression()); + break; + + case TokenKind.LeftParen: + expr = ParseExpression(); + if (!MaybeEat(TokenKind.RightParen)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + break; + + case TokenKind.Identifier: + switch (PeekToken().Kind) + { + case TokenKind.LeftParen: + NextToken(); + expr = Expression.Call(null, token.Text, ParseArguments()); + if (!MaybeEat(TokenKind.RightParen)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + + break; + + case TokenKind.Dot: + expr = Expression.Parameter(null, token.Text); + while (MaybeEat(TokenKind.Dot)) + { + token = NextToken(); + if (token.Kind != TokenKind.Identifier) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + if (PeekToken().Kind == TokenKind.LeftParen) + { + expr = Expression.Call(expr, token.Text, ParseArguments()); + if (!MaybeEat(TokenKind.RightParen)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + break; + } + + expr = Expression.Member(expr, token.Text); + } + if (MaybeEat(TokenKind.LeftParen)) + { + expr = Expression.Call(expr, token.Text, ParseArguments()); + if (!MaybeEat(TokenKind.RightParen)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + } + break; + + default: + expr = Expression.Parameter(typeof(double), token.Text); + break; + } + break; + + case TokenKind.Not: + return Expression.Not(ParseExpression()); + + case TokenKind.IF: + if (!MaybeEat(TokenKind.LeftParen)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + var test = ParseExpression(); + if (!MaybeEat(TokenKind.Comma)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + var trueExpre = ParseExpression(); + if (!MaybeEat(TokenKind.Comma)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + var falseExpre = ParseExpression(); + if (!MaybeEat(TokenKind.RightParen)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + + expr = Expression.Condition(test, trueExpre, falseExpre); + break; + } + + return expr; + } + + private Expression ParseExpression(byte precedence = 0) + { + Expression leftOperand = ParseTerm(); + while (true) + { + Token token = PeekToken(); + if (token is OperatorToken operatorToken && operatorToken.Precedence >= precedence) + { + NextToken(); + Expression rightOperand = ParseExpression(checked((byte)(operatorToken.Precedence + 1))); + ExpressionType @operator = GetBinaryOperator(token.Kind); + leftOperand = new BinaryExpression(@operator, leftOperand, rightOperand); + continue; + } + + break; + } + + return leftOperand; + } + + private LambdaExpression ParseLambdaExpression() + { + Token token = NextToken(); + if (token.Kind != TokenKind.Identifier) + throw new CompilerException(tokenizer.Position, token.Text); + + var name = token.Text; + var parameters = new List(); + if (MaybeEat(TokenKind.LeftParen)) + { + if (!MaybeEat(TokenKind.RightParen)) + { + do + { + token = NextToken(); + if (token.Kind != TokenKind.Identifier) + throw new CompilerException(tokenizer.Position, token.Text); + + parameters.Add(Expression.Parameter(typeof(double), token.Text)); + } while (MaybeEat(TokenKind.Comma)); + + if (!MaybeEat(TokenKind.RightParen)) + throw new CompilerException(tokenizer.Position, token.Text); + } + } + if (!MaybeEat(TokenKind.Assign)) + throw new CompilerException(tokenizer.Position, PeekToken().Text); + + Expression body = ParseExpression(); + return Expression.Lambda(name, body, parameters); + } + + public LambdaExpression Parse() + { + return ParseStatement(); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Compiler/SourceLocation.cs b/EC.Helper/RabbitFunc/Compiler/SourceLocation.cs new file mode 100644 index 0000000..146a728 --- /dev/null +++ b/EC.Helper/RabbitFunc/Compiler/SourceLocation.cs @@ -0,0 +1,97 @@ +namespace EC.Helper.RabbitFunc.Compiler; + +[Serializable] +public readonly struct SourceLocation : IEquatable +{ + public static readonly SourceLocation None = new SourceLocation(0, 16707566, 0, true); + public static readonly SourceLocation Invalid = new SourceLocation(0, 0, 0, true); + public static readonly SourceLocation MinValue = new SourceLocation(0, 1, 1); + + private readonly int index; + private readonly int line; + private readonly int column; + + public SourceLocation(int index, int line, int column) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + if (line < 1) + throw new ArgumentOutOfRangeException(nameof(line)); + if (column < 1) + throw new ArgumentOutOfRangeException(nameof(column)); + + this.index = index; + this.line = line; + this.column = column; + } + + private SourceLocation(int index, int line, int column, bool _) + { + this.index = index; + this.line = line; + this.column = column; + } + + public int Index => index; + + public int Line => line; + + public int Column => column; + + public bool IsValid => line != 0 && column != 0; + + public static bool operator ==(SourceLocation left, SourceLocation right) + { + return left.index == right.index && left.line == right.line && left.column == right.column; + } + + public static bool operator !=(SourceLocation left, SourceLocation right) + { + return left.index != right.index || left.line != right.line || left.column != right.column; + } + + public static bool operator <(SourceLocation left, SourceLocation right) + { + return left.index < right.index; + } + + public static bool operator >(SourceLocation left, SourceLocation right) + { + return left.index > right.index; + } + + public static bool operator <=(SourceLocation left, SourceLocation right) + { + return left.index <= right.index; + } + + public static bool operator >=(SourceLocation left, SourceLocation right) + { + return left.index >= right.index; + } + + public static int Compare(SourceLocation left, SourceLocation right) + { + return left > right ? 1 : left < right ? -1 : 0; + } + + public bool Equals(SourceLocation other) + { + return other.index == index && other.line == line && other.column == column; + } + + public override bool Equals(object obj) + { + return obj is SourceLocation other && Equals(other); + } + + public override int GetHashCode() + { + return (line << 16) ^ column; + } + + public override string ToString() + { + return string.Format("{{index:{0} line:{1} column:{2}}}", index, line, column); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Compiler/TokenWithSpan.cs b/EC.Helper/RabbitFunc/Compiler/TokenWithSpan.cs new file mode 100644 index 0000000..82591be --- /dev/null +++ b/EC.Helper/RabbitFunc/Compiler/TokenWithSpan.cs @@ -0,0 +1,20 @@ +using EC.Helper.RabbitFunc.Syntax; + +namespace EC.Helper.RabbitFunc.Compiler; + +[Serializable] +internal readonly struct TokenWithSpan +{ + private readonly Token token; + private readonly IndexSpan span; + + public TokenWithSpan(Token token, IndexSpan span) + { + this.token = token; + this.span = span; + } + + public IndexSpan Span => span; + + public Token Token => token; +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Compiler/Tokenizer.cs b/EC.Helper/RabbitFunc/Compiler/Tokenizer.cs new file mode 100644 index 0000000..f5b116d --- /dev/null +++ b/EC.Helper/RabbitFunc/Compiler/Tokenizer.cs @@ -0,0 +1,403 @@ +using EC.Helper.RabbitFunc.Syntax; + +namespace EC.Helper.RabbitFunc.Compiler; + +internal class Tokenizer +{ + #region Attr + + private const int DefaultBufferCapacity = 1024; + + private readonly TextReader reader; + private char[] buffer; + private int start; + private int end; + private int position; + private bool endOfStream; + private bool multiEolns; + private int tokenEnd; + private int tokenStartIndex; + private int tokenEndIndex; + private List newLineLocations; + private SourceLocation initialLocation; + + public bool EndOfStream => endOfStream; + + public int Index => tokenStartIndex + Math.Min(position, end) - start; + + public SourceLocation Position => IndexToLocation(Index); + + public IndexSpan TokenSpan => new(tokenStartIndex, tokenEndIndex - tokenStartIndex); + + private int TokenLength => tokenEnd - start; + + #endregion Attr + + public Tokenizer(TextReader reader) + { + this.reader = reader; + Init(DefaultBufferCapacity); + } + + private void Init(int bufferCapacity) + { + multiEolns = true; + buffer = new char[bufferCapacity]; + newLineLocations = new List(); + initialLocation = SourceLocation.MinValue; + } + + private static void ResizeInternal(ref char[] array, int newSize, int start, int count) + { + char[] array2 = (newSize != array.Length) ? new char[newSize] : array; + Buffer.BlockCopy(array, start * 2, array2, 0, count * 2); + array = array2; + } + + private static bool IsNameStart(int ch) + { + return char.IsLetter((char)ch) || ch == 95; + } + + private static bool IsNamePart(int ch) + { + return char.IsLetterOrDigit((char)ch) || ch == 95; + } + + private static Token BadChar(int ch) + { + return Token.Error(((char)ch).ToString()); + } + + private void RefillBuffer() + { + if (end == buffer.Length) + { + int newSize = Math.Max(Math.Max((end - start) * 2, buffer.Length), position); + ResizeInternal(ref buffer, newSize, start, end - start); + end -= start; + position -= start; + start = 0; + //_bufferResized = true; + } + + end += reader.Read(buffer, end, buffer.Length - end); + } + + private int Peek() + { + if (position >= end) + { + RefillBuffer(); + if (position >= end) + { + endOfStream = true; + return -1; + } + } + + return buffer[position]; + } + + private bool NextChar(int ch) + { + if (Peek() == ch) + { + position++; + return true; + } + return false; + } + + private int NextChar() + { + int result = Peek(); + position++; + return result; + } + + private int ReadLine() + { + int num; + do + { + num = NextChar(); + } while (num != -1 && !IsEoln(num)); + if (num == 10) + { + newLineLocations.Add(Index); + } + BufferBack(); + return num; + } + + private bool IsEoln(int current) + { + return current == 10 || (current == 13 && multiEolns); + } + + private void BufferBack() + { + SeekRelative(-1); + } + + private void SeekRelative(int disp) + { + position += disp; + } + + private void MarkTokenEnd() + { + tokenEnd = Math.Min(position, end); + int num = tokenEnd - start; + tokenEndIndex = tokenStartIndex + num; + } + + private void DiscardToken() + { + if (tokenEnd == -1) + { + MarkTokenEnd(); + } + + start = tokenEnd; + tokenStartIndex = tokenEndIndex; + tokenEnd = -1; + } + + private string GetTokenString() + { + return new string(buffer, start, tokenEnd - start); + } + + private string GetTokenSubstring(int offset) + { + return GetTokenSubstring(offset, tokenEnd - start - offset); + } + + private string GetTokenSubstring(int offset, int length) + { + return new string(buffer, start + offset, length); + } + + private int SkipWhiteSpace() + { + int num; + do + { + num = NextChar(); + } while (num == 32 || num == 9); + + BufferBack(); + DiscardToken(); + SeekRelative(1); + return num; + } + + private SourceLocation IndexToLocation(int index) + { + int num = newLineLocations.BinarySearch(index); + if (num < 0) + { + if (num == -1) + { + int column = checked(index + initialLocation.Column); + if (TokenLength > 0) column -= TokenLength; + return new SourceLocation(index + initialLocation.Index, initialLocation.Line, column); + } + num = ~num - 1; + } + + int fcolumn = index - newLineLocations[num] + initialLocation.Column; + if (TokenLength > 0) fcolumn -= TokenLength; + return new SourceLocation(index + initialLocation.Index, num + 1 + initialLocation.Line, fcolumn); + } + + private Token ReadName() + { + BufferBack(); + int num = NextChar(); + if (!IsNameStart(num)) + return BadChar(num); + + while (IsNamePart(num)) + { + num = this.NextChar(); + } + + BufferBack(); + MarkTokenEnd(); + return Token.Identifier(GetTokenString()); + } + + private Token ReadNumber() + { + int num; + do + { + num = NextChar(); + } while (48 <= num && num <= 57); + if (num == 46) + { + do + { + num = NextChar(); + } while (48 <= num && num <= 57); + } + + BufferBack(); + MarkTokenEnd(); + return Token.Constant(double.Parse(GetTokenString())); + } + + private Token ReadComment() + { + BufferBack(); + int num = NextChar(); + if (num == 42) + { + do + { + num = NextChar(); + if (num == 10) + { + newLineLocations.Add(Index); + } + } while (!(num == 42 && NextChar(47))); + + SeekRelative(-2); + MarkTokenEnd(); + SeekRelative(2); + return Token.Comment(GetTokenSubstring(2)); + } + else + { + ReadLine(); + MarkTokenEnd(); + return Token.Comment(GetTokenSubstring(2)); + } + } + + public Token NextToken() + { + DiscardToken(); + int num = NextChar(); + while (true) + { + switch (num) + { + case -1: + return Tokens.EndOfFileToken; + + case 10://'\n' + newLineLocations.Add(Index); + num = SkipWhiteSpace(); + continue; + case 13://'\r' + case 32://' ' + num = SkipWhiteSpace(); + continue; + case 33://! + var notToken = NextChar(61) ? Tokens.NotEqualToken : Tokens.NotToken; + MarkTokenEnd(); + return notToken; + + case 37://'%' + MarkTokenEnd(); + return Tokens.ModToken; + + case 40://'(' + MarkTokenEnd(); + return Tokens.LeftParenToken; + + case 41://')' + MarkTokenEnd(); + return Tokens.RightParenToken; + + case 42://'*' + MarkTokenEnd(); + return Tokens.MultiplyToken; + + case 43://'+' + MarkTokenEnd(); + return Tokens.AddToken; + + case 44://',' + MarkTokenEnd(); + return Tokens.CommaToken; + + case 45://'-' + MarkTokenEnd(); + return Tokens.SubtractToken; + + case 46://'.' + MarkTokenEnd(); + return Tokens.DotToken; + + case 47://'/' + if (NextChar(42) || NextChar(47)) + { + return ReadComment(); + } + + MarkTokenEnd(); + return Tokens.DivideToken; + + case 48://'0' + case 49: + case 50: + case 51: + case 52: + case 53: + case 54: + case 55: + case 56: + case 57://'9' + return ReadNumber(); + + case 59://';' + MarkTokenEnd(); + return Tokens.NewLineToken; + + case 60://< + var lessToken = NextChar(61) ? Tokens.LessThanOrEqualToken : Tokens.LessThanToken; + MarkTokenEnd(); + return lessToken; + + case 61://'=' + var assignOrEqualToken = NextChar(61) ? Tokens.EqualToken : Tokens.AssignToken; + MarkTokenEnd(); + return assignOrEqualToken; + + case 62://> + var greaterToken = NextChar(61) ? Tokens.GreaterThanOrEqualToken : Tokens.GreaterThanToken; + MarkTokenEnd(); + return greaterToken; + + case 91://'[' + MarkTokenEnd(); + return Tokens.LeftBracketToken; + + case 93://']' + MarkTokenEnd(); + return Tokens.RightBracketToken; + + case 94://'^' + MarkTokenEnd(); + return Tokens.PowerToken; + + case 105://i + if (NextChar(102)) + { + MarkTokenEnd(); + return Tokens.IFToken; + } + + return ReadName(); + + default: + return ReadName(); + } + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/BinaryExpression.cs b/EC.Helper/RabbitFunc/Expressions/BinaryExpression.cs new file mode 100644 index 0000000..bcc1a1c --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/BinaryExpression.cs @@ -0,0 +1,119 @@ +using EC.Helper.RabbitFunc.Runtime; +using System.Text; + +namespace EC.Helper.RabbitFunc.Expressions; + +public class BinaryExpression : Expression +{ + internal BinaryExpression(ExpressionType nodeType, Expression left, Expression right) + : base(nodeType) + { + Left = left; + Right = right; + } + + public Expression Left { get; } + + public Expression Right { get; } + + public override object Eval(RuntimeContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + var leftResult = Left.Eval(context); + var rightResult = Right.Eval(context); + return NodeType switch + { + ExpressionType.Add => System.Convert.ToDouble(leftResult) + System.Convert.ToDouble(rightResult), + ExpressionType.Subtract => System.Convert.ToDouble(leftResult) - System.Convert.ToDouble(rightResult), + ExpressionType.Multiply => System.Convert.ToDouble(leftResult) * System.Convert.ToDouble(rightResult), + ExpressionType.Divide => System.Convert.ToDouble(leftResult) / System.Convert.ToDouble(rightResult), + ExpressionType.Modulo => System.Convert.ToDouble(leftResult) % System.Convert.ToDouble(rightResult), + ExpressionType.Power => Math.Pow(System.Convert.ToDouble(leftResult), System.Convert.ToDouble(rightResult)), + ExpressionType.LessThan => System.Convert.ToDouble(leftResult) < System.Convert.ToDouble(rightResult), + ExpressionType.LessThanOrEqual => System.Convert.ToDouble(leftResult) <= System.Convert.ToDouble(rightResult), + ExpressionType.Equal => leftResult is bool ? System.Convert.ToBoolean(leftResult) == System.Convert.ToBoolean(rightResult) : System.Convert.ToDouble(leftResult) == System.Convert.ToDouble(rightResult), + ExpressionType.GreaterThanOrEqual => System.Convert.ToDouble(leftResult) >= System.Convert.ToDouble(rightResult), + ExpressionType.GreaterThan => System.Convert.ToDouble(leftResult) > System.Convert.ToDouble(rightResult), + ExpressionType.NotEqual => leftResult is bool ? System.Convert.ToBoolean(leftResult) != System.Convert.ToBoolean(rightResult) : System.Convert.ToDouble(leftResult) != System.Convert.ToDouble(rightResult), + _ => throw new RuntimeException("unknown operator:" + NodeType.ToString()), + }; + } + + public override string ToString() + { + var sb = new StringBuilder(); + if (Left is BinaryExpression) + { + sb.Append('('); + sb.Append(Left.ToString()); + sb.Append(')'); + } + else + { + sb.Append(Left.ToString()); + } + switch (NodeType) + { + case ExpressionType.Add: + sb.Append('+'); + break; + + case ExpressionType.Subtract: + sb.Append('-'); + break; + + case ExpressionType.Multiply: + sb.Append('*'); + break; + + case ExpressionType.Divide: + sb.Append('/'); + break; + + case ExpressionType.Modulo: + sb.Append('%'); + break; + + case ExpressionType.Power: + sb.Append('^'); + break; + + case ExpressionType.LessThan: + sb.Append('<'); + break; + + case ExpressionType.LessThanOrEqual: + sb.Append("<="); + break; + + case ExpressionType.Equal: + sb.Append("=="); + break; + + case ExpressionType.GreaterThanOrEqual: + sb.Append(">="); + break; + + case ExpressionType.GreaterThan: + sb.Append('>'); + break; + + case ExpressionType.NotEqual: + sb.Append("!="); + break; + + default: + throw new RuntimeException("unknown operator:" + NodeType.ToString()); + } + if (Right is BinaryExpression) + sb.Append('('); + + sb.Append(Right.ToString()); + if (Right is BinaryExpression) + sb.Append(')'); + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/ConditionalExpression.cs b/EC.Helper/RabbitFunc/Expressions/ConditionalExpression.cs new file mode 100644 index 0000000..106208f --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/ConditionalExpression.cs @@ -0,0 +1,32 @@ +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Expressions; + +public class ConditionalExpression : Expression +{ + internal ConditionalExpression(Expression test, Expression trueExpression, Expression falseExpression) + : base(ExpressionType.MethodCall) + { + Test = test; + TrueExpression = trueExpression; + FalseExpression = falseExpression; + } + + public override ExpressionType NodeType => ExpressionType.Conditional; + + public Expression Test { get; } + + public Expression TrueExpression { get; } + + public Expression FalseExpression { get; } + + public override object Eval(RuntimeContext context) + { + return (bool)Test.Eval(context) ? TrueExpression.Eval(context) : FalseExpression.Eval(context); + } + + public override string ToString() + { + return string.Format("if({0},{1},{2})", Test, TrueExpression, FalseExpression); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/ConstantExpression.cs b/EC.Helper/RabbitFunc/Expressions/ConstantExpression.cs new file mode 100644 index 0000000..8222c17 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/ConstantExpression.cs @@ -0,0 +1,29 @@ +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Expressions; + +public class ConstantExpression : Expression +{ + internal ConstantExpression(object value) + : base(ExpressionType.Constant) + { + Value = value; + } + + public object Value { get; } + + public override Type Type => Value?.GetType(); + + public override object Eval(RuntimeContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return Value; + } + + public override string ToString() + { + return Value.ToString(); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/Expression.cs b/EC.Helper/RabbitFunc/Expressions/Expression.cs new file mode 100644 index 0000000..6549315 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/Expression.cs @@ -0,0 +1,238 @@ +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Expressions; + +public abstract class Expression +{ + protected Expression() + { } + + protected Expression(ExpressionType nodeType) + { + NodeType = nodeType; + } + + public virtual ExpressionType NodeType { get; } + + public virtual Type Type { get; } + + public static ParameterExpression Parameter(Type type, string name) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + return new ParameterExpression(ExpressionType.Parameter, type, name); + } + + public static ParameterExpression Variable(Type type, string name) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + return new ParameterExpression(ExpressionType.Variable, type, name); + } + + public static MemberExpression Member(Expression instance, string name) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + if (name == null) + throw new ArgumentNullException(nameof(name)); + + return new MemberExpression(instance, name); + } + + public static MemberExpression Field(Expression instance, string name) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + if (name == null) + throw new ArgumentNullException(nameof(name)); + + return new MemberExpression(instance, name); + } + + public static MemberExpression Property(Expression instance, string name) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + if (name == null) + throw new ArgumentNullException(nameof(name)); + + return new MemberExpression(instance, name); + } + + public static BinaryExpression Assign(Expression left, Expression right) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + + return new BinaryExpression(ExpressionType.Assign, left, right); + } + + public static ConstantExpression Constant(object value) + { + return new ConstantExpression(value); + } + + public static UnaryExpression Convert(Expression expression, Type type) + { + if (expression == null) + throw new ArgumentNullException(nameof(expression)); + if (type == null) + throw new ArgumentNullException(nameof(type)); + + return new UnaryExpression(ExpressionType.Constant, expression); + } + + public static BinaryExpression Add(Expression left, Expression right) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + + return new BinaryExpression(ExpressionType.Add, left, right); + } + + public static BinaryExpression Subtract(Expression left, Expression right) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + + return new BinaryExpression(ExpressionType.Subtract, left, right); + } + + public static BinaryExpression Multiply(Expression left, Expression right) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + + return new BinaryExpression(ExpressionType.Multiply, left, right); + } + + public static BinaryExpression Divide(Expression left, Expression right) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + + return new BinaryExpression(ExpressionType.Divide, left, right); + } + + public static BinaryExpression Modulo(Expression left, Expression right) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + + return new BinaryExpression(ExpressionType.Modulo, left, right); + } + + public static BinaryExpression Power(Expression left, Expression right) + { + if (left == null) + throw new ArgumentNullException(nameof(left)); + if (right == null) + throw new ArgumentNullException(nameof(right)); + + return new BinaryExpression(ExpressionType.Power, left, right); + } + + public static UnaryExpression Negate(Expression expression) + { + if (expression == null) + throw new ArgumentNullException(nameof(expression)); + + return new UnaryExpression(ExpressionType.Negate, expression); + } + + public static BinaryExpression ArrayIndex(Expression array, Expression index) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (index == null) + throw new ArgumentNullException(nameof(index)); + + return new BinaryExpression(ExpressionType.ArrayIndex, array, index); + } + + public static UnaryExpression ArrayLength(Expression array) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + return new UnaryExpression(ExpressionType.ArrayLength, array); + } + + public static MethodCallExpression Call(Expression instance, string methodName, params Expression[] arguments) + { + if (methodName == null) + throw new ArgumentNullException(nameof(methodName)); + + return new MethodCallExpression(instance, methodName, arguments); + } + + public static MethodCallExpression Call(Expression instance, string methodName, IList arguments) + { + if (methodName == null) + throw new ArgumentNullException(nameof(methodName)); + if (arguments == null) + throw new ArgumentNullException(nameof(arguments)); + + return new MethodCallExpression(instance, methodName, arguments); + } + + public static LambdaExpression Lambda(string name, Expression body, params ParameterExpression[] parameters) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (body == null) + throw new ArgumentNullException(nameof(body)); + + return new LambdaExpression(name, body, parameters); + } + + public static LambdaExpression Lambda(string name, Expression body, IList parameters) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (body == null) + throw new ArgumentNullException(nameof(body)); + + return new LambdaExpression(name, body, parameters.ToArray()); + } + + public static UnaryExpression Not(Expression expression) + { + if (expression == null) + throw new ArgumentNullException(nameof(expression)); + + return new UnaryExpression(ExpressionType.Not, expression); + } + + public static ConditionalExpression Condition(Expression condition, Expression trueExpression, Expression falseExpression) + { + if (condition == null) + throw new ArgumentNullException(nameof(condition)); + if (trueExpression == null) + throw new ArgumentNullException(nameof(trueExpression)); + if (falseExpression == null) + throw new ArgumentNullException(nameof(falseExpression)); + + return new ConditionalExpression(condition, trueExpression, falseExpression); + } + + public virtual object Eval(RuntimeContext context) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/ExpressionCompiler.cs b/EC.Helper/RabbitFunc/Expressions/ExpressionCompiler.cs new file mode 100644 index 0000000..92f8063 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/ExpressionCompiler.cs @@ -0,0 +1,146 @@ +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Expressions; + +internal class ExpressionCompiler +{ + private readonly Dictionary lambdas = new Dictionary(); + + private LambdaCompiler GetLambdaCompiler(LambdaExpression expression) + { + if (!lambdas.TryGetValue(expression.Name, out var lambda)) + { + lambda = new LambdaCompiler(this, expression); + lambdas.Add(expression.Name, lambda); + } + return lambda; + } + + public Delegate Compile(LambdaExpression expression, Type delegateType = null) + { + var lambda = new LambdaCompiler(this, expression); + lambdas.Add(lambda.Name, lambda); + return lambda.Compile(delegateType); + } + + private sealed class LambdaCompiler + { + private readonly ExpressionCompiler compiler; + private readonly LambdaExpression expression; + private readonly List parameters; + + public LambdaCompiler(ExpressionCompiler compiler, LambdaExpression expression) + { + this.compiler = compiler; + this.expression = expression; + this.parameters = new List(); + foreach (var parameter in expression.Parameters) + { + parameters.Add(System.Linq.Expressions.Expression.Parameter(parameter.Type, parameter.Name)); + } + } + + public string Name => expression.Name; + + private System.Linq.Expressions.Expression EmitExpression(Expression expression) + { + switch (expression) + { + case ConditionalExpression conditionExpression: + { + var test = EmitExpression(conditionExpression.Test); + var trueExpression = EmitExpression(conditionExpression.TrueExpression); + var falseExpression = EmitExpression(conditionExpression.FalseExpression); + return System.Linq.Expressions.Expression.Condition(test, trueExpression, falseExpression); + } + case BinaryExpression binaryExpression: + { + var left = EmitExpression(binaryExpression.Left); + var right = EmitExpression(binaryExpression.Right); + return expression.NodeType switch + { + ExpressionType.Add => System.Linq.Expressions.Expression.Add(left, right), + ExpressionType.Subtract => System.Linq.Expressions.Expression.Subtract(left, right), + ExpressionType.Multiply => System.Linq.Expressions.Expression.Multiply(left, right), + ExpressionType.Divide => System.Linq.Expressions.Expression.Divide(left, right), + ExpressionType.Modulo => System.Linq.Expressions.Expression.Modulo(left, right), + ExpressionType.Power => System.Linq.Expressions.Expression.Power(left, right), + ExpressionType.LessThan => System.Linq.Expressions.Expression.LessThan(left, right), + ExpressionType.LessThanOrEqual => System.Linq.Expressions.Expression.LessThanOrEqual(left, right), + ExpressionType.Equal => System.Linq.Expressions.Expression.Equal(left, right), + ExpressionType.GreaterThanOrEqual => System.Linq.Expressions.Expression.GreaterThanOrEqual(left, right), + ExpressionType.GreaterThan => System.Linq.Expressions.Expression.GreaterThan(left, right), + ExpressionType.NotEqual => System.Linq.Expressions.Expression.NotEqual(left, right), + _ => throw new LambdaCompilerException("unknown operator char:" + expression.NodeType.ToString()), + }; + } + case ConstantExpression constantExpression: + return System.Linq.Expressions.Expression.Constant(constantExpression.Value); + + case MethodCallExpression methodCallExpression: + { + var lambda = methodCallExpression.GetLambda(this.expression.Domain); + if (lambda == null) throw new LambdaCompilerException(string.Format("missing method:{0}", methodCallExpression.MethodName)); + if (lambda is SystemLambdaExpression systemLambda) + { + var arguments = new List(); + foreach (var argument in methodCallExpression.Arguments) + { + arguments.Add(EmitExpression(argument)); + } + return System.Linq.Expressions.Expression.Call(null, systemLambda.Method, arguments); + } + else + { + var elementType = typeof(object); + var customLambda = compiler.GetLambdaCompiler(lambda); + var arguments = new System.Linq.Expressions.Expression[customLambda.parameters.Count]; + for (var i = 0; i < arguments.Length; i++) + { + var parameter = customLambda.parameters[i]; + var argument = EmitExpression(methodCallExpression.Arguments[i]); + arguments[i] = parameter.Type != elementType + ? System.Linq.Expressions.Expression.Convert(argument, elementType) + : argument; + } + + var custom = customLambda.Compile(); + return System.Linq.Expressions.Expression.Convert( + System.Linq.Expressions.Expression.Call( + System.Linq.Expressions.Expression.Constant(custom), + typeof(Delegate).GetMethod(nameof(Delegate.DynamicInvoke)), + System.Linq.Expressions.Expression.NewArrayInit(elementType, arguments)), + lambda.Type); + } + } + case ParameterExpression parameterExpression: + { + var parameter = parameters.Find(p => p.Name == parameterExpression.Name); + if (parameter == null) throw new RuntimeException("Missing member:" + parameterExpression.Name); + return parameter; + } + case UnaryExpression unaryExpression: + { + var operand = EmitExpression(unaryExpression.Operand); + return unaryExpression.NodeType switch + { + ExpressionType.Negate => System.Linq.Expressions.Expression.Negate(operand), + ExpressionType.Not => System.Linq.Expressions.Expression.Not(operand), + _ => throw new RuntimeException("unknown NodeType:" + unaryExpression.NodeType.ToString()), + }; + } + default: + return null; + } + } + + public Delegate Compile(Type delegateType = null) + { + var body = EmitExpression(this.expression.Body); + var lambda = delegateType == null + ? System.Linq.Expressions.Expression.Lambda(body, this.expression.Name, parameters) + : System.Linq.Expressions.Expression.Lambda(delegateType, body, this.expression.Name, parameters); + return lambda.Compile(); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/ExpressionType.cs b/EC.Helper/RabbitFunc/Expressions/ExpressionType.cs new file mode 100644 index 0000000..60637f1 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/ExpressionType.cs @@ -0,0 +1,65 @@ +namespace EC.Helper.RabbitFunc.Expressions; + +public enum ExpressionType : byte +{ + None, + Add, + Subtract, + Multiply, + Divide, + Modulo, + Power, + + Constant, + ArrayIndex, + ArrayLength, + Negate, + Convert, + Assign, + + MemberAccess, + Variable, + Parameter, + Lambda, + MethodCall, + + /// + /// < + /// + LessThan, + + /// + /// <= + /// + LessThanOrEqual, + + /// + /// == + /// + Equal, + + /// + /// >= + /// + GreaterThanOrEqual, + + /// + /// > + /// + GreaterThan, + + /// + /// != + /// + NotEqual, + + /// + /// ! + /// + Not, + + /// + /// IF + /// + Conditional, +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/LambdaCompilerException.cs b/EC.Helper/RabbitFunc/Expressions/LambdaCompilerException.cs new file mode 100644 index 0000000..6593c40 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/LambdaCompilerException.cs @@ -0,0 +1,9 @@ +namespace EC.Helper.RabbitFunc.Expressions; + +[Serializable] +public class LambdaCompilerException : Exception +{ + public LambdaCompilerException(string message) + : base(message) + { } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/LambdaExpression.cs b/EC.Helper/RabbitFunc/Expressions/LambdaExpression.cs new file mode 100644 index 0000000..6e16f0e --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/LambdaExpression.cs @@ -0,0 +1,75 @@ +using EC.Helper.RabbitFunc.Runtime; +using System.Collections.ObjectModel; +using System.Text; + +namespace EC.Helper.RabbitFunc.Expressions; + +public class LambdaExpression : Expression +{ + internal LambdaExpression(string name, Expression body, params ParameterExpression[] parameters) + : base(ExpressionType.Lambda) + { + Name = name; + Parameters = new ReadOnlyCollection(parameters); + Body = body; + } + + public RabbitDomain Domain { get; internal set; } + + public string Name { get; } + + public ReadOnlyCollection Parameters { get; } + + public Expression Body { get; } + + public override Type Type => typeof(double); + + public Delegate Compile(Type deletageType = null) + { + var lambdaCompiler = new ExpressionCompiler(); + return lambdaCompiler.Compile(this, deletageType); + } + + public T Compile() + { + return (T)(object)Compile(typeof(T)); + } + + public override object Eval(RuntimeContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + var lambdaContext = new RuntimeContext(context); + lambdaContext.Domain = Domain; + for (int i = 0; i < Parameters.Count; i++) + { + var parameter = Parameters[i]; + var value = parameter.Eval(context); + lambdaContext.Variable(parameter.Name, value); + } + + return Body.Eval(lambdaContext); + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(Name).Append('('); + using (var e = Parameters.GetEnumerator()) + { + if (e.MoveNext()) + { + sb.Append(e.Current.Name); + while (e.MoveNext()) + { + sb.Append(','); + sb.Append(e.Current.Name); + } + } + } + + sb.Append(")=").Append(Body.ToString()); + return sb.ToString(); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/MemberExpression.cs b/EC.Helper/RabbitFunc/Expressions/MemberExpression.cs new file mode 100644 index 0000000..3f8ebf3 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/MemberExpression.cs @@ -0,0 +1,30 @@ +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Expressions; + +public class MemberExpression : Expression +{ + internal MemberExpression(Expression instance, string name) + : base(ExpressionType.MemberAccess) + { + Instance = instance; + Name = name; + } + + public Expression Instance { get; } + + public string Name { get; } + + public override object Eval(RuntimeContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return default(double); + } + + public override string ToString() + { + return Instance == null ? Name : Instance.ToString() + "." + Name; + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/MethodCallExpression.cs b/EC.Helper/RabbitFunc/Expressions/MethodCallExpression.cs new file mode 100644 index 0000000..ac440a0 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/MethodCallExpression.cs @@ -0,0 +1,88 @@ +using EC.Helper.RabbitFunc.Runtime; +using System.Collections.ObjectModel; +using System.Text; +using MissingMethodException = EC.Helper.RabbitFunc.Runtime.MissingMethodException; + +namespace EC.Helper.RabbitFunc.Expressions; + +public class MethodCallExpression : Expression +{ + internal MethodCallExpression(Expression instance, string methodName, IList arguments) + : base(ExpressionType.MethodCall) + { + Instance = instance; + MethodName = methodName; + Arguments = new ReadOnlyCollection(arguments); + } + + public Expression Instance { get; } + + public string MethodName { get; } + + public ReadOnlyCollection Arguments { get; } + + internal LambdaExpression GetLambda(RabbitDomain domain) + { + LambdaExpression lambda; + if (domain != null) + { + lambda = domain.GetLambda(MethodName); + } + else + { + lambda = Rabbit.GetSystemLambda(MethodName); + } + + return lambda; + } + + public override object Eval(RuntimeContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + if (Instance == null) + { + var lambdaExpression = GetLambda(context.Domain); + if (lambdaExpression == null) + throw new MissingMethodException(string.Format("missing method:{0}", MethodName)); + if (Arguments.Count != lambdaExpression.Parameters.Count) + throw new RuntimeException(string.Format("method:{0}. parame count error!", MethodName)); + + var lambdaContext = new RuntimeContext(context); + for (int i = 0; i < Arguments.Count; i++) + { + var parameter = lambdaExpression.Parameters[i]; + var argument = Arguments[i]; + var value = argument.Eval(context); + lambdaContext.Variable(parameter.Name, value); + } + + return lambdaExpression.Body.Eval(lambdaContext); + } + else + { + throw new NotSupportedException(); + } + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(MethodName).Append('('); + using (var e = Arguments.GetEnumerator()) + { + if (e.MoveNext()) + { + sb.Append(e.Current.ToString()); + while (e.MoveNext()) + { + sb.Append(','); + sb.Append(e.Current.ToString()); + } + } + } + sb.Append(')'); + return sb.ToString(); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/ParameterExpression.cs b/EC.Helper/RabbitFunc/Expressions/ParameterExpression.cs new file mode 100644 index 0000000..4d6f132 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/ParameterExpression.cs @@ -0,0 +1,45 @@ +using EC.Helper.RabbitFunc.Runtime; +using MissingMemberException = EC.Helper.RabbitFunc.Runtime.MissingMemberException; + +namespace EC.Helper.RabbitFunc.Expressions; + +public class ParameterExpression : Expression +{ + internal ParameterExpression(ExpressionType nodeType, Type type, string name) + : base(nodeType) + { + Type = type; + Name = name; + } + + public override Type Type { get; } + + public string Name { get; } + + internal static object Access(RuntimeContext context, string name) + { + var value = context.Access(name); + if (value == null) + throw new MissingMemberException(string.Format("missing member:{0}", name)); + + return value.Value; + } + + internal static T Access(RuntimeContext context, string name) + { + return (T)Access(context, name); + } + + public override object Eval(RuntimeContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + return Access(context, Name); + } + + public override string ToString() + { + return Name; + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/SystemLambdaExpression.cs b/EC.Helper/RabbitFunc/Expressions/SystemLambdaExpression.cs new file mode 100644 index 0000000..01dd1ff --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/SystemLambdaExpression.cs @@ -0,0 +1,19 @@ +using System.Reflection; + +namespace EC.Helper.RabbitFunc.Expressions; + +public abstract class SystemLambdaExpression : LambdaExpression +{ + internal SystemLambdaExpression(MethodInfo method, string name, Expression body, params ParameterExpression[] parameters) + : base(name, body, parameters) + { + if (method == null) + throw new ArgumentNullException(nameof(method)); + + Method = method; + } + + public MethodInfo Method { get; } + + public override Type Type => Method.ReturnType; +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Expressions/UnaryExpression.cs b/EC.Helper/RabbitFunc/Expressions/UnaryExpression.cs new file mode 100644 index 0000000..efb69a6 --- /dev/null +++ b/EC.Helper/RabbitFunc/Expressions/UnaryExpression.cs @@ -0,0 +1,48 @@ +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Expressions; + +public class UnaryExpression : Expression +{ + internal UnaryExpression(ExpressionType type, Expression operand) + : base(type) + { + Operand = operand; + } + + public Expression Operand { get; } + + public override object Eval(RuntimeContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + object value = Operand.Eval(context); + switch (NodeType) + { + case ExpressionType.Negate: + return (-(double)value); + + case ExpressionType.Not: + return (!(bool)value); + + default: + throw new RuntimeException("unknown unary:" + NodeType.ToString()); + } + } + + public override string ToString() + { + switch (NodeType) + { + case ExpressionType.Negate: + return Operand is BinaryExpression ? "-(" + Operand.ToString() + ")" : "-" + Operand.ToString(); + + case ExpressionType.Not: + return Operand is BinaryExpression ? "!(" + Operand.ToString() + ")" : "!" + Operand.ToString(); + + default: + throw new RuntimeException("unknown operator:" + NodeType.ToString()); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/AbsLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/AbsLambdaExpression.cs new file mode 100644 index 0000000..4586034 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/AbsLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class AbsLambdaExpression : SystemLambdaExpression +{ + public AbsLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Abs), new Type[] { typeof(double) }), "abs", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Abs(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/AcosLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/AcosLambdaExpression.cs new file mode 100644 index 0000000..430c0fb --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/AcosLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class AcosLambdaExpression : SystemLambdaExpression +{ + public AcosLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Acos), new Type[] { typeof(double) }), "acos", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Acos(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/AsinLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/AsinLambdaExpression.cs new file mode 100644 index 0000000..fdf167a --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/AsinLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class AsinLambdaExpression : SystemLambdaExpression +{ + public AsinLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Asin), new Type[] { typeof(double) }), "asin", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Asin(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/AtanLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/AtanLambdaExpression.cs new file mode 100644 index 0000000..e8e5648 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/AtanLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class AtanLambdaExpression : SystemLambdaExpression +{ + public AtanLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Atan), new Type[] { typeof(double) }), "atan", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Atan(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/CeilingLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/CeilingLambdaExpression.cs new file mode 100644 index 0000000..c789665 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/CeilingLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class CeilingLambdaExpression : SystemLambdaExpression +{ + public CeilingLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Ceiling), new Type[] { typeof(double) }), "ceiling", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Ceiling(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/CosLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/CosLambdaExpression.cs new file mode 100644 index 0000000..70700ca --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/CosLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class CosLambdaExpression : SystemLambdaExpression +{ + public CosLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Cos), new Type[] { typeof(double) }), "cos", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Cos(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/CoshLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/CoshLambdaExpression.cs new file mode 100644 index 0000000..a4f2f82 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/CoshLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class CoshLambdaExpression : SystemLambdaExpression +{ + public CoshLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Cosh), new Type[] { typeof(double) }), "cosh", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Cosh(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/ExpLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/ExpLambdaExpression.cs new file mode 100644 index 0000000..09ec321 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/ExpLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class ExpLambdaExpression : SystemLambdaExpression +{ + public ExpLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Exp), new Type[] { typeof(double) }), "exp", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Exp(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/ExternLambdaAttribute.cs b/EC.Helper/RabbitFunc/Extern/ExternLambdaAttribute.cs new file mode 100644 index 0000000..dc84f5b --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/ExternLambdaAttribute.cs @@ -0,0 +1,5 @@ +namespace EC.Helper.RabbitFunc.Extern; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +internal class ExternLambdaAttribute : Attribute +{ } \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/FloorLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/FloorLambdaExpression.cs new file mode 100644 index 0000000..5744285 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/FloorLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class FloorLambdaExpression : SystemLambdaExpression +{ + public FloorLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Floor), new Type[] { typeof(double) }), "floor", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Floor(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/LogLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/LogLambdaExpression.cs new file mode 100644 index 0000000..1eb262c --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/LogLambdaExpression.cs @@ -0,0 +1,24 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class LogLambdaExpression : SystemLambdaExpression +{ + public LogLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Log), new Type[] { typeof(double), typeof(double) }), "log", new BodyExpression(), + Expression.Parameter(typeof(double), "x"), + Expression.Parameter(typeof(double), "e")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + var x = ParameterExpression.Access(context, "x"); + var e = ParameterExpression.Access(context, "e"); + return Math.Log(x, e); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/MaxLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/MaxLambdaExpression.cs new file mode 100644 index 0000000..9a887ef --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/MaxLambdaExpression.cs @@ -0,0 +1,24 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class MaxLambdaExpression : SystemLambdaExpression +{ + public MaxLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Max), new Type[] { typeof(double), typeof(double) }), "max", new BodyExpression(), + Expression.Parameter(typeof(double), "left"), + Expression.Parameter(typeof(double), "right")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + var left = ParameterExpression.Access(context, "left"); + var right = ParameterExpression.Access(context, "right"); + return Math.Max(left, right); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/MinLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/MinLambdaExpression.cs new file mode 100644 index 0000000..c957d24 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/MinLambdaExpression.cs @@ -0,0 +1,24 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class MinLambdaExpression : SystemLambdaExpression +{ + public MinLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Min), new Type[] { typeof(double), typeof(double) }), "min", new BodyExpression(), + Expression.Parameter(typeof(double), "left"), + Expression.Parameter(typeof(double), "right")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + var left = ParameterExpression.Access(context, "left"); + var right = ParameterExpression.Access(context, "right"); + return Math.Min(left, right); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/RoundLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/RoundLambdaExpression.cs new file mode 100644 index 0000000..c724482 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/RoundLambdaExpression.cs @@ -0,0 +1,24 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class RoundLambdaExpression : SystemLambdaExpression +{ + public RoundLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Round), new Type[] { typeof(double), typeof(int) }), "round", new BodyExpression(), + Expression.Parameter(typeof(double), "value"), + Expression.Parameter(typeof(double), "digits")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + var value = ParameterExpression.Access(context, "value"); + var digits = (int)ParameterExpression.Access(context, "digits"); + return Math.Round(value, digits); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/SinLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/SinLambdaExpression.cs new file mode 100644 index 0000000..e491af3 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/SinLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class SinLambdaExpression : SystemLambdaExpression +{ + public SinLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Sin), new Type[] { typeof(double) }), "sin", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Sin(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/SinhLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/SinhLambdaExpression.cs new file mode 100644 index 0000000..1f8b2cc --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/SinhLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class SinhLambdaExpression : SystemLambdaExpression +{ + public SinhLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Sinh), new Type[] { typeof(double) }), "sinh", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Sinh(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/SqrtLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/SqrtLambdaExpression.cs new file mode 100644 index 0000000..df9f019 --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/SqrtLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class SqrtLambdaExpression : SystemLambdaExpression +{ + public SqrtLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Sqrt), new Type[] { typeof(double) }), "sqrt", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Sqrt(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/TanLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/TanLambdaExpression.cs new file mode 100644 index 0000000..a79766b --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/TanLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class TanLambdaExpression : SystemLambdaExpression +{ + public TanLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Tan), new Type[] { typeof(double) }), "tan", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Tan(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Extern/TanhLambdaExpression.cs b/EC.Helper/RabbitFunc/Extern/TanhLambdaExpression.cs new file mode 100644 index 0000000..d854a1a --- /dev/null +++ b/EC.Helper/RabbitFunc/Extern/TanhLambdaExpression.cs @@ -0,0 +1,21 @@ +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Runtime; + +namespace EC.Helper.RabbitFunc.Extern; + +[ExternLambda] +internal class TanhLambdaExpression : SystemLambdaExpression +{ + public TanhLambdaExpression() + : base(typeof(Math).GetMethod(nameof(Math.Tanh), new Type[] { typeof(double) }), "tanh", new BodyExpression(), + Expression.Parameter(typeof(double), "x")) + { } + + private class BodyExpression : Expression + { + public override object Eval(RuntimeContext context) + { + return Math.Tanh(ParameterExpression.Access(context, "x")); + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Rabbit.cs b/EC.Helper/RabbitFunc/Rabbit.cs new file mode 100644 index 0000000..2780f61 --- /dev/null +++ b/EC.Helper/RabbitFunc/Rabbit.cs @@ -0,0 +1,84 @@ +using EC.Helper.RabbitFunc.Compiler; +using EC.Helper.RabbitFunc.Expressions; +using EC.Helper.RabbitFunc.Extern; +using System.Reflection; + +namespace EC.Helper.RabbitFunc; + +public class Rabbit +{ + private static readonly Dictionary systemFunctions = new Dictionary(); + + static Rabbit() + { + var assembly = Assembly.GetExecutingAssembly(); + foreach (var type in assembly.GetTypes()) + { + var attributes = type.GetCustomAttributes(typeof(ExternLambdaAttribute), false); + if (attributes.Length > 0) + { + var constructor = type.GetConstructor(Type.EmptyTypes); + if (constructor != null) + Register((SystemLambdaExpression)constructor.Invoke(null)); + } + } + } + + internal static void Register(SystemLambdaExpression lambda) + { + if (lambda == null) + throw new ArgumentNullException(nameof(lambda)); + + systemFunctions[lambda.Name] = lambda; + } + + internal static bool TryGetSystemLambda(string name, out SystemLambdaExpression? lambda) + { + return systemFunctions.TryGetValue(name, out lambda); + } + + internal static SystemLambdaExpression? GetSystemLambda(string name) + { + systemFunctions.TryGetValue(name, out SystemLambdaExpression? lambda); + return lambda; + } + + public static LambdaExpression CompileFromSource(string source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + LambdaExpression? lambda = null; + using var reader = new StringReader(source); + var tokenizer = new Tokenizer(reader); + var parser = new Parser(tokenizer); + while (!tokenizer.EndOfStream) + { + var expression = parser.Parse(); + if (expression == null) break; + else lambda = expression; + } + + return lambda ?? throw new CompilerException(tokenizer.Position, "The formula was not found"); + } + + public static LambdaExpression CompileFromFile(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + LambdaExpression? lambda = null; + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var reader = new StreamReader(stream, System.Text.Encoding.UTF8); + var tokenizer = new Tokenizer(reader); + var parser = new Parser(tokenizer); + while (!tokenizer.EndOfStream) + { + var expression = parser.Parse(); + if (expression == null) break; + else lambda = expression; + } + + return lambda ?? throw new CompilerException(tokenizer.Position, "The formula was not found"); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/RabbitDomain.cs b/EC.Helper/RabbitFunc/RabbitDomain.cs new file mode 100644 index 0000000..10b7b03 --- /dev/null +++ b/EC.Helper/RabbitFunc/RabbitDomain.cs @@ -0,0 +1,83 @@ +using EC.Helper.RabbitFunc.Compiler; +using EC.Helper.RabbitFunc.Expressions; + +namespace EC.Helper.RabbitFunc; + +public class RabbitDomain +{ + private readonly Dictionary lambdas = new Dictionary(); + + public void Register(LambdaExpression lambda) + { + if (lambda == null) + throw new ArgumentNullException(nameof(lambda)); + + lambdas[lambda.Name] = lambda; + } + + public bool TryGetLambda(string name, out LambdaExpression? lambda) + { + if (lambdas.TryGetValue(name, out lambda)) + { + return true; + } + if (Rabbit.TryGetSystemLambda(name, out var systemLambda)) + { + lambda = systemLambda; + return true; + } + + return false; + } + + public LambdaExpression? GetLambda(string name) + { + TryGetLambda(name, out LambdaExpression? lambda); + return lambda; + } + + public LambdaExpression CompileFromSource(string source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + LambdaExpression? lambda = null; + using var reader = new StringReader(source); + var tokenizer = new Tokenizer(reader); + var parser = new Parser(tokenizer); + while (!tokenizer.EndOfStream) + { + var expression = parser.Parse(); + if (expression == null) break; + + expression.Domain = this; + Register(expression); + lambda = expression; + } + + return lambda ?? throw new CompilerException(tokenizer.Position, "The formula was not found"); + } + + public LambdaExpression CompileFromFile(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + LambdaExpression? lambda = null; + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var reader = new StreamReader(stream, System.Text.Encoding.UTF8); + var tokenizer = new Tokenizer(reader); + var parser = new Parser(tokenizer); + while (!tokenizer.EndOfStream) + { + var expression = parser.Parse(); + if (expression == null) break; + + expression.Domain = this; + Register(expression); + lambda = expression; + } + + return lambda ?? throw new CompilerException(tokenizer.Position, "The formula was not found"); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/RabbitFuncUtil.cs b/EC.Helper/RabbitFunc/RabbitFuncUtil.cs new file mode 100644 index 0000000..7b8ba6a --- /dev/null +++ b/EC.Helper/RabbitFunc/RabbitFuncUtil.cs @@ -0,0 +1,45 @@ +using EC.Helper.RabbitFunc.Compiler; +using EC.Helper.RabbitFunc.Expressions; + +namespace EC.Helper.RabbitFunc; + +public class RabbitFuncUtil +{ + public static LambdaExpression CompileFromSource(string source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + + LambdaExpression? lambda = null; + using var reader = new StringReader(source); + var tokenizer = new Tokenizer(reader); + var parser = new Parser(tokenizer); + while (!tokenizer.EndOfStream) + { + var expression = parser.Parse(); + if (expression == null) break; + lambda = expression; + } + + return lambda ?? throw new CompilerException(tokenizer.Position, "The formula was not found"); + } + + public static LambdaExpression CompileFromFile(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + LambdaExpression? lambda = null; + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var reader = new StreamReader(stream, System.Text.Encoding.UTF8); + var tokenizer = new Tokenizer(reader); + var parser = new Parser(tokenizer); + while (!tokenizer.EndOfStream) + { + var expression = parser.Parse(); + if (expression == null) break; + lambda = expression; + } + + return lambda ?? throw new CompilerException(tokenizer.Position, "The formula was not found"); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Runtime/MemberAccessException.cs b/EC.Helper/RabbitFunc/Runtime/MemberAccessException.cs new file mode 100644 index 0000000..f8390dd --- /dev/null +++ b/EC.Helper/RabbitFunc/Runtime/MemberAccessException.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace EC.Helper.RabbitFunc.Runtime +{ + [Serializable] + public class MemberAccessException : SystemException + { + public MemberAccessException() + { } + + public MemberAccessException(string message) + : base(message) + { } + + public MemberAccessException(string message, Exception innerException) + : base(message, innerException) + { } + + protected MemberAccessException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Runtime/MissingMemberException.cs b/EC.Helper/RabbitFunc/Runtime/MissingMemberException.cs new file mode 100644 index 0000000..a267bb7 --- /dev/null +++ b/EC.Helper/RabbitFunc/Runtime/MissingMemberException.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace EC.Helper.RabbitFunc.Runtime +{ + [Serializable] + public class MissingMemberException : MemberAccessException + { + public MissingMemberException() + { } + + public MissingMemberException(string message) + : base(message) + { } + + public MissingMemberException(string message, Exception innerException) + : base(message, innerException) + { } + + protected MissingMemberException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Runtime/MissingMethodException.cs b/EC.Helper/RabbitFunc/Runtime/MissingMethodException.cs new file mode 100644 index 0000000..7474ea0 --- /dev/null +++ b/EC.Helper/RabbitFunc/Runtime/MissingMethodException.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace EC.Helper.RabbitFunc.Runtime +{ + [Serializable] + public class MissingMethodException : MissingMemberException + { + public MissingMethodException() + { } + + public MissingMethodException(string message) + : base(message) + { } + + public MissingMethodException(string message, Exception innerException) + : base(message, innerException) + { } + + protected MissingMethodException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Runtime/RuntimeContext.cs b/EC.Helper/RabbitFunc/Runtime/RuntimeContext.cs new file mode 100644 index 0000000..2e6c6bb --- /dev/null +++ b/EC.Helper/RabbitFunc/Runtime/RuntimeContext.cs @@ -0,0 +1,91 @@ +namespace EC.Helper.RabbitFunc.Runtime; + +public class RuntimeContext +{ + private readonly Dictionary variables = new Dictionary(); + private readonly RuntimeContext parent; + + public RuntimeContext() + { } + + internal RuntimeContext(RuntimeContext parent) + { + if (parent == null) + throw new ArgumentNullException(nameof(parent)); + + this.parent = parent; + this.Domain = parent.Domain; + } + + public object this[string name] + { + set + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + variables[name] = new RuntimeVariable(value); + } + } + + internal RabbitDomain Domain { get; set; } + + private RuntimeContext FindCurrentOrParent(string name) + { + return variables.ContainsKey(name) ? this : parent?.FindCurrentOrParent(name); + } + + public void Variable(string name, object value) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + variables[name] = new RuntimeVariable(value); + } + + public RuntimeVariable Assign(string name, object value) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + var context = FindCurrentOrParent(name); + if (context == null) return null; + if (!context.variables.TryGetValue(name, out RuntimeVariable rv)) return null; + + rv.SetValue(value); + return rv; + } + + public RuntimeVariable Access(string name) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + var context = FindCurrentOrParent(name); + if (context == null) return null; + if (!context.variables.TryGetValue(name, out RuntimeVariable rv)) return null; + return rv; + } + + public class RuntimeVariable + { + public RuntimeVariable() + { } + + public RuntimeVariable(object value) + { + Value = value; + IsAssigned = true; + } + + public object Value { get; set; } + + public bool IsAssigned { get; set; } + + public void SetValue(object value) + { + Value = value; + IsAssigned = true; + } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Runtime/RuntimeException.cs b/EC.Helper/RabbitFunc/Runtime/RuntimeException.cs new file mode 100644 index 0000000..1c3df77 --- /dev/null +++ b/EC.Helper/RabbitFunc/Runtime/RuntimeException.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace EC.Helper.RabbitFunc.Runtime +{ + [Serializable] + public class RuntimeException : Exception + { + public RuntimeException() + { } + + public RuntimeException(string message) + : base(message) + { } + + public RuntimeException(string message, Exception innerException) + : base(message, innerException) + { } + + protected RuntimeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Runtime/SystemException.cs b/EC.Helper/RabbitFunc/Runtime/SystemException.cs new file mode 100644 index 0000000..80d6510 --- /dev/null +++ b/EC.Helper/RabbitFunc/Runtime/SystemException.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace EC.Helper.RabbitFunc.Runtime +{ + [Serializable] + public class SystemException : Exception + { + public SystemException() + { } + + public SystemException(string message) + : base(message) + { } + + public SystemException(string message, Exception innerException) + : base(message, innerException) + { } + + protected SystemException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/CommentToken.cs b/EC.Helper/RabbitFunc/Syntax/CommentToken.cs new file mode 100644 index 0000000..89a75d5 --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/CommentToken.cs @@ -0,0 +1,14 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal class CommentToken : Token +{ + private string comment; + + public CommentToken(string comment) + : base(TokenKind.Comment) + { + this.comment = comment; + } + + public override string Text => comment; +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/ConstantToken.cs b/EC.Helper/RabbitFunc/Syntax/ConstantToken.cs new file mode 100644 index 0000000..0ba6ce6 --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/ConstantToken.cs @@ -0,0 +1,16 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal class ConstantToken : Token +{ + private double value; + + public ConstantToken(double value) + : base(TokenKind.Constant) + { + this.value = value; + } + + public override string Text => value.ToString(); + + public override double Value => value; +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/ErrorToken.cs b/EC.Helper/RabbitFunc/Syntax/ErrorToken.cs new file mode 100644 index 0000000..e206388 --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/ErrorToken.cs @@ -0,0 +1,16 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal class ErrorToken : Token +{ + private string message; + + public ErrorToken(string message) + : base(TokenKind.Error) + { + this.message = message; + } + + public override string Text => message; + + public string Message => message; +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/IdentifierToken.cs b/EC.Helper/RabbitFunc/Syntax/IdentifierToken.cs new file mode 100644 index 0000000..1616272 --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/IdentifierToken.cs @@ -0,0 +1,14 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal class IdentifierToken : Token +{ + private string name; + + public IdentifierToken(string name) + : base(TokenKind.Identifier) + { + this.name = name; + } + + public override string Text => name; +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/OperatorToken.cs b/EC.Helper/RabbitFunc/Syntax/OperatorToken.cs new file mode 100644 index 0000000..84c56b0 --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/OperatorToken.cs @@ -0,0 +1,18 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal class OperatorToken : Token +{ + private string @operator; + private byte precedence; + + public OperatorToken(TokenKind kind, string @operator, byte precedence) + : base(kind) + { + this.@operator = @operator; + this.precedence = precedence; + } + + public override string Text => @operator; + + public byte Precedence => precedence; +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/SymbolToken.cs b/EC.Helper/RabbitFunc/Syntax/SymbolToken.cs new file mode 100644 index 0000000..2baa9f2 --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/SymbolToken.cs @@ -0,0 +1,14 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal class SymbolToken : Token +{ + private string symbol; + + public SymbolToken(TokenKind kind, string symbol) + : base(kind) + { + this.symbol = symbol; + } + + public override string Text => symbol; +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/Token.cs b/EC.Helper/RabbitFunc/Syntax/Token.cs new file mode 100644 index 0000000..02cfd3c --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/Token.cs @@ -0,0 +1,55 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal abstract class Token +{ + private TokenKind kind; + + protected Token(TokenKind kind) + { + this.kind = kind; + } + + public abstract string Text { get; } + + public TokenKind Kind => kind; + + public virtual double Value + { + get => throw new System.NotSupportedException(); + } + + public static Token Error(string message) + { + return new ErrorToken(message); + } + + public static Token Operator(TokenKind kind, string @operator, byte precedence) + { + return new OperatorToken(kind, @operator, precedence); + } + + public static Token Symbol(TokenKind kind, string symbol) + { + return new SymbolToken(kind, symbol); + } + + public static Token Constant(double value) + { + return new ConstantToken(value); + } + + public static Token Comment(string text) + { + return new CommentToken(text); + } + + public static Token Identifier(string name) + { + return new IdentifierToken(name); + } + + public override string ToString() + { + return Text.ToString(); + } +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/TokenKind.cs b/EC.Helper/RabbitFunc/Syntax/TokenKind.cs new file mode 100644 index 0000000..1172658 --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/TokenKind.cs @@ -0,0 +1,113 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal enum TokenKind : byte +{ + EndOfFile, + NewLine, + Error, + Identifier, + Constant, + Comment, + + /// + /// + + /// + Add, + + /// + /// - + /// + Subtract, + + /// + /// * + /// + Multiply, + + /// + /// / + /// + Divide, + + /// + /// % + /// + Mod, + + /// + /// ^ + /// + Power, + + /// + /// , + /// + Comma, + + /// + /// = + /// + Assign, + + /// + /// . + /// + Dot, + + /// + /// ( + /// + LeftParen, + + /// + /// ) + /// + RightParen, + + /// + /// [ + /// + LeftBracket, + + /// + /// ] + /// + RightBracket, + + /// + /// ! + /// + Not, + + /// + /// < + /// + LessThan, + + /// + /// <= + /// + LessThanOrEqual, + + /// + /// == + /// + Equal, + + /// + /// >= + /// + GreaterThanOrEqual, + + /// + /// > + /// + GreaterThan, + + /// + /// != + /// + NotEqual, + + IF, +} \ No newline at end of file diff --git a/EC.Helper/RabbitFunc/Syntax/Tokens.cs b/EC.Helper/RabbitFunc/Syntax/Tokens.cs new file mode 100644 index 0000000..2f738ce --- /dev/null +++ b/EC.Helper/RabbitFunc/Syntax/Tokens.cs @@ -0,0 +1,32 @@ +namespace EC.Helper.RabbitFunc.Syntax; + +internal static class Tokens +{ + public static readonly Token EndOfFileToken = Token.Symbol(TokenKind.EndOfFile, ""); + public static readonly Token NewLineToken = Token.Symbol(TokenKind.NewLine, ""); + + public static readonly Token AddToken = Token.Operator(TokenKind.Add, "+", 4); + public static readonly Token SubtractToken = Token.Operator(TokenKind.Subtract, "-", 4); + public static readonly Token MultiplyToken = Token.Operator(TokenKind.Multiply, "*", 5); + public static readonly Token DivideToken = Token.Operator(TokenKind.Divide, "/", 5); + public static readonly Token ModToken = Token.Operator(TokenKind.Mod, "%", 5); + public static readonly Token PowerToken = Token.Operator(TokenKind.Power, "^", 6); + public static readonly Token CommaToken = Token.Symbol(TokenKind.Comma, ","); + public static readonly Token AssignToken = Token.Symbol(TokenKind.Assign, "="); + public static readonly Token DotToken = Token.Symbol(TokenKind.Dot, "."); + + public static readonly Token LeftParenToken = Token.Symbol(TokenKind.LeftParen, "("); + public static readonly Token RightParenToken = Token.Symbol(TokenKind.RightParen, ")"); + public static readonly Token LeftBracketToken = Token.Symbol(TokenKind.LeftBracket, "["); + public static readonly Token RightBracketToken = Token.Symbol(TokenKind.RightBracket, "]"); + + public static readonly Token NotToken = Token.Symbol(TokenKind.Not, "!"); + public static readonly Token LessThanToken = Token.Operator(TokenKind.LessThan, "<", 2); + public static readonly Token LessThanOrEqualToken = Token.Operator(TokenKind.LessThanOrEqual, "<=", 2); + public static readonly Token EqualToken = Token.Operator(TokenKind.Equal, "==", 1); + public static readonly Token GreaterThanOrEqualToken = Token.Operator(TokenKind.GreaterThanOrEqual, ">=", 2); + public static readonly Token GreaterThanToken = Token.Operator(TokenKind.GreaterThan, ">", 2); + public static readonly Token NotEqualToken = Token.Operator(TokenKind.NotEqual, "!=", 1); + + public static readonly Token IFToken = Token.Symbol(TokenKind.IF, "if"); +} \ No newline at end of file