Skip to content

Commit 1370bba

Browse files
committed
Created a basic expression language called MScript.
It supports 5 basic types: numbers, vectors (arrays of numbers), strings, functions and 'none' (the absence of a value). Standard operators are provided: unary: -, !, binary: +, -, *, /, %, ==, !=, >, >=, <, <=, tertiary: ?;, if-else. + performs string concatenation if one of the operands is a string. The other arithmetic operators only operate on numbers and/or vectors. == only returns true if both operands are of the same type, and have the same value. The comparison operators only work with numbers. Unlike null in most languages, 'none' can be used in most operations: it acts as the number 0 when the other operand is a number or vector, and it acts as an empty string if the other operand is a string (except for equality checks: none is only equal to none). Instead of having a separate boolean type, all types can be used in a boolean context: none, 0.0, [] and '' are false, anything else is true. Vectors and strings can be indexed. Negative indexes start at the end of the vector/string (-1 being the last element). Indexing returns none if the index is out-of-range. Because vectors are used almost exclusively to represent positions, rotation angles and colors/brightness, vectors have x/y/z, pitch/yaw/roll, r/g/b/brightness properties that act as shorthands for indexing with index 0, 1, 2 and 3 respectively (that is, x, pitch and r all return the first element).
1 parent c78f80a commit 1370bba

36 files changed

+1744
-0
lines changed

MESS.sln

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.28307.438
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MESS", "MESS\MESS.csproj", "{0E37B023-13DD-48FE-863D-554351916B16}"
77
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MScript", "MScript\MScript.csproj", "{2E0B469A-714B-4D7C-95A6-200D93EC08AE}"
9+
EndProject
810
Global
911
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1012
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
1517
{0E37B023-13DD-48FE-863D-554351916B16}.Debug|Any CPU.Build.0 = Debug|Any CPU
1618
{0E37B023-13DD-48FE-863D-554351916B16}.Release|Any CPU.ActiveCfg = Release|Any CPU
1719
{0E37B023-13DD-48FE-863D-554351916B16}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{2E0B469A-714B-4D7C-95A6-200D93EC08AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{2E0B469A-714B-4D7C-95A6-200D93EC08AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{2E0B469A-714B-4D7C-95A6-200D93EC08AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{2E0B469A-714B-4D7C-95A6-200D93EC08AE}.Release|Any CPU.Build.0 = Release|Any CPU
1824
EndGlobalSection
1925
GlobalSection(SolutionProperties) = preSolution
2026
HideSolutionNode = FALSE

MScript/Evaluation/BaseTypes.cs

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using MScript.Evaluation.Types;
2+
3+
namespace MScript.Evaluation
4+
{
5+
class BaseTypes
6+
{
7+
/// <summary>
8+
/// None represents the absence of a value.
9+
/// </summary>
10+
public static TypeDescriptor None { get; }
11+
12+
/// <summary>
13+
/// A floating-point double-precision number.
14+
/// </summary>
15+
public static TypeDescriptor Number { get; }
16+
17+
/// <summary>
18+
/// A fixed-length sequence of numbers.
19+
/// </summary>
20+
public static TypeDescriptor Vector { get; }
21+
22+
/// <summary>
23+
/// A sequence of characters.
24+
/// </summary>
25+
public static TypeDescriptor String { get; }
26+
27+
/// <summary>
28+
/// A function or method.
29+
/// </summary>
30+
public static TypeDescriptor Function { get; }
31+
32+
33+
static BaseTypes()
34+
{
35+
// TODO: Depending on how complex this will become, we may need to allow mutations during this initialization phase!
36+
37+
// NOTE: Initialization order is important here (earlier types can be referenced by later types):
38+
None = CreateNoneTypeDescriptor();
39+
Number = CreateNumberTypeDescriptor();
40+
Function = CreateFunctionTypeDescriptor();
41+
Vector = CreateVectorTypeDescriptor();
42+
String = CreateStringTypeDescriptor();
43+
}
44+
45+
46+
private static TypeDescriptor CreateNoneTypeDescriptor()
47+
{
48+
return new TypeDescriptor(nameof(None));
49+
}
50+
51+
private static TypeDescriptor CreateNumberTypeDescriptor()
52+
{
53+
return new TypeDescriptor(nameof(Number));
54+
}
55+
56+
private static TypeDescriptor CreateFunctionTypeDescriptor()
57+
{
58+
return new TypeDescriptor(nameof(Function));
59+
}
60+
61+
private static TypeDescriptor CreateVectorTypeDescriptor()
62+
{
63+
return new TypeDescriptor(nameof(Vector),
64+
new PropertyDescriptor("length", Number, obj => (obj as double[]).Length),
65+
66+
// Position properties:
67+
new PropertyDescriptor("x", Number, obj => Operations.Index(obj as double[], 0)),
68+
new PropertyDescriptor("y", Number, obj => Operations.Index(obj as double[], 1)),
69+
new PropertyDescriptor("z", Number, obj => Operations.Index(obj as double[], 2)),
70+
71+
// Angles properties:
72+
new PropertyDescriptor("pitch", Number, obj => Operations.Index(obj as double[], 0)),
73+
new PropertyDescriptor("yaw", Number, obj => Operations.Index(obj as double[], 1)),
74+
new PropertyDescriptor("roll", Number, obj => Operations.Index(obj as double[], 2)),
75+
76+
// Color + brightness properties:
77+
new PropertyDescriptor("r", Number, obj => Operations.Index(obj as double[], 0)),
78+
new PropertyDescriptor("g", Number, obj => Operations.Index(obj as double[], 1)),
79+
new PropertyDescriptor("b", Number, obj => Operations.Index(obj as double[], 2)),
80+
new PropertyDescriptor("brightness", Number, obj => Operations.Index(obj as double[], 3))
81+
);
82+
}
83+
84+
private static TypeDescriptor CreateStringTypeDescriptor()
85+
{
86+
return new TypeDescriptor(nameof(String),
87+
new PropertyDescriptor("length", Number, obj => (obj as string).Length)
88+
89+
// TODO: Add useful string methods here!
90+
);
91+
}
92+
}
93+
}

MScript/Evaluation/BoundMethod.cs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Linq;
2+
3+
namespace MScript.Evaluation
4+
{
5+
class BoundMethod : IFunction
6+
{
7+
public int ParameterCount => _function.ParameterCount - 1;
8+
9+
10+
private object _object;
11+
private IFunction _function;
12+
13+
14+
public BoundMethod(object @object, IFunction function)
15+
{
16+
_object = @object;
17+
_function = function;
18+
}
19+
20+
21+
public object Apply(object[] arguments, EvaluationContext context)
22+
{
23+
return _function.Apply(new[] { _object }.Concat(arguments).ToArray(), context);
24+
}
25+
}
26+
}

MScript/Evaluation/Evaluator.cs

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
using MScript.Evaluation.Types;
2+
using MScript.Parsing;
3+
using MScript.Parsing.AST;
4+
using System;
5+
using System.Linq;
6+
7+
namespace MScript.Evaluation
8+
{
9+
static class Evaluator
10+
{
11+
public static object Evaluate(Expression expression, EvaluationContext context)
12+
{
13+
switch (expression)
14+
{
15+
case NoneLiteral noneLiteral: return null;
16+
case NumberLiteral numberLiteral: return numberLiteral.Value;
17+
case StringLiteral stringLiteral: return stringLiteral.Value;
18+
case VectorLiteral vectorLiteral: return EvaluateVectorLiteral(vectorLiteral, context);
19+
case Variable variable: return context.Resolve(variable.Name);
20+
case FunctionCall functionCall: return EvaluateFunctionCall(functionCall, context);
21+
case Indexing indexing: return EvaluateIndexing(indexing, context);
22+
case MemberAccess memberAccess: return EvaluateMemberAccess(memberAccess, context);
23+
case BinaryOperation binaryOperation: return EvaluateBinaryOperation(binaryOperation, context);
24+
case UnaryOperation unaryOperation: return EvaluateUnaryOperation(unaryOperation, context);
25+
case ConditionalOperation conditionalOperation: return EvaluateConditionalOperation(conditionalOperation, context);
26+
27+
default: throw new InvalidOperationException($"Unknown expression type: {expression}.");
28+
}
29+
}
30+
31+
32+
private static object EvaluateVectorLiteral(VectorLiteral vectorLiteral, EvaluationContext context)
33+
{
34+
var vector = new double[vectorLiteral.Elements.Count];
35+
for (int i = 0; i < vectorLiteral.Elements.Count; i++)
36+
{
37+
if (!((Evaluate(vectorLiteral.Elements[i], context) ?? 0.0) is double number))
38+
throw new InvalidOperationException("Vectors can only contain numbers.");
39+
40+
vector[i] = number;
41+
}
42+
return vector;
43+
}
44+
45+
private static object EvaluateFunctionCall(FunctionCall functionCall, EvaluationContext context)
46+
{
47+
if (!(Evaluate(functionCall.Function, context) is IFunction function))
48+
throw new InvalidOperationException($"Function call requires a function.");
49+
50+
var arguments = functionCall.Arguments.Select(argument => Evaluate(argument, context)).ToArray();
51+
return function.Apply(arguments, context);
52+
}
53+
54+
private static object EvaluateIndexing(Indexing indexing, EvaluationContext context)
55+
{
56+
var indexable = Evaluate(indexing.Indexable, context);
57+
if (indexable is double[] || indexable is string)
58+
{
59+
if (!((Evaluate(indexing.Index, context) ?? 0.0) is double index))
60+
throw new InvalidOperationException($"Indexing requires a numeric index.");
61+
62+
if (indexable is double[] vector)
63+
return Operations.Index(vector, (int)index);
64+
else if (indexable is string @string)
65+
return Operations.Index(@string, (int)index);
66+
}
67+
68+
throw new InvalidOperationException($"{indexable} cannot be indexed.");
69+
}
70+
71+
private static object EvaluateMemberAccess(MemberAccess memberAccess, EvaluationContext context)
72+
{
73+
var @object = Evaluate(memberAccess.Object, context);
74+
var type = TypeDescriptor.GetType(@object);
75+
var member = type.GetMember(memberAccess.MemberName);
76+
if (member is null)
77+
throw new InvalidOperationException($"{@object} does not have a member named '{memberAccess.MemberName}'.");
78+
79+
return member.GetValue(@object);
80+
}
81+
82+
private static object EvaluateBinaryOperation(BinaryOperation binaryOperation, EvaluationContext context)
83+
{
84+
switch (binaryOperation.Operator)
85+
{
86+
case BinaryOperator.And: return Operations.And(binaryOperation.LeftOperand, binaryOperation.RightOperand, context);
87+
case BinaryOperator.Or: return Operations.Or(binaryOperation.LeftOperand, binaryOperation.RightOperand, context);
88+
}
89+
90+
var leftOperand = Evaluate(binaryOperation.LeftOperand, context);
91+
var rightOperand = Evaluate(binaryOperation.RightOperand, context);
92+
switch (binaryOperation.Operator)
93+
{
94+
case BinaryOperator.Add: return Operations.Add(leftOperand, rightOperand);
95+
case BinaryOperator.Subtract: return Operations.Subtract(leftOperand, rightOperand);
96+
case BinaryOperator.Multiply: return Operations.Multiply(leftOperand, rightOperand);
97+
case BinaryOperator.Divide: return Operations.Divide(leftOperand, rightOperand);
98+
case BinaryOperator.Remainder: return Operations.Remainder(leftOperand, rightOperand);
99+
case BinaryOperator.Equals: return Operations.Equals(leftOperand, rightOperand);
100+
case BinaryOperator.NotEquals: return Operations.NotEquals(leftOperand, rightOperand);
101+
case BinaryOperator.GreaterThan: return Operations.GreaterThan(leftOperand, rightOperand);
102+
case BinaryOperator.GreaterThanOrEqual: return Operations.GreaterThanOrEqual(leftOperand, rightOperand);
103+
case BinaryOperator.LessThan: return Operations.LessThan(leftOperand, rightOperand);
104+
case BinaryOperator.LessThanOrEqual: return Operations.LessThanOrEqual(leftOperand, rightOperand);
105+
default: throw new InvalidOperationException($"Unknown operator: {binaryOperation.Operator}.");
106+
}
107+
}
108+
109+
private static object EvaluateUnaryOperation(UnaryOperation unaryOperation, EvaluationContext context)
110+
{
111+
var operand = Evaluate(unaryOperation.Operand, context);
112+
switch (unaryOperation.Operator)
113+
{
114+
case UnaryOperator.Negate: return Operations.Negate(operand);
115+
case UnaryOperator.LogicalNegate: return Operations.LogicalNegate(operand);
116+
default: throw new InvalidOperationException($"Unknown operator: {unaryOperation.Operator}.");
117+
}
118+
}
119+
120+
private static object EvaluateConditionalOperation(ConditionalOperation conditionalOperation, EvaluationContext context)
121+
{
122+
return Operations.Conditional(
123+
conditionalOperation.Condition,
124+
conditionalOperation.TrueExpression,
125+
conditionalOperation.FalseExpression,
126+
context);
127+
}
128+
}
129+
}

MScript/Evaluation/IFunction.cs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+

2+
namespace MScript.Evaluation
3+
{
4+
interface IFunction
5+
{
6+
int ParameterCount { get; }
7+
8+
9+
object Apply(object[] arguments, EvaluationContext context);
10+
}
11+
}

MScript/Evaluation/NativeFunction.cs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
3+
namespace MScript.Evaluation
4+
{
5+
class NativeFunction : IFunction
6+
{
7+
public int ParameterCount { get; }
8+
9+
10+
private Func<object[], EvaluationContext, object> _func;
11+
12+
13+
public NativeFunction(int parameterCount, Func<object[], EvaluationContext, object> func)
14+
{
15+
ParameterCount = parameterCount;
16+
_func = func;
17+
}
18+
19+
public object Apply(object[] arguments, EvaluationContext context)
20+
{
21+
if (arguments == null || arguments.Length != ParameterCount)
22+
throw new InvalidOperationException($"Invalid parameter count: expected {ParameterCount} but got {arguments?.Length}.");
23+
24+
return _func(arguments, context);
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)