Skip to content

Instantly share code, notes, and snippets.

@Timwi
Created October 26, 2015 20:03
Show Gist options
  • Save Timwi/dcb40ad1906b7286cfdf to your computer and use it in GitHub Desktop.
Save Timwi/dcb40ad1906b7286cfdf to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using RT.Generexes;
using RT.Util;
using RT.Util.Consoles;
using RT.Util.ExtensionMethods;
using S = RT.Generexes.Stringerex;
using SN = RT.Generexes.Stringerex<Timwi.Temp.GenerexBasedParser.ExpressionNode>;
namespace Timwi.Temp
{
using GenerexBasedParser;
partial class Program
{
private static int ExpressionParser()
{
var unaryFunctionsRaw = new Dictionary<string, Func<double, double>>
{
{ "sin", Math.Sin },
{ "cos", Math.Cos },
{ "tan", Math.Tan },
{ "sinh", Math.Sinh},
{ "cosh", Math.Cosh },
{ "tanh", Math.Tanh },
{ "sqrt", Math.Sqrt },
{ "sqr", x => x * x }
};
var unaryFunctions = unaryFunctionsRaw.Select(kvp => new S(kvp.Key).Process(m => kvp.Value));
var unaryOperatorsRaw = new Dictionary<string, Func<double, double>>
{
{ "+", d => d },
{ "-", d => -d }
};
var unaryOperators = unaryOperatorsRaw.Select(kvp => new S(kvp.Key).Process(m => kvp.Value));
var digit = (S) (ch => ch >= '0' && ch <= '9');
var number = digit.RepeatGreedy().Then(new S('.').Then(digit.RepeatGreedy(1)).OptionalGreedy()).Where(m => m.Length > 0).Process(m => (ExpressionNode) new NumberNode { Number = Convert.ToDouble(m.Match) });
var functionNameFirstCharacter = (S) (ch => ch == '_' || char.IsLetter(ch));
var functionNameCharacter = (S) (ch => ch == '_' || char.IsLetterOrDigit(ch));
var functionName = functionNameFirstCharacter.Then(functionNameCharacter.RepeatGreedy()).Process(m => m.Match);
var expression = SN.Recursive(expr =>
{
var leftAssociativeBinaryOperators = Ut.Lambda((SN higherPrecedence, Stringerex<Func<double, double, double>> operators) =>
higherPrecedence.ThenRaw(operators.ThenRaw(higherPrecedence, (op, node) => new { Op = op, ExpressionNode = node }).RepeatGreedy(),
(firstOperand, operands) => operands.Aggregate(firstOperand, (prev, next) => (ExpressionNode) new BinaryOperatorNode { Left = prev, Right = next.ExpressionNode, Operation = next.Op })));
var rightAssociativeBinaryOperators = Ut.Lambda((SN higherPrecedence, Stringerex<Func<double, double, double>> operators) =>
higherPrecedence.ThenRaw(operators, (node, op) => new { Op = op, ExpressionNode = node }).RepeatGreedy().ThenRaw(higherPrecedence,
(operands, lastOperand) => operands.Reverse().Aggregate(lastOperand, (prev, next) => (ExpressionNode) new BinaryOperatorNode { Left = next.ExpressionNode, Right = prev, Operation = next.Op })));
var expectCloseParen = ((S) ')')
.Or(S.End.Where(m => { throw new ParseException(m.Index, "Unexpected end of expression: ')' missing."); }))
.Or(new S().Where(m => { throw new ParseException(m.Index, "Unrecognized operator."); }));
var expectOpenParen = ((S) '(')
.Or(S.End.Where(m => { throw new ParseException(m.Index, "Unexpected end of expression: '(' missing."); }))
.Or(new S().Where(m => { throw new ParseException(m.Index, "'(' expected."); }));
var power = SN.Recursive(pwr =>
{
var primaryExpr = Stringerex.Ors(
number,
((S) '(').Then(expr).Then(expectCloseParen),
S.Ors(unaryFunctions).Then(expectOpenParen).ThenRaw(expr, (func, node) => (ExpressionNode) new UnaryOperatorNode { Child = node, Operation = func }).Then(expectCloseParen),
S.Ors(unaryOperators).ThenRaw(pwr, (func, node) => (ExpressionNode) new UnaryOperatorNode { Child = node, Operation = func }),
S.End.Process<ExpressionNode>(m => { throw new ParseException(m.Index, "Unexpected end of expression: operand missing."); }),
((S) ')').Process<ExpressionNode>(m => { throw new ParseException(m.Index, "Expected a number, parenthesised expression, unary operator or function name."); }),
new SN(null).Where(m => { throw new ParseException(m.Index, "Unrecognized unary operator or function name."); })
).Atomic();
return rightAssociativeBinaryOperators(primaryExpr, ((S) '^').Process(m => new Func<double, double, double>(Math.Pow)));
});
var multiplicative = leftAssociativeBinaryOperators(power, Stringerex.Ors(
((S) '*').Process(m => Ut.Lambda((double a, double b) => a * b)),
((S) '/').Process(m => Ut.Lambda((double a, double b) => a / b))));
var additive = leftAssociativeBinaryOperators(multiplicative, Stringerex.Ors(
((S) '+').Process(m => Ut.Lambda((double a, double b) => a + b)),
((S) '-').Process(m => Ut.Lambda((double a, double b) => a - b))
));
return additive;
}).Then(Stringerex.Ors(
((S) ')').LookAhead().Where(m => { throw new ParseException(m.Index, "Extraneous closing parenthesis."); }),
S.End.LookAheadNegative().Where(m => { throw new ParseException(m.Index, "Missing operator."); }),
new S()
));
while (true)
{
ConsoleUtil.Write("> ".Color(ConsoleColor.Cyan));
var line = Console.ReadLine();
if (string.IsNullOrEmpty(line))
continue;
if (line == "exit")
return 0;
try
{
var parsed = expression.RawMatchExact(line.ToCharArray());
if (parsed == null)
ConsoleUtil.WriteLine("Parse error.".Color(ConsoleColor.Yellow));
else
ConsoleUtil.WriteLine(("= " + parsed.Evaluate()).Color(ConsoleColor.Green));
}
catch (ParseException p)
{
ConsoleUtil.WriteLine(new string(' ', p.Index + 2) + "^".Color(ConsoleColor.Red));
ConsoleUtil.WriteLine(p.Message.Color(ConsoleColor.Magenta));
}
}
}
}
namespace GenerexBasedParser
{
sealed class ParseException : Exception
{
public int Index { get; private set; }
public ParseException(int index, string message)
: base(message)
{
Index = index;
}
}
abstract class ExpressionNode
{
public abstract double Evaluate();
}
sealed class UnaryOperatorNode : ExpressionNode
{
public ExpressionNode Child;
public Func<double, double> Operation;
public override double Evaluate() { return Operation(Child.Evaluate()); }
}
sealed class BinaryOperatorNode : ExpressionNode
{
public ExpressionNode Left;
public ExpressionNode Right;
public Func<double, double, double> Operation;
public override double Evaluate() { return Operation(Left.Evaluate(), Right.Evaluate()); }
}
sealed class NumberNode : ExpressionNode
{
public double Number;
public override double Evaluate() { return Number; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment