ln.templates/Expression.cs

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 e)
{
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);
// }
//}
}
}