/// <summary> /// Creates a rule - metadata that allows invocation of a function on /// </summary> private void RuleDeclaration() { if (_EnclosingCompiler != null) { throw new CompilerException(Tokens.Peek(), "Rules must be globally scoped."); } Token triggerName = Tokens.Consume(IDENTIFIER, "Rule declarations must begin with a named trigger."); List <RuleCondition> conditions = new List <RuleCondition>(); // rule conditions while (!Tokens.Match(RIGHT_BRACKET)) { Token contextVariableName = Tokens.Consume(IDENTIFIER, "Rule must contain list of comparison expressions (missing identifier)."); Token comparisonOperation = Tokens.Advance(); // we will check validity of this token after consuming the value Token contextVariableValue = Tokens.Consume(NUMBER, "Rule must contain list of comparison expressions (missing value)"); switch (comparisonOperation.Type) { case BANG_EQUAL: // conditions.Add(RuleCondition.ConditionNotEquals(contextVariableName.Lexeme, contextVariableValue.LiteralAsNumber)); throw new CompilerException(Tokens.Peek(), "Rule must contain list of comparison expressions (can't use != operator)."); case EQUAL: case EQUAL_EQUAL: conditions.Add(RuleCondition.ConditionEquals(contextVariableName.Lexeme, contextVariableValue.LiteralAsNumber)); break; case GREATER: conditions.Add(RuleCondition.ConditionGreaterThan(contextVariableName.Lexeme, contextVariableValue.LiteralAsNumber)); break; case GREATER_EQUAL: conditions.Add(RuleCondition.ConditionGreaterThanOrEqual(contextVariableName.Lexeme, contextVariableValue.LiteralAsNumber)); break; case LESS: conditions.Add(RuleCondition.ConditionLessThan(contextVariableName.Lexeme, contextVariableValue.LiteralAsNumber)); break; case LESS_EQUAL: conditions.Add(RuleCondition.ConditionLessThanOrEqual(contextVariableName.Lexeme, contextVariableValue.LiteralAsNumber)); break; default: throw new CompilerException(Tokens.Peek(), "Rule must contain list of comparison expressions (missing operator)."); } } if (Tokens.Peek().Type == FUNCTION && Tokens.Peek(1).Type == IDENTIFIER) { string functionName = Tokens.Peek(1).Lexeme; _Rules.Add(new Rule(BitString.GetBitStr(triggerName.Lexeme), BitString.GetBitStr(functionName), conditions.ToArray())); } else { throw new CompilerException(Tokens.Peek(), "Rule declaration must be followed by function."); } }
private GearsNativeWrapper(Type wrappedType, bool wrapAllPublicFields = false) { WrappedType = wrappedType; BindingFlags binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; FieldInfo[] fields = wrappedType.GetFields(binding); foreach (FieldInfo info in fields) { LoxFieldAttribute attr = info.GetCustomAttribute <LoxFieldAttribute>(); if (!wrapAllPublicFields && attr == null) { continue; } ulong name = BitString.GetBitStr(attr?.Name ?? info.Name); if (NameExists(name)) { Tracer.Warn($"GearsNativeWrapper: {wrappedType.Name}.{info.Name} is masked by a field, method, or property named '{BitString.GetBitStr(name)}'."); continue; } _Fields.Add(name, info); } MethodInfo[] methods = wrappedType.GetMethods(binding).Where(d => !d.IsSpecialName).ToArray(); foreach (MethodInfo info in methods) { LoxFieldAttribute attr = info.GetCustomAttribute <LoxFieldAttribute>(); if (!wrapAllPublicFields && attr == null) { continue; } ulong name = BitString.GetBitStr(attr?.Name ?? info.Name); if (NameExists(name)) { Tracer.Warn($"GearsNativeWrapper: {wrappedType.Name}.{info.Name} is masked by a field, method, or property named '{BitString.GetBitStr(name)}'."); continue; } _Methods.Add(name, info); } PropertyInfo[] properties = wrappedType.GetProperties(binding); foreach (PropertyInfo info in properties) { LoxFieldAttribute attr = info.GetCustomAttribute <LoxFieldAttribute>(); if (!wrapAllPublicFields && attr == null) { continue; } ulong name = BitString.GetBitStr(attr?.Name ?? info.Name); if (NameExists(name)) { Tracer.Warn($"GearsNativeWrapper: {wrappedType.Name}.{info.Name} is masked by a field, method, or property named '{BitString.GetBitStr(name)}'."); continue; } _Properties.Add(name, info); } }
// === Rules ================================================================================================= // =========================================================================================================== internal IEnumerable <string> GetRuleMatches(string triggerName, RuleInvocationContext context) { ulong triggerBitString = BitString.GetBitStr(triggerName); for (int i = 0; i < Rules.Length; i++) { if (Rules[i].IsTrue(triggerBitString, context)) { yield return(BitString.GetBitStr(Rules[i].Result)); } } }
private int MakeBitStrConstant(string value) { ulong bitstr = BitString.GetBitStr(value); if (_TempOptimizedStrings.TryGetValue(bitstr, out string optimized)) { if (optimized != value) { throw new CompilerException(Tokens.Previous(), $"String collision: '{value}' != '{optimized}'. First 10 characters of all identifiers must be distinct."); } } _TempOptimizedStrings[bitstr] = value; return(MakeValueConstant(bitstr)); }
internal partial class Gears { // support for native c# calling lox script functions. /// <summary> /// Calls a LoxScript function from native code, passing arguments. /// If the function call was successful, returns true, and returnValue will be the returned value from the function, if any. /// If the function call was not successful, returns false, and returnValue will be an error string. /// </summary> internal bool CallGearsFunction(string fnName, out object returned, params object[] args) { ulong name = BitString.GetBitStr(fnName); return(CallGearsFunction(name, out returned, args)); }
internal bool CallGearsFunction(ulong name, out object returned, params object[] args) { if (!Globals.TryGet(name, out GearsValue fnValue) || !fnValue.IsObjPtr) { // error: no function with that name. returned = $"Error: no function with name '{BitString.GetBitStr(name)}'."; return(false); } GearsObj fnObject = fnValue.AsObject(this); if (fnObject is GearsObjFunction fnFunction) { if (fnFunction.Arity != args.Length) { // error: wrong arity. returned = $"Error: called '{BitString.GetBitStr(name)}' with wrong arity (passed arity is '{args?.Length ?? 0}')."; return(false); } } Push(fnValue); for (int i = 0; i < (args?.Length ?? 0); i++) { object arg = args[i]; Type argType = arg?.GetType() ?? null; if (arg == null) { Push(GearsValue.NilValue); } else if (GearsNativeWrapper.IsNumeric(argType)) { double fieldValue = Convert.ToDouble(arg); Push(new GearsValue(fieldValue)); } else if (argType == typeof(bool)) { bool fieldValue = Convert.ToBoolean(arg); Push(fieldValue ? GearsValue.TrueValue : GearsValue.FalseValue); } else if (argType == typeof(string)) { string fieldValue = Convert.ToString(arg); if (fieldValue == null) { Push(GearsValue.NilValue); } else { Push(GearsValue.CreateObjPtr(HeapAddObject(new GearsObjString(fieldValue)))); } } else if (argType.IsSubclassOf(typeof(object))) { if (arg == null) { Push(GearsValue.NilValue); } else { Push(GearsValue.CreateObjPtr(HeapAddObject(new GearsObjInstanceNative(this, arg)))); } } else { // error: could not pass arg of this type returned = $"Error: called '{BitString.GetBitStr(name)}' with argument of type '{argType.Name}' as parameter {i}. Gears could not interpret this argument."; return(false); } } Call(args?.Length ?? 0); Run(); returned = LastReturnValue; // the return value // todo: process return value? return(true); }
internal string ReadConstantValueAsBitStr(int offset) { GearsValue value = ReadConstantValue(offset); return(BitString.GetBitStr((ulong)value)); }
// --- Can probably merge a ton of code from the three call methods --- private void CallInvoke() { int argCount = ReadByte(); ulong methodName = (ulong)ReadConstant(); GearsValue receiverPtr = Peek(argCount); if (!receiverPtr.IsObjPtr) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Attempted invoke to non-pointer."); } GearsObj obj = receiverPtr.AsObject(this); if (obj is GearsObjInstance instance) { if (instance.TryGetField(methodName, out GearsValue value)) { if (!value.IsObjPtr) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Attempted call to non-pointer."); } GearsObj objFn = HeapGetObject(value.AsObjPtr); if (objFn is GearsObjFunction function) { if (function.Arity != argCount) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"{function} expects {function.Arity} arguments but was passed {argCount}."); } int ip = function.IP; int bp = _SP - (function.Arity + 1); PushFrame(new GearsCallFrame(function, ip, bp)); } else if (objFn is GearsObjFunctionNative native) { if (native.Arity != argCount) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"{native} expects {native.Arity} arguments but was passed {argCount}."); } GearsValue[] args = new GearsValue[argCount]; for (int i = argCount - 1; i >= 0; i--) { args[i] = Pop(); } Pop(); // pop the function signature Push(native.Invoke(args)); } else { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"Could not resolve method {methodName} in {instance}."); } } else if (instance is GearsObjInstanceLox instanceLox) { InvokeFromClass(argCount, methodName, receiverPtr, instanceLox.Class); } else { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"{instance} does not have a public method named '{BitString.GetBitStr(methodName)}'."); } return; } throw new GearsRuntimeException(Chunk.LineAt(_IP), "Attempted invoke to non-instance."); }
/// <summary> /// Runs the script from top to bottom. /// </summary> private bool RunOne() { LastReturnValue = GearsValue.NilValue; while (true) { EGearsOpCode instruction = (EGearsOpCode)ReadByte(); switch (instruction) { case OP_LOAD_CONSTANT: Push(ReadConstant()); break; case OP_LOAD_STRING: Push(GearsValue.CreateObjPtr(HeapAddObject(new GearsObjString(ReadConstantString())))); break; case OP_LOAD_FUNCTION: { int arity = ReadByte(); int address = ReadShort(); int upvalueCount = ReadByte(); GearsObjFunction closure = new GearsObjFunction(Chunk, arity, upvalueCount, address); for (int i = 0; i < upvalueCount; i++) { bool isLocal = ReadByte() == 1; int index = ReadByte(); if (isLocal) { int location = _OpenFrame.BP + index; closure.Upvalues[i] = CaptureUpvalue(location); } else { closure.Upvalues[i] = _OpenFrame.Function.Upvalues[index]; } } Push(GearsValue.CreateObjPtr(HeapAddObject(closure))); } break; case OP_NIL: Push(GearsValue.NilValue); break; case OP_TRUE: Push(GearsValue.TrueValue); break; case OP_FALSE: Push(GearsValue.FalseValue); break; case OP_POP: Pop(); break; case OP_GET_LOCAL: { int slot = ReadShort(); Push(StackGet(slot + _BP)); } break; case OP_SET_LOCAL: { int slot = ReadShort(); StackSet(slot + _BP, Peek()); } break; case OP_GET_GLOBAL: { ulong name = (ulong)ReadConstant(); if (!Globals.TryGet(name, out GearsValue value)) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"Undefined variable '{BitString.GetBitStr(name)}'."); } Push(value); } break; case OP_DEFINE_GLOBAL: { ulong name = (ulong)ReadConstant(); Globals.Set(name, Peek()); Pop(); } break; case OP_SET_GLOBAL: { ulong name = (ulong)ReadConstant(); if (!Globals.ContainsKey(name)) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"Undefined variable '{BitString.GetBitStr(name)}'."); } Globals.Set(name, Peek()); break; } case OP_GET_UPVALUE: { int slot = ReadShort(); GearsObjUpvalue upvalue = _OpenFrame.Function.Upvalues[slot]; if (upvalue.IsClosed) { Push(upvalue.Value); } else { Push(StackGet(upvalue.OriginalSP)); } } break; case OP_SET_UPVALUE: { int slot = ReadShort(); GearsObjUpvalue upvalue = _OpenFrame.Function.Upvalues[slot]; if (upvalue.IsClosed) { upvalue.Value = Peek(); } else { StackSet(upvalue.OriginalSP, Peek()); } } break; case OP_GET_PROPERTY: { GearsObjInstance instance = GetObjectFromPtr <GearsObjInstance>(Peek()); ulong name = (ulong)ReadConstant(); // property name if (instance.TryGetField(name, out GearsValue value)) { Pop(); // instance Push(value); // property value break; } if (instance is GearsObjInstanceLox loxInstance && BindLoxMethod(loxInstance.Class, name)) { break; } throw new GearsRuntimeException(Chunk.LineAt(_IP), $"Undefined property or method '{name}'."); } case OP_SET_PROPERTY: { GearsObjInstance instance = GetObjectFromPtr <GearsObjInstance>(Peek(1)); ulong name = (ulong)ReadConstant(); // property name GearsValue value = Pop(); // value instance.SetField(name, value); Pop(); // ptr Push(value); // value } break; case OP_GET_SUPER: { ulong name = (ulong)ReadConstant(); // method/property name GearsObjClass superclass = GetObjectFromPtr <GearsObjClass>(Pop()); if (!BindLoxMethod(superclass, name)) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"Could not get {name} in superclass {superclass}"); } } break; case OP_EQUAL: Push(AreValuesEqual(Pop(), Pop())); break; case OP_GREATER: { if (!Peek(0).IsNumber || !Peek(1).IsNumber) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operands must be numbers."); } GearsValue b = Pop(); GearsValue a = Pop(); Push(a > b); } break; case OP_LESS: { if (!Peek(0).IsNumber || !Peek(1).IsNumber) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operands must be numbers."); } GearsValue b = Pop(); GearsValue a = Pop(); Push(a < b); } break; case OP_BITWISE_AND: { if (!Peek(0).IsNumber || !Peek(1).IsNumber) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operands of bitwise operators must be numbers."); } GearsValue b = Pop(); GearsValue a = Pop(); Push((long)((double)a) & (long)((double)b)); } break; case OP_BITWISE_OR: { if (!Peek(0).IsNumber || !Peek(1).IsNumber) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operands of bitwise operators must be numbers."); } GearsValue b = Pop(); GearsValue a = Pop(); Push((long)((double)a) | (long)((double)b)); } break; case OP_ADD: { GearsValue b = Pop(); GearsValue a = Pop(); if (a.IsNumber && b.IsNumber) { Push(a + b); } else if (a.IsObjType <GearsObjString>(this) && b.IsObjType <GearsObjString>(this)) { string sa = GetObjectFromPtr <GearsObjString>(a).Value; string sb = GetObjectFromPtr <GearsObjString>(b).Value; Push(GearsValue.CreateObjPtr(HeapAddObject(new GearsObjString(sa + sb)))); } else { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operands of add must be numbers or strings."); } } break; case OP_SUBTRACT: { if (!Peek(0).IsNumber || !Peek(1).IsNumber) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operands of subtract must be numbers."); } GearsValue b = Pop(); GearsValue a = Pop(); Push(a - b); } break; case OP_MULTIPLY: { if (!Peek(0).IsNumber || !Peek(1).IsNumber) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operands of multiply must be numbers."); } GearsValue b = Pop(); GearsValue a = Pop(); Push(a * b); } break; case OP_DIVIDE: { if (!Peek(0).IsNumber || !Peek(1).IsNumber) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operands of divide must be numbers."); } GearsValue b = Pop(); GearsValue a = Pop(); Push(a / b); } break; case OP_NOT: Push(IsFalsey(Pop())); break; case OP_NEGATE: { if (!Peek(0).IsNumber) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Operand of negate must be a number."); } Push(-Pop()); } break; case OP_JUMP: { int offset = ReadShort(); ModIP(offset); } break; case OP_JUMP_IF_FALSE: { int offset = ReadShort(); if (IsFalsey(Peek())) { ModIP(offset); } } break; case OP_LOOP: { int offset = ReadShort(); ModIP(-offset); } break; case OP_CALL: { Call(argCount: ReadByte()); } break; case OP_INVOKE: { CallInvoke(); } break; case OP_SUPER_INVOKE: { CallInvokeSuper(); } break; case OP_CLOSE_UPVALUE: CloseUpvalues(_SP - 1); Pop(); break; case OP_RETURN: { GearsValue result = Pop(); CloseUpvalues(_OpenFrame.BP); if (PopFrame()) { if (_SP != 0) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"Error after final return: SP is '{_SP}', not '0'."); } LastReturnValue = result; _IP = Chunk.SizeCode; // code is complete and no longer running. return(true); } Push(result); } break; case OP_CLASS: { Push(GearsValue.CreateObjPtr(HeapAddObject(new GearsObjClass(ReadConstantString())))); } break; case OP_INHERIT: { if (!Peek(0).IsObjType <GearsObjClass>(this)) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Superclass is not a class."); } GearsObjClass super = GetObjectFromPtr <GearsObjClass>(Peek(1)); GearsObjClass sub = GetObjectFromPtr <GearsObjClass>(Peek(0)); foreach (ulong key in super.Methods.AllKeys) { if (!super.Methods.TryGet(key, out GearsValue methodPtr)) { throw new GearsRuntimeException(Chunk.LineAt(_IP), "Could not copy superclass method table."); } sub.Methods.Set(key, methodPtr); } Pop(); // pop subclass } break; case OP_METHOD: { DefineMethod(); } break; default: throw new GearsRuntimeException(Chunk.LineAt(_IP), $"Unknown opcode {instruction}"); } } }
internal static RuleCondition ConditionGreaterThanOrEqual(string varName, double value) { ulong varname = BitString.GetBitStr(varName); return(new RuleCondition(varname, value, float.MaxValue)); }
/*internal static RuleCondition ConditionNotEquals(string varName, double value) { * ulong varname = BitString.GetBitStr(varName); * return new RuleCondition(varname, value - Epsilon * 2, value + Epsilon * 2); * }*/ internal static RuleCondition ConditionLessThan(string varName, double value) { ulong varname = BitString.GetBitStr(varName); return(new RuleCondition(varname, float.MinValue, value - Epsilon * 2)); }
internal static RuleCondition ConditionEquals(string varName, double value) { ulong varname = BitString.GetBitStr(varName); return(new RuleCondition(varname, value, value)); }
internal void AddNativeObjectToGlobals(string name, object obj) { Globals.Set(BitString.GetBitStr(name), GearsValue.CreateObjPtr(HeapAddObject(new GearsObjInstanceNative(this, obj)))); }
/// <summary> /// Defines a function that can be called by scripts. /// Arity is the number of arguments expected. /// </summary> internal void AddNativeFunctionToGlobals(string name, int arity, GearsFunctionNativeDelegate onInvoke) { Globals.Set(BitString.GetBitStr(name), GearsValue.CreateObjPtr(HeapAddObject(new GearsObjFunctionNative(name, arity, onInvoke)))); }
public override string ToString() => $"[{BitString.GetBitStr(Trigger)} ...] => {Result}()";
public void SetField(Gears context, object wrappedObject, ulong name, GearsValue value) { if (_Fields.TryGetValue(name, out FieldInfo fieldInfo)) { if (value.IsNumber) { if (!IsNumeric(fieldInfo.FieldType)) { throw new GearsRuntimeException($"Attempted to set {WrappedType.Name}.{fieldInfo.Name} to numeric value."); } try { fieldInfo.SetValue(wrappedObject, Convert.ChangeType((double)value, fieldInfo.FieldType)); return; } catch (Exception e) { throw new GearsRuntimeException($"Error setting {WrappedType.Name}.{fieldInfo.Name} to {(double)value}: {e.Message}"); } } else if (value.IsNil && fieldInfo.FieldType == typeof(string)) { fieldInfo.SetValue(wrappedObject, null); return; } else if (fieldInfo.FieldType == typeof(bool) && value.IsBool) { fieldInfo.SetValue(wrappedObject, value.IsTrue); return; } else if (value.IsObjPtr) { GearsObj obj = value.AsObject(context); if (fieldInfo.FieldType == typeof(string) && obj is GearsObjString objString) { fieldInfo.SetValue(wrappedObject, objString.Value); return; } } } else if (_Properties.TryGetValue(name, out PropertyInfo propertyInfo)) { if (!propertyInfo.GetSetMethod().IsPublic) { throw new GearsRuntimeException($"Unsupported reference: Native class {WrappedType.Name} does not have a public set method for '{BitString.GetBitStr(name)}'."); } if (value.IsNumber) { if (!IsNumeric(propertyInfo.PropertyType)) { throw new GearsRuntimeException($"Attempted to set {WrappedType.Name}.{propertyInfo.Name} to numeric value."); } try { propertyInfo.SetValue(wrappedObject, Convert.ChangeType((double)value, propertyInfo.PropertyType), null); return; } catch (Exception e) { throw new GearsRuntimeException($"Error setting {WrappedType.Name}.{propertyInfo.Name} to {(double)value}: {e.Message}"); } } else if (value.IsNil && propertyInfo.PropertyType == typeof(string)) { propertyInfo.SetValue(wrappedObject, null, null); return; } else if (propertyInfo.PropertyType == typeof(bool) && value.IsBool) { propertyInfo.SetValue(wrappedObject, value.IsTrue, null); return; } else if (value.IsObjPtr) { GearsObj obj = value.AsObject(context); if (propertyInfo.PropertyType == typeof(string) && obj is GearsObjString objString) { propertyInfo.SetValue(wrappedObject, objString.Value, null); return; } } } throw new GearsRuntimeException($"Unsupported native conversion: Error setting {WrappedType.Name}.{BitString.GetBitStr(name)} to {value}."); }
internal RuleInvocationContext AddContext(string varName, double value) { _Variables[BitString.GetBitStr(varName)] = value; return(this); }
private void InvokeFromClass(int argCount, ulong methodName, GearsValue receiverPtr, GearsObjClass objClass) { if (!objClass.Methods.TryGet(methodName, out GearsValue methodPtr)) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"{objClass} has no method with name '{BitString.GetBitStr(methodName)}'."); } if ((!methodPtr.IsObjPtr) || !(HeapGetObject(methodPtr.AsObjPtr) is GearsObjFunction method)) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"Could not resolve method '{BitString.GetBitStr(methodName)}' in class {objClass}."); } if (method.Arity != argCount) { throw new GearsRuntimeException(Chunk.LineAt(_IP), $"{method} expects {method.Arity} arguments but was passed {argCount}."); } int ip = method.IP; int bp = _SP - (method.Arity + 1); if (!receiverPtr.IsNil) { StackSet(bp, receiverPtr); // todo: this wipes out the method object. Is this bad? } PushFrame(new GearsCallFrame(method, ip, bp)); }