private SObject ExecuteAssignment(ScriptStatement statement) { var exp = statement.Code; var leftSide = ""; var rightSide = ""; var assignmentOperator = ""; // Get left and right side of the assignment: { var depth = 0; var index = 0; StringEscapeHelper escaper = new LeftToRightStringEscapeHelper(exp, 0); while (index < exp.Length && assignmentOperator.Length == 0) { var t = exp[index]; escaper.CheckStartAt(index); if (!escaper.IsString) { if (t == '(' || t == '[' || t == '{') { depth++; } else if (t == ')' || t == ']' || t == '}') { depth--; } else if (t == '=' && depth == 0) { var previous = ' '; if (index > 0) { previous = exp[index - 1]; } if (previous == '+' || previous == '-' || previous == '/' || previous == '*') { assignmentOperator = previous.ToString(); leftSide = exp.Substring(0, index - 1).TrimEnd(); } else { assignmentOperator = "="; leftSide = exp.Substring(0, index).TrimEnd(); } rightSide = exp.Substring(index + 1).TrimStart(); } } index++; } } // This means it's a function call, which cannot be assigned to: if (leftSide.EndsWith(")") || leftSide.Length == 0) { return(ErrorHandler.ThrowError(ErrorType.ReferenceError, ErrorHandler.MessageReferenceInvalidAssignmentLeft)); } var isIndexer = false; var host = ""; var member = ""; if (leftSide.EndsWith("]")) { var indexerStartIndex = 0; var index = leftSide.Length - 1; var depth = 0; StringEscapeHelper escaper = new RightToLeftStringEscapeHelper(leftSide, index); while (index > 0 && !isIndexer) { var t = leftSide[index]; escaper.CheckStartAt(index); if (!escaper.IsString) { if (t == '(' || t == '{') { depth--; } else if (t == ')' || t == ']' || t == '}') { depth++; } else if (t == '[') { depth--; if (depth == 0 && index > 0) { isIndexer = true; indexerStartIndex = index; } } } index--; } if (isIndexer) { member = leftSide.Substring(indexerStartIndex + 1); member = member.Remove(member.Length - 1, 1); host = leftSide.Remove(indexerStartIndex); } } else { var foundMember = false; if (leftSide.Contains(".")) { var index = leftSide.Length - 1; var depth = 0; StringEscapeHelper escaper = new RightToLeftStringEscapeHelper(leftSide, index); while (index > 0 && !foundMember) { var t = leftSide[index]; escaper.CheckStartAt(index); if (!escaper.IsString) { if (t == '(' || t == '[' || t == '{') { depth--; } else if (t == ')' || t == ']' || t == '}') { depth++; } else if (t == '.' && depth == 0) { foundMember = true; host = leftSide.Substring(0, index); member = leftSide.Remove(0, index + 1); } } index--; } } if (!foundMember) { host = SObject.LiteralThis; member = leftSide; } } // When it's an indexer, we parse it as statement: var accessor = isIndexer ? SObject.Unbox(ExecuteStatement(new ScriptStatement(member))) : CreateString(member); var memberHost = ExecuteStatement(new ScriptStatement(host)); var value = SObject.Unbox(ExecuteStatement(new ScriptStatement(rightSide))); if (assignmentOperator == "=") { memberHost.SetMember(this, accessor, isIndexer, value); } else { var memberContent = memberHost.GetMember(this, accessor, isIndexer); var result = ""; switch (assignmentOperator) { case "+": result = ObjectOperators.AddOperator(this, memberContent, value); break; case "-": result = ObjectOperators.SubtractOperator(this, memberContent, value); break; case "*": result = ObjectOperators.MultiplyOperator(this, memberContent, value); break; case "/": result = ObjectOperators.DivideOperator(this, memberContent, value); break; } memberHost.SetMember(this, accessor, isIndexer, ToScriptObject(result)); } return(value); }
/// <summary> /// Captures an element left from the starting index. /// </summary> private static ElementCapture CaptureLeft(string exp, int index) { if (string.IsNullOrWhiteSpace(exp)) { return new ElementCapture() { Length = 0, StartIndex = 0, Identifier = "", Depth = 0 } } ; var identifier = ""; var foundSeparatorChar = false; var depth = 0; StringEscapeHelper escaper = new RightToLeftStringEscapeHelper(exp, index); while (index >= 0 && !foundSeparatorChar) { var t = exp[index]; escaper.CheckStartAt(index); if (!escaper.IsString) { if (t == ')' || t == ']' || t == '}') { depth++; } else if (t == '(' || t == '[' || t == '{') { depth--; } } if (depth < 0) { // this is when we walk out of the capture area because we are inside some area and a )]} appeared. foundSeparatorChar = true; } else { if (!escaper.IsString && depth == 0) { if (t == '.') { // Check if the '.' is not a decimal separator: if (!Regex.IsMatch(identifier.Trim(), RegexNumrightdot)) { foundSeparatorChar = true; } } else if (IdentifierSeparators.Contains(t)) { foundSeparatorChar = true; } } // Append the char to the identifier: if (!foundSeparatorChar) { identifier = t + identifier; } } index--; } // Check for a minus in front of the identifier to indicate a negative number: if (index >= -1 && exp[index + 1] == '-' && !string.IsNullOrWhiteSpace(identifier)) { identifier = "-" + identifier; index--; } if (foundSeparatorChar) { return new ElementCapture() { StartIndex = index + 2, Length = identifier.Length, Identifier = identifier, Depth = depth } } ; else { return new ElementCapture() { StartIndex = index + 1, Length = identifier.Length, Identifier = identifier, Depth = depth } }; }
private SObject InvokeMethod(SObject owner, string methodName) { var exp = methodName; var index = exp.Length - 1; var argumentStartIndex = -1; var This = owner; if (exp.EndsWith("()")) { argumentStartIndex = exp.Length - 2; index = argumentStartIndex - 1; } else { var depth = 0; var foundArguments = false; StringEscapeHelper escaper = new RightToLeftStringEscapeHelper(exp, index); while (index > 0 && !foundArguments) { var t = exp[index]; escaper.CheckStartAt(index); if (!escaper.IsString) { if (t == ')' || t == '}' || t == ']') { depth++; } else if (t == '(' || t == '{' || t == '[') { depth--; } if (depth == 0) { if (index > 0) { foundArguments = true; argumentStartIndex = index; } } } index--; } } methodName = exp.Remove(argumentStartIndex); var argumentCode = exp.Remove(0, argumentStartIndex + 1); argumentCode = argumentCode.Remove(argumentCode.Length - 1, 1).Trim(); var parameters = ParseParameters(argumentCode); if (methodName == CallLiteral && owner is SFunction) { This = Context.This; } // If it has an indexer, parse it again: if (index > 0 && exp[index] == ']') { var member = InvokeMemberOrMethod(owner, methodName); if ((member as SVariable)?.Data is SFunction) { return(owner.ExecuteMethod(this, ((SVariable)member).Identifier, owner, This, parameters)); } else { return(ErrorHandler.ThrowError(ErrorType.TypeError, ErrorHandler.MessageTypeNotAFunction, methodName)); } } else { return(owner.ExecuteMethod(this, methodName, owner, This, parameters)); } }
/// <summary> /// Converts a string expression into a script object. /// </summary> private SObject ToScriptObject(string exp) { exp = exp.Trim(); // This means it's either an indexer or an array if (exp.EndsWith("]")) { if (!(exp.StartsWith("[") && !exp.Remove(0, 1).Contains("["))) // When there's no "[" besides the start, and it starts with [, then it is an array. Otherwise, do real check. { // It is possible that we are having a simple array declaration here. // We check that by looking if we can find a "[" before the expression ends: var depth = 0; var index = exp.Length - 2; var indexerStartIndex = 0; var foundIndexer = false; StringEscapeHelper escaper = new RightToLeftStringEscapeHelper(exp, index); while (index > 0 && !foundIndexer) { var t = exp[index]; escaper.CheckStartAt(index); if (!escaper.IsString) { if (t == ')' || t == '}' || t == ']') { depth++; } else if (t == '(' || t == '{') { depth--; } else if (t == '[') { if (depth == 0) { if (index > 0) { indexerStartIndex = index; foundIndexer = true; } } else { depth--; } } } index--; } if (foundIndexer) { var indexerCode = exp.Substring(indexerStartIndex + 1, exp.Length - indexerStartIndex - 2); var identifier = exp.Remove(indexerStartIndex); var statementResult = ExecuteStatement(new ScriptStatement(indexerCode)); return(ToScriptObject(identifier).GetMember(this, statementResult, true)); } } } // Normal object return procedure: // Negative number: var isNegative = false; if (exp.StartsWith("-")) { exp = exp.Remove(0, 1); isNegative = true; } SObject returnObject; if (exp == SObject.LiteralNull) { returnObject = Null; } else if (exp == SObject.LiteralUndefined) { returnObject = Undefined; } else if (exp == SObject.LiteralBoolFalse) { returnObject = CreateBool(false); } else if (exp == SObject.LiteralBoolTrue) { returnObject = CreateBool(true); } else if (exp == SObject.LiteralNan) { returnObject = CreateNumber(double.NaN); } else if (exp == SObject.LiteralThis) { returnObject = Context.This; } else if (SNumber.TryParse(exp, out var dblResult)) { returnObject = CreateNumber(dblResult); } else if (exp.StartsWith("\"") && exp.EndsWith("\"") || exp.StartsWith("\'") && exp.EndsWith("\'")) { returnObject = CreateString(exp.Remove(exp.Length - 1, 1).Remove(0, 1), true, false); } else if (exp.StartsWith("$\"") && exp.EndsWith("\"") || exp.StartsWith("$\'") && exp.EndsWith("\'")) { returnObject = CreateString(exp.Remove(exp.Length - 1, 1).Remove(0, 2), true, true); } else if (exp.StartsWith("@\"") && exp.EndsWith("\"") || exp.StartsWith("@\'") && exp.EndsWith("\'")) { returnObject = CreateString(exp.Remove(exp.Length - 1, 1).Remove(0, 2), false, false); } else if (exp.StartsWith("{") && exp.EndsWith("}")) { returnObject = SProtoObject.Parse(this, exp); } else if (exp.StartsWith("[") && exp.EndsWith("]")) { returnObject = SArray.Parse(this, exp); } else if (exp.StartsWith("function") && Regex.IsMatch(exp, RegexFunction)) { returnObject = new SFunction(this, exp); } else if (Context.IsApiUsing(exp)) { returnObject = Context.GetApiUsing(exp); } else if (Context.IsVariable(exp)) { returnObject = Context.GetVariable(exp); } else if (Context.This.HasMember(this, exp)) { returnObject = Context.This.GetMember(this, CreateString(exp), false); } else if (Context.IsPrototype(exp)) { returnObject = Context.GetPrototype(exp); } else if (exp.StartsWith("new ")) { returnObject = Context.CreateInstance(exp); } else if (exp.StartsWith(ObjectBuffer.ObjPrefix)) { var strId = exp.Remove(0, ObjectBuffer.ObjPrefix.Length); if (int.TryParse(strId, out var id) && ObjectBuffer.HasObject(id)) { returnObject = (SObject)ObjectBuffer.GetObject(id); } else { returnObject = ErrorHandler.ThrowError(ErrorType.SyntaxError, ErrorHandler.MessageSyntaxInvalidToken, exp); } } else { returnObject = ErrorHandler.ThrowError(ErrorType.ReferenceError, ErrorHandler.MessageReferenceNotDefined, exp); } if (isNegative) { returnObject = ObjectOperators.NegateNumber(this, returnObject); } return(returnObject); }
private SObject InvokeMember(SObject owner, string memberName) { // When we have an indexer at the end of the member name, we get the member variable, then apply the indexer: if (memberName.Last() == ']') { var exp = memberName; var depth = 0; var index = exp.Length - 2; var indexerStartIndex = 0; var foundIndexer = false; var escaper = new RightToLeftStringEscapeHelper(exp, index); while (index > 0 && !foundIndexer) { var t = exp[index]; escaper.CheckStartAt(index); if (!escaper.IsString) { if (t == ')' || t == ']' || t == '}') { depth++; } else if (t == '(' || t == '{') { depth--; } else if (t == '[') { if (depth == 0) { if (index > 0) { indexerStartIndex = index; foundIndexer = true; } } else { depth--; } } } } if (foundIndexer) { var indexerCode = exp.Substring(indexerStartIndex + 1, exp.Length - indexerStartIndex - 2); var identifier = exp.Remove(indexerStartIndex); var indexerObject = ExecuteStatement(new ScriptStatement(indexerCode)); return(InvokeMemberOrMethod(owner, identifier).GetMember(this, indexerObject, true)); } else { return(ErrorHandler.ThrowError(ErrorType.SyntaxError, ErrorHandler.MessageSyntaxExpectedExpression, "end of string")); } } else { return(owner.GetMember(this, CreateString(memberName), false)); } }