560 lines
16 KiB
C#
560 lines
16 KiB
C#
using System;
|
|
using System.Reflection;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Linq;
|
|
namespace ln.templates
|
|
{
|
|
delegate bool ConditionDelegate(char ch);
|
|
|
|
public class Expression
|
|
{
|
|
public String Source { get; }
|
|
private Eval TopEval { get; set; }
|
|
|
|
public Expression(String expr)
|
|
{
|
|
Source = expr;
|
|
|
|
Compile();
|
|
}
|
|
|
|
public object Evaluate(object o)
|
|
{
|
|
Context context = new Context(o, null);
|
|
return TopEval.Evaluate(context);
|
|
}
|
|
public object Evaluate(Context context)
|
|
{
|
|
return TopEval.Evaluate(context);
|
|
}
|
|
|
|
public bool IsTrue(Context context)
|
|
{
|
|
try
|
|
{
|
|
object v = Evaluate(context);
|
|
if ((v is bool) && ((bool)v))
|
|
return true;
|
|
if ((v is string) && (!String.Empty.Equals(v)))
|
|
return true;
|
|
if ((v is int) && ((int)v != 0))
|
|
return true;
|
|
return false;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
private void Compile()
|
|
{
|
|
try
|
|
{
|
|
StringReader reader = new StringReader(Source);
|
|
List<Token> tokens = new List<Token>();
|
|
while (reader.Peek() != -1)
|
|
ReadToken(reader, tokens);
|
|
|
|
Queue<Token> qtokens = new Queue<Token>(tokens);
|
|
|
|
TopEval = BuildExpression(qtokens);
|
|
} catch (Exception e)
|
|
{
|
|
throw new Exception(String.Format("Failed to compile expression: {0}", Source), e);
|
|
}
|
|
}
|
|
|
|
|
|
private Eval BuildName(Token nameToken,Eval parent,Queue<Token> tokens)
|
|
{
|
|
if ((tokens.Count > 0) && (tokens.Peek().TokenType == TokenType.OPENLIST))
|
|
{
|
|
tokens.Dequeue();
|
|
|
|
List<Eval> pl = new List<Eval>();
|
|
while (tokens.Peek().TokenType != TokenType.ENDLIST)
|
|
{
|
|
pl.Add(BuildExpression(tokens));
|
|
|
|
if (tokens.Peek().TokenType == TokenType.ENDLIST)
|
|
break;
|
|
|
|
if (tokens.Peek().TokenType != TokenType.COMMA)
|
|
throw new FormatException("Expected ',' between parameters to call statement");
|
|
|
|
tokens.Dequeue();
|
|
}
|
|
|
|
tokens.Dequeue();
|
|
|
|
return new MethodCall(parent, nameToken.Value, pl.ToArray());
|
|
}
|
|
else
|
|
{
|
|
return new Name(parent, nameToken.Value);
|
|
}
|
|
}
|
|
|
|
private Eval BuildPath(Queue<Token> tokens)
|
|
{
|
|
Eval currentEval = BuildName(tokens.Dequeue(), null, tokens);
|
|
|
|
while (tokens.Count > 0)
|
|
{
|
|
Token token = tokens.Peek();
|
|
switch (token.TokenType)
|
|
{
|
|
case TokenType.DOT:
|
|
tokens.Dequeue();
|
|
currentEval = BuildName(tokens.Dequeue(), currentEval, tokens);
|
|
break;
|
|
case TokenType.OPENINDEXER:
|
|
tokens.Dequeue();
|
|
Eval index = BuildExpression(tokens);
|
|
token = tokens.Dequeue();
|
|
if (token.TokenType != TokenType.ENDINDEXER)
|
|
throw new FormatException("Expected ']'");
|
|
currentEval = new Indexer(currentEval, index);
|
|
break;
|
|
default:
|
|
return currentEval;
|
|
}
|
|
}
|
|
return currentEval;
|
|
}
|
|
|
|
|
|
private Eval BuildExpression(Queue<Token> tokens)
|
|
{
|
|
Token next = tokens.Peek();
|
|
|
|
switch (next.TokenType)
|
|
{
|
|
case TokenType.NAME:
|
|
return BuildPath(tokens);
|
|
case TokenType.NUMBER:
|
|
return new Number(tokens.Dequeue().Value);
|
|
case TokenType.STRING:
|
|
return new CString(tokens.Dequeue().Value);
|
|
}
|
|
|
|
throw new FormatException(String.Format("unexpected Token: {0}", next.Value));
|
|
}
|
|
|
|
private void ReadToken(TextReader reader, List<Token> tokens)
|
|
{
|
|
if (reader.Peek() == -1)
|
|
return;
|
|
char ch = (char)reader.Peek();
|
|
if (char.IsWhiteSpace(ch))
|
|
{
|
|
reader.Read();
|
|
}
|
|
else if (char.IsLetter(ch) || (ch == '_'))
|
|
{
|
|
ReadName(reader, tokens);
|
|
}
|
|
else if (char.IsDigit(ch))
|
|
{
|
|
ReadNumber(reader, tokens);
|
|
}
|
|
else if (ch == '"')
|
|
{
|
|
ReadString(reader, tokens);
|
|
}
|
|
else if (ch == '.')
|
|
{
|
|
reader.Read();
|
|
tokens.Add(new Token(TokenType.DOT, "."));
|
|
}
|
|
else if (ch == '[')
|
|
{
|
|
reader.Read();
|
|
tokens.Add(new Token(TokenType.OPENINDEXER, new string(new char[] { ch })));
|
|
}
|
|
else if (ch == ']')
|
|
{
|
|
reader.Read();
|
|
tokens.Add(new Token(TokenType.ENDINDEXER, new string(new char[] { ch })));
|
|
}
|
|
else if (ch == '(')
|
|
{
|
|
reader.Read();
|
|
tokens.Add(new Token(TokenType.OPENLIST, new string(new char[] { ch })));
|
|
}
|
|
else if (ch == ')')
|
|
{
|
|
reader.Read();
|
|
tokens.Add(new Token(TokenType.ENDLIST, new string(new char[] { ch })));
|
|
}
|
|
else if (ch == ',')
|
|
{
|
|
reader.Read();
|
|
tokens.Add(new Token(TokenType.COMMA, new string(new char[] { ch })));
|
|
}
|
|
else
|
|
{
|
|
throw new FormatException(String.Format("Unexpected character: {0}", (char)reader.Peek()));
|
|
}
|
|
}
|
|
private void ReadName(TextReader reader, List<Token> tokens)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
int ch = 0;
|
|
while (char.IsLetterOrDigit((char)(ch = reader.Peek())) || (ch == '_'))
|
|
{
|
|
sb.Append((char)reader.Read());
|
|
}
|
|
tokens.Add(new Token(TokenType.NAME, sb.ToString()));
|
|
}
|
|
private void ReadNumber(TextReader reader, List<Token> tokens)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
int ch = 0;
|
|
while (char.IsDigit((char)(ch = reader.Peek())) | (ch == '.'))
|
|
{
|
|
sb.Append((char)reader.Read());
|
|
}
|
|
tokens.Add(new Token(TokenType.NUMBER, sb.ToString()));
|
|
}
|
|
private void ReadString(TextReader reader, List<Token> tokens)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
int ch = 0;
|
|
reader.Read();
|
|
while ((ch = reader.Peek())!='"')
|
|
{
|
|
if (ch == '\\')
|
|
{
|
|
reader.Read();
|
|
char sch = (char)reader.Read();
|
|
switch (sch)
|
|
{
|
|
case 'n':
|
|
sb.Append('\n');
|
|
break;
|
|
case 'r':
|
|
sb.Append('\r');
|
|
break;
|
|
case 't':
|
|
sb.Append('\t');
|
|
break;
|
|
default:
|
|
sb.Append(sch);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.Append((char)reader.Read());
|
|
}
|
|
}
|
|
reader.Read();
|
|
|
|
tokens.Add(new Token(TokenType.STRING, sb.ToString()));
|
|
}
|
|
|
|
|
|
enum TokenType { NAME, DOT, NUMBER, STRING, OPENINDEXER, ENDINDEXER, OPENLIST, ENDLIST, COMMA }
|
|
class Token
|
|
{
|
|
public TokenType TokenType;
|
|
public string Value;
|
|
|
|
public Token(TokenType tokenType,String value)
|
|
{
|
|
this.TokenType = tokenType;
|
|
this.Value = value;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return String.Format("[Token TokenType={0:8} Value={1}]", TokenType, Value);
|
|
}
|
|
}
|
|
|
|
public class Context
|
|
{
|
|
public object This { get; }
|
|
public Dictionary<string,object> MappedValues { get; }
|
|
|
|
public Context(Context source)
|
|
: this(source.This, source.MappedValues)
|
|
{
|
|
}
|
|
|
|
public Context(object o,IEnumerable<KeyValuePair<string,object>> mappedValues)
|
|
{
|
|
this.This = o;
|
|
this.MappedValues = new Dictionary<string, object>();
|
|
|
|
if (mappedValues != null)
|
|
foreach (KeyValuePair<string, object> kvp in mappedValues)
|
|
MappedValues.Add(kvp.Key,kvp.Value);
|
|
}
|
|
|
|
public void AddMappedValue(string name, object value)
|
|
{
|
|
MappedValues.Add(name, value);
|
|
}
|
|
public void SetMappedValue(string name, object value)
|
|
{
|
|
MappedValues[name] = value;
|
|
}
|
|
public void RemoveMappedValue(string name)
|
|
{
|
|
MappedValues.Remove(name);
|
|
}
|
|
|
|
public object Evaluate(string name)
|
|
{
|
|
if (MappedValues.ContainsKey(name))
|
|
return MappedValues[name];
|
|
|
|
if (This != null)
|
|
{
|
|
FieldInfo fieldInfo = This.GetType().GetField(name);
|
|
if (fieldInfo != null)
|
|
{
|
|
return fieldInfo.GetValue(This);
|
|
}
|
|
PropertyInfo propertyInfo = This.GetType().GetProperty(name);
|
|
if (propertyInfo != null)
|
|
{
|
|
return propertyInfo.GetValue(This);
|
|
}
|
|
}
|
|
throw new KeyNotFoundException(name);
|
|
}
|
|
|
|
}
|
|
|
|
abstract class Eval
|
|
{
|
|
public Eval Parent { get; }
|
|
|
|
public Eval(Eval parent)
|
|
{
|
|
this.Parent = parent;
|
|
}
|
|
|
|
public abstract object Evaluate(Context context);
|
|
}
|
|
|
|
class Name : Eval
|
|
{
|
|
public string name;
|
|
|
|
public Name(Eval parent,String name)
|
|
:base(parent)
|
|
{
|
|
this.name = name;
|
|
}
|
|
|
|
public override object Evaluate(Context context)
|
|
{
|
|
if (Parent == null)
|
|
{
|
|
return context.Evaluate(name);
|
|
}
|
|
|
|
object p = Parent.Evaluate(context);
|
|
if (p != null)
|
|
{
|
|
FieldInfo fieldInfo = p.GetType().GetField(name);
|
|
if (fieldInfo != null)
|
|
{
|
|
return fieldInfo.GetValue(p);
|
|
}
|
|
PropertyInfo propertyInfo = p.GetType().GetProperty(name);
|
|
if (propertyInfo != null)
|
|
{
|
|
return propertyInfo.GetValue(p);
|
|
}
|
|
throw new KeyNotFoundException(name);
|
|
}
|
|
throw new NullReferenceException();
|
|
}
|
|
}
|
|
|
|
class Indexer : Eval
|
|
{
|
|
Eval index;
|
|
|
|
public Indexer(Eval parent,Eval index)
|
|
:base(parent)
|
|
{
|
|
this.index = index;
|
|
}
|
|
|
|
public override object Evaluate(Context context)
|
|
{
|
|
object i = index.Evaluate(context);
|
|
Type itype = i.GetType();
|
|
|
|
object p = Parent.Evaluate(context);
|
|
|
|
if (p is Array)
|
|
{
|
|
Array pa = p as Array;
|
|
return pa.GetValue((int)i);
|
|
}
|
|
|
|
foreach (PropertyInfo pi in p.GetType().GetProperties())
|
|
{
|
|
ParameterInfo[] infos = pi.GetIndexParameters();
|
|
if (infos.Length == 1)
|
|
{
|
|
if (infos[0].ParameterType.IsAssignableFrom(itype))
|
|
{
|
|
return pi.GetValue(p, new object[] { i });
|
|
}
|
|
}
|
|
}
|
|
throw new KeyNotFoundException();
|
|
}
|
|
}
|
|
|
|
class MethodCall : Eval
|
|
{
|
|
Eval[] Parameters;
|
|
String Name;
|
|
|
|
public MethodCall(Eval parent, String name,params Eval[] p)
|
|
: base(parent)
|
|
{
|
|
Name = name;
|
|
Parameters = p;
|
|
}
|
|
|
|
public override object Evaluate(Context context)
|
|
{
|
|
object p = Parent.Evaluate(context);
|
|
|
|
object[] pvalues = new object[Parameters.Length];
|
|
Type[] ptypes = new Type[Parameters.Length];
|
|
|
|
for (int i=0;i<pvalues.Length;i++)
|
|
{
|
|
pvalues[i] = Parameters[i].Evaluate(context);
|
|
if (pvalues[i] == null)
|
|
ptypes[i] = null;
|
|
else
|
|
ptypes[i] = pvalues[i].GetType();
|
|
}
|
|
|
|
MethodInfo methodInfo = p.GetType().GetRuntimeMethod(Name, ptypes);
|
|
|
|
return methodInfo.Invoke(p, pvalues);
|
|
}
|
|
}
|
|
|
|
class Number : Eval
|
|
{
|
|
object value;
|
|
|
|
public Number(String sourceValue)
|
|
: base(null)
|
|
{
|
|
try
|
|
{
|
|
value = int.Parse(sourceValue);
|
|
} catch
|
|
{
|
|
value = float.Parse(sourceValue);
|
|
}
|
|
}
|
|
|
|
public override object Evaluate(Context context)
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
class CString : Eval
|
|
{
|
|
string value;
|
|
|
|
public CString(String sourceValue)
|
|
: base(null)
|
|
{
|
|
this.value = sourceValue;
|
|
}
|
|
|
|
public override object Evaluate(Context context)
|
|
{
|
|
return this.value;
|
|
}
|
|
}
|
|
|
|
//private String read(TextReader reader,ConditionDelegate condition)
|
|
//{
|
|
// StringBuilder sb = new StringBuilder();
|
|
// int ch;
|
|
// while (((ch = reader.Peek())!=-1)&&condition((char)ch))
|
|
// {
|
|
// sb.Append((char)ch);
|
|
// }
|
|
// return sb.ToString();
|
|
//}
|
|
|
|
//private Token ReadToken(TextReader reader)
|
|
//{
|
|
// int ch = reader.Peek();
|
|
// if (char.IsLetter((char)ch))
|
|
// return ReadFieldName(reader);
|
|
|
|
// throw new FormatException();
|
|
//}
|
|
|
|
//private Token ReadPath(TextReader reader)
|
|
//{
|
|
// String read(reader, (ch) => char.IsLetterOrDigit(ch));
|
|
//}
|
|
|
|
//abstract class Token
|
|
//{
|
|
// public abstract object Evaluate();
|
|
//}
|
|
|
|
//class Field : Token
|
|
//{
|
|
// Token owner;
|
|
// String fieldName;
|
|
|
|
// public Field(Token owner,String fieldName)
|
|
// {
|
|
// this.owner = owner;
|
|
// this.fieldName = fieldName;
|
|
// }
|
|
|
|
// public override object Evaluate()
|
|
// {
|
|
// if (o == null)
|
|
// return null;
|
|
|
|
// Type t = o.GetType();
|
|
// FieldInfo fieldInfo = t.GetField(this.fieldName);
|
|
// if (fieldInfo != null)
|
|
// {
|
|
// return fieldInfo.GetValue(owner.Evaluate());
|
|
// }
|
|
// PropertyInfo propertyInfo = t.GetProperty(this.fieldName);
|
|
// if (propertyInfo != null)
|
|
// {
|
|
// return propertyInfo.GetValue(owner.Evaluate());
|
|
// }
|
|
// throw new MissingFieldException(t.FullName, this.fieldName);
|
|
// }
|
|
//}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|