diff --git a/CSharp/Interpreter/ArcscriptExpression.cs b/CSharp/Interpreter/ArcscriptExpression.cs
index 0541dbe..8c0ece2 100644
--- a/CSharp/Interpreter/ArcscriptExpression.cs
+++ b/CSharp/Interpreter/ArcscriptExpression.cs
@@ -390,10 +390,10 @@ private static DoubleValues GetDoubleValues(object first, object second) {
internal static bool GetBoolValue(object value)
{
- if (value.GetType() == typeof(bool)) { return (bool)value; }
- if (value.GetType() == typeof(string)) { return ((string)value).Length > 0; }
- if (value.GetType() == typeof(int)) { return (int)value > 0; }
- if (value.GetType() == typeof(double)) { return (double)value > 0; }
+ if (value is bool b) { return b; }
+ if (value is string s) { return s.Length > 0; }
+ if (value is int i) { return i > 0; }
+ if (value is double d) { return d > 0; }
return false;
}
}
diff --git a/CSharp/Interpreter/ArcscriptFunctions.cs b/CSharp/Interpreter/ArcscriptFunctions.cs
index e79ee09..cfae923 100644
--- a/CSharp/Interpreter/ArcscriptFunctions.cs
+++ b/CSharp/Interpreter/ArcscriptFunctions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Arcweave.Interpreter.INodes;
+using Arcweave.Project;
namespace Arcweave.Interpreter
{
@@ -13,21 +14,29 @@ public class Functions
private ArcscriptState state;
public Dictionary, object>> functions { get; private set; } = new Dictionary, object>>();
- public Dictionary returnTypes = new Dictionary()
+ public class FunctionDefinition
{
- { "sqrt", typeof (double) },
- { "sqr", typeof (double) },
- { "abs", typeof (double) },
- { "random", typeof (double) },
- { "roll", typeof (int) },
- { "show", typeof (string) },
- { "reset", typeof (void) },
- { "resetAll", typeof (void) },
- { "round", typeof (int) },
- { "min", typeof (double) },
- { "max", typeof (double) },
- { "visits", typeof (int) },
- { "resetVisits", typeof (void) },
+ public int? MinArgs { get; set; }
+ public int? MaxArgs { get; set; }
+ public Type ArgumentsType { get; set; }
+ public Type ReturnType { get; set; }
+ }
+
+ public static Dictionary FunctionDefinitions =
+ new() {
+ { "abs", new FunctionDefinition { MinArgs=1, MaxArgs=1, ReturnType = typeof(int)} },
+ { "max", new FunctionDefinition { MinArgs=2, ReturnType = typeof(double)} },
+ { "min", new FunctionDefinition { MinArgs=2, ReturnType = typeof(double)} },
+ { "random", new FunctionDefinition { MinArgs=0, MaxArgs=0, ReturnType = typeof(double)} },
+ { "roll", new FunctionDefinition { MinArgs=1, MaxArgs=2, ReturnType = typeof(int)} },
+ { "round", new FunctionDefinition { MinArgs=1, MaxArgs=1, ReturnType = typeof(int)} },
+ { "sqr", new FunctionDefinition { MinArgs=1, MaxArgs=1, ReturnType = typeof(double)} },
+ { "sqrt", new FunctionDefinition { MinArgs=1, MaxArgs=1, ReturnType = typeof(double)} },
+ { "visits", new FunctionDefinition { MinArgs=0, MaxArgs=1, ReturnType = typeof(int)} },
+ { "show", new FunctionDefinition { MinArgs=1, ReturnType = typeof(string)} },
+ { "reset", new FunctionDefinition { MinArgs=1, ArgumentsType = typeof(Variable), ReturnType = typeof(void)} },
+ { "resetAll", new FunctionDefinition { MinArgs=0, ArgumentsType = typeof(Variable), ReturnType = typeof(void)} },
+ { "resetVisits", new FunctionDefinition { MinArgs=0, MaxArgs = 0, ReturnType = typeof(void)} },
};
public Functions(string elementId, IProject project, ArcscriptState state) {
@@ -152,7 +161,7 @@ public object Show(IList
".Length);
this.state.Outputs.AddParagraph(paragraphContent);
@@ -99,7 +101,7 @@ public override object VisitAssignment_segment([NotNull] ArcscriptParser.Assignm
public override object VisitFunction_call_segment([NotNull] ArcscriptParser.Function_call_segmentContext context) {
this.state.Outputs.AddScriptOutput(null);
- return this.VisitStatement_function_call(context.statement_function_call());
+ return this.VisitFunction_call(context.function_call());
}
public override object VisitConditional_section([NotNull] ArcscriptParser.Conditional_sectionContext context) {
@@ -152,180 +154,191 @@ public override object VisitElse_section([NotNull] ArcscriptParser.Else_sectionC
}
public override object VisitIf_clause([NotNull] ArcscriptParser.If_clauseContext context) {
- return this.VisitCompound_condition_or(context.compound_condition_or());
+ return Visit(context.expression())!;
}
public override object VisitElse_if_clause([NotNull] ArcscriptParser.Else_if_clauseContext context) {
- return this.VisitCompound_condition_or(context.compound_condition_or());
+ return Visit(context.expression())!;
}
- public override object VisitStatement_assignment([NotNull] ArcscriptParser.Statement_assignmentContext context) {
- string variableName = context.VARIABLE().GetText();
+ public override object VisitStatement_assignment([NotNull] ArcscriptParser.Statement_assignmentContext context)
+ {
+ var identifier = (IdentifierDef) this.VisitAssignable(context.assignable());
+
+ var identifierValue = new Expression(this.state.GetVarValue(identifier.Name, identifier.Scope));
- Expression compound_condition_or = (Expression)this.VisitCompound_condition_or(context.compound_condition_or());
+ var re = (Expression)Visit(context.expression())!;
+
if ( context.ASSIGN() != null ) {
- this.state.SetVarValue(variableName, compound_condition_or.Value);
+ this.state.SetVarValue(identifier, re.Value);
return null!;
}
- Expression variableValue = new Expression(this.state.GetVarValue(variableName));
-
if ( context.ASSIGNADD() != null ) {
- variableValue += compound_condition_or;
+ identifierValue += re;
} else if ( context.ASSIGNSUB() != null ) {
- variableValue -= compound_condition_or;
+ identifierValue -= re;
} else if ( context.ASSIGNMUL() != null ) {
- variableValue *= compound_condition_or;
+ identifierValue *= re;
} else if ( context.ASSIGNDIV() != null ) {
- variableValue /= compound_condition_or;
+ identifierValue /= re;
} else if (context.ASSIGNMOD() != null) {
- variableValue %= compound_condition_or;
+ identifierValue %= re;
}
- this.state.SetVarValue(variableName, variableValue.Value);
+ this.state.SetVarValue(identifier, identifierValue.Value);
return null!;
}
- public override object VisitCompound_condition_or([NotNull] ArcscriptParser.Compound_condition_orContext context) {
- Expression compound_condition_and = (Expression)this.VisitCompound_condition_and(context.compound_condition_and());
- if ( context.compound_condition_or() != null ) {
- Expression compound_condition_or = (Expression)this.VisitCompound_condition_or(context.compound_condition_or());
- Expression result = compound_condition_and || compound_condition_or;
- return result;
- }
- return compound_condition_and;
+ public override object VisitAssignable(ArcscriptParser.AssignableContext context)
+ {
+ return this.VisitIdentifier(context.identifier());
}
- public override object VisitCompound_condition_and([NotNull] ArcscriptParser.Compound_condition_andContext context) {
- Expression negated_unary_condition = (Expression)this.VisitNegated_unary_condition(context.negated_unary_condition());
- if ( context.compound_condition_and() != null ) {
- Expression compound_condition_and = (Expression)this.VisitCompound_condition_and(context.compound_condition_and());
- Expression result = negated_unary_condition && compound_condition_and;
- return result;
+ public override object VisitComparisonExpression(ArcscriptParser.ComparisonExpressionContext context)
+ {
+ var left = (Expression)Visit(context.expression(0))!;
+ if (context.AND() != null || context.ANDKEYWORD() != null)
+ {
+ if (!Expression.GetBoolValue(left.Value))
+ {
+ return new Expression(false);
+ }
+ var rightComp = (Expression)Visit(context.expression(1))!;
+ return new Expression(Expression.GetBoolValue(rightComp.Value));
}
- return negated_unary_condition;
- }
-
- public override object VisitNegated_unary_condition([NotNull] ArcscriptParser.Negated_unary_conditionContext context) {
- Expression unary_condition = (Expression)this.VisitUnary_condition(context.unary_condition());
-
- if ( context.NEG() != null || context.NOTKEYWORD() != null ) {
- return !unary_condition;
+ if (context.OR() != null || context.ORKEYWORD() != null)
+ {
+ if (Expression.GetBoolValue(left.Value))
+ {
+ return left;
+ }
+ var rightComp = (Expression)Visit(context.expression(1))!;
+ return rightComp;
}
-
- return unary_condition;
- }
-
- public override object VisitUnary_condition([NotNull] ArcscriptParser.Unary_conditionContext context) {
- return this.VisitCondition(context.condition());
- }
-
- public override object VisitCondition([NotNull] ArcscriptParser.ConditionContext context) {
- if ( context.expression().Length == 1 ) {
- return this.VisitExpression(context.expression()[0]);
+
+ var right = (Expression)Visit(context.expression(1))!;
+
+ if (context.EQ() != null || (context.ISKEYWORD() != null && context.NOTKEYWORD() == null))
+ {
+ return new Expression(left == right);
}
- ArcscriptParser.Conditional_operatorContext conditional_operator_context = context.conditional_operator();
- Expression exp0 = (Expression)this.VisitExpression(context.expression()[0]);
- Expression exp1 = (Expression)this.VisitExpression(context.expression()[1]);
-
- bool result = false;
- if ( conditional_operator_context.GT() != null ) {
- result = exp0 > exp1;
+ if (context.NE() != null || (context.ISKEYWORD() != null && context.NOTKEYWORD() != null))
+ {
+ return new Expression(left != right);
}
- if ( conditional_operator_context.GE() != null ) {
- result = exp0 >= exp1;
+
+ if (context.LT() != null)
+ {
+ return new Expression(left < right);
}
- if ( conditional_operator_context.LT() != null ) {
- result = exp0 < exp1;
+
+ if (context.GT() != null)
+ {
+ return new Expression(left > right);
}
- if ( conditional_operator_context.LE() != null ) {
- result = exp0 <= exp1;
+
+ if (context.LE() != null)
+ {
+ return new Expression(left <= right);
}
- if ( conditional_operator_context.EQ() != null ) {
- result = exp0 == exp1;
+
+ if (context.GE() != null)
+ {
+ return new Expression(left >= right);
}
- if ( conditional_operator_context.NE() != null ) {
- result = exp0 != exp1;
+
+ throw new Exception("Unknown comparison operator");
+ }
+
+ public override object VisitUnaryExpression(ArcscriptParser.UnaryExpressionContext context)
+ {
+ if (context.NOTKEYWORD() != null || context.NEG() != null)
+ {
+ return !(Expression)Visit(context.expression())!;
}
- if ( conditional_operator_context.ISKEYWORD() != null ) {
- if ( conditional_operator_context.NOTKEYWORD() != null ) {
- result = exp0 != exp1;
- } else
- {
- result = exp0 == exp1;
- }
+
+ if (context.ADD() != null)
+ {
+ return Visit(context.expression())!;
}
- return new Expression(result);
+ if (context.SUB() != null)
+ {
+ return -(Expression)Visit(context.expression())!;
+ }
+ throw new Exception("Unknown unary operator");
}
- public override object VisitExpression([NotNull] ArcscriptParser.ExpressionContext context) {
- if ( context.STRING() != null ) {
- string result = context.STRING().GetText();
- result = result.Substring(1, result.Length - 2);
- return new Expression(result);
- }
- if ( context.BOOLEAN() != null ) {
- return new Expression(context.BOOLEAN().GetText() == "true");
+ public override object VisitMultiplicativeExpression(ArcscriptParser.MultiplicativeExpressionContext context)
+ {
+ var left = (Expression)Visit(context.expression(0))!;
+ var right = (Expression)Visit(context.expression(1))!;
+ if (context.MUL() != null)
+ {
+ return left * right;
}
- return this.VisitAdditive_numeric_expression(context.additive_numeric_expression());
- }
- public override object VisitAdditive_numeric_expression([NotNull] ArcscriptParser.Additive_numeric_expressionContext context) {
- if ( context.additive_numeric_expression() != null ) {
- Expression result = (Expression)this.VisitAdditive_numeric_expression(context.additive_numeric_expression());
- Expression mult_num_expression = (Expression)this.VisitMultiplicative_numeric_expression(context.multiplicative_numeric_expression());
- if ( context.ADD() != null ) {
- return result + mult_num_expression;
- }
- // Else MINUS
- return result - mult_num_expression;
+ if (context.DIV() != null)
+ {
+ return left / right;
}
- return (Expression)this.VisitMultiplicative_numeric_expression(context.multiplicative_numeric_expression());
+ if (context.MOD() != null)
+ {
+ return left % right;
+ }
+ throw new Exception("Unknown multiplicative operator");
}
- public override object VisitMultiplicative_numeric_expression([NotNull] ArcscriptParser.Multiplicative_numeric_expressionContext context) {
- if ( context.multiplicative_numeric_expression() != null ) {
- Expression result = (Expression)this.VisitMultiplicative_numeric_expression(context.multiplicative_numeric_expression());
- Expression signed_unary_num_expr = (Expression)this.VisitSigned_unary_numeric_expression(context.signed_unary_numeric_expression());
- if ( context.MUL() != null ) {
- return result * signed_unary_num_expr;
- }
+ public override object VisitAdditiveExpression(ArcscriptParser.AdditiveExpressionContext context)
+ {
+ var left = (Expression)Visit(context.expression(0))!;
+ var right = (Expression)Visit(context.expression(1))!;
+ if (context.ADD() != null)
+ {
+ return left + right;
+ }
- if (context.DIV() != null)
- {
- return result / signed_unary_num_expr;
- }
- // Else MOD
- return result % signed_unary_num_expr;
+ if (context.SUB() != null)
+ {
+ return left - right;
}
+ throw new Exception("Unknown additive operator");
+ }
- return (Expression)this.VisitSigned_unary_numeric_expression(context.signed_unary_numeric_expression());
+ public override object VisitParenthesizedExpression(ArcscriptParser.ParenthesizedExpressionContext context)
+ {
+ return Visit(context.expression())!;
}
- public override object VisitSigned_unary_numeric_expression([NotNull] ArcscriptParser.Signed_unary_numeric_expressionContext context) {
- Expression unary_num_expr = (Expression)this.VisitUnary_numeric_expression(context.unary_numeric_expression());
- ArcscriptParser.SignContext sign = context.sign();
+ public override object VisitIdentifierExpression(ArcscriptParser.IdentifierExpressionContext context)
+ {
+ var identifier = (IdentifierDef)VisitIdentifier(context.identifier());
+ return new Expression(this.state.GetVarValue(identifier.Name, identifier.Scope));
+ }
- if ( sign != null ) {
- if ( sign.ADD() != null ) {
- return unary_num_expr;
- }
- // Else MINUS
- return -unary_num_expr;
- }
- return unary_num_expr;
+ public override object VisitLiteralExpression(ArcscriptParser.LiteralExpressionContext context)
+ {
+ return VisitLiteral(context.literal());
}
- public override object VisitUnary_numeric_expression([NotNull] ArcscriptParser.Unary_numeric_expressionContext context) {
- if ( context.FLOAT() != null ) {
- return new Expression(double.Parse(context.FLOAT().GetText(), CultureInfo.InvariantCulture));
- }
- if ( context.INTEGER() != null ) {
- return new Expression(int.Parse(context.INTEGER().GetText()));
+ public override object VisitFunctionCallExpression(ArcscriptParser.FunctionCallExpressionContext context)
+ {
+ return new Expression(VisitFunction_call(context.function_call()));
+ }
+
+ public override object VisitLiteral(ArcscriptParser.LiteralContext context)
+ {
+ if (context.BOOLEAN() != null)
+ {
+ if (context.BOOLEAN().GetText() == "true")
+ {
+ return new Expression(true);
+ }
+ return new Expression(false);
}
if (context.STRING() != null)
@@ -334,100 +347,91 @@ public override object VisitUnary_numeric_expression([NotNull] ArcscriptParser.U
result = result.Substring(1, result.Length - 2);
return new Expression(result);
}
-
- if (context.BOOLEAN() != null)
- {
- return new Expression(context.BOOLEAN().GetText() == "true");
- }
- if ( context.VARIABLE() != null ) {
- string variableName = context.VARIABLE().GetText();
- return new Expression(this.state.GetVarValue(variableName));
- }
-
- if ( context.function_call() != null )
- {
- object functionResult = this.VisitFunction_call(context.function_call());
- if (functionResult.GetType() == typeof(Expression))
- {
- return functionResult;
- }
- return new Expression(functionResult);
- }
- return this.VisitCompound_condition_or(context.compound_condition_or());
+ return VisitNumeric_literal(context.numeric_literal());
}
- public override object VisitVoid_function_call([NotNull] ArcscriptParser.Void_function_callContext context)
+
+ public override object VisitNumeric_literal(ArcscriptParser.Numeric_literalContext context)
{
- string fname = "";
- IList? argument_list_result = null;
- if (context.VFNAME() != null)
+ if (context.FLOAT() != null)
{
- fname = context.VFNAME().GetText();
- if (context.argument_list() != null)
- {
- argument_list_result = (IList)this.VisitArgument_list(context.argument_list());
- }
- }
- if (context.VFNAMEVARS() != null)
- {
- fname = context.VFNAMEVARS().GetText();
- if (context.variable_list() != null)
- {
- argument_list_result = (IList)this.VisitVariable_list(context.variable_list());
- }
+ return new Expression(double.Parse(context.FLOAT().GetText(), CultureInfo.InvariantCulture));
}
-
- object returnValue = this._functions.functions[fname](argument_list_result);
-
- return returnValue;
+ return new Expression(int.Parse(context.INTEGER().GetText()));
}
public override object VisitFunction_call([NotNull] ArcscriptParser.Function_callContext context) {
IList? argument_list_result = null;
+ string fname = context.FNAME().GetText();
+
if ( context.argument_list() != null ) {
argument_list_result = (IList)this.VisitArgument_list(context.argument_list());
}
- string fname = context.FNAME().GetText();
-
- Type resultType = this._functions.returnTypes[fname];
- object returnValue = this._functions.functions[fname](argument_list_result);
-
- return returnValue;
- }
-
- public override object VisitVariable_list([NotNull] ArcscriptParser.Variable_listContext context)
- {
- List variables = new List();
- foreach (ITerminalNode variable in context.VARIABLE())
+ if (context.identifier_list() != null)
{
- var varObject = this.state.GetVariable(variable.GetText());
- if (varObject != null)
+ argument_list_result = (IList)this.VisitIdentifier_list(context.identifier_list());
+ if (Functions.FunctionDefinitions.ContainsKey(fname))
{
- variables.Add(varObject);
+ if (Functions.FunctionDefinitions[fname].ArgumentsType == typeof(Variable))
+ {
+ for (int i = 0; i < argument_list_result.Count; i++)
+ {
+ IdentifierDef identifierDef = (IdentifierDef)argument_list_result[i];
+ argument_list_result[i] = this.state.GetVariable(identifierDef.Name, identifierDef.Scope);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < argument_list_result.Count; i++)
+ {
+ IdentifierDef identifierDef = (IdentifierDef)argument_list_result[i];
+ argument_list_result[i] = new Expression(this.state.GetVarValue(identifierDef.Name, identifierDef.Scope));
+ }
+ }
}
}
- return variables;
+
+ object returnValue = this._functions.functions[fname](argument_list_result);
+
+ return returnValue;
}
- public override object VisitArgument_list([NotNull] ArcscriptParser.Argument_listContext context) {
- List argumentList = new List();
- foreach ( ArcscriptParser.ArgumentContext argument in context.argument() ) {
- argumentList.Add(this.VisitArgument(argument));
- }
- return argumentList;
+ public override object VisitArgument_list([NotNull] ArcscriptParser.Argument_listContext context)
+ {
+ return context.argument().Select(this.VisitArgument).ToList();
}
public override object VisitArgument([NotNull] ArcscriptParser.ArgumentContext context) {
- if ( context.STRING() != null ) {
- string result = context.STRING().GetText();
- result = result.Substring(1, result.Length - 2);
- return new Expression(result);
+ if (context.expression() != null)
+ {
+ return Visit(context.expression())!;
}
if ( context.mention() != null ) {
Mention mention_result = (Mention)this.VisitMention(context.mention());
return mention_result;
}
- return this.VisitAdditive_numeric_expression(context.additive_numeric_expression());
+ throw new Exception("Unknown argument");
+ }
+
+ public override object VisitIdentifier_list(ArcscriptParser.Identifier_listContext context)
+ {
+ return context.identifier().Select(this.VisitIdentifier).ToList();
+ }
+
+ public override object VisitIdentifier(ArcscriptParser.IdentifierContext context)
+ {
+ string name = "";
+ string? scope = null;
+ if (context.IDENTIFIER().Length == 1)
+ {
+ name = context.IDENTIFIER(0).GetText();
+ }
+ else
+ {
+ scope = context.IDENTIFIER(0).GetText();
+ name = context.IDENTIFIER(1).GetText();
+ }
+ return new IdentifierDef(name, scope);
}
public override object VisitMention([NotNull] ArcscriptParser.MentionContext context) {
@@ -487,5 +491,12 @@ public struct ConditionalSection
public ConditionalSection(bool clause, object script) { Clause = clause; Script = script; }
}
+
+ public struct IdentifierDef
+ {
+ public readonly string Name;
+ public readonly string? Scope;
+ public IdentifierDef(string name, string? scope = null) { Name = name; Scope = scope; }
+ }
}
}
\ No newline at end of file
diff --git a/CSharp/Interpreter/INodes/IBoard.cs b/CSharp/Interpreter/INodes/IBoard.cs
index 94faf57..f60fead 100644
--- a/CSharp/Interpreter/INodes/IBoard.cs
+++ b/CSharp/Interpreter/INodes/IBoard.cs
@@ -1,6 +1,6 @@
namespace Arcweave.Interpreter.INodes
{
- public interface IBoard
+ public interface IBoard : IHasVariables
{
}
}
diff --git a/CSharp/Interpreter/INodes/IHasVariables.cs b/CSharp/Interpreter/INodes/IHasVariables.cs
new file mode 100644
index 0000000..614f335
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IHasVariables.cs
@@ -0,0 +1,20 @@
+#if GODOT
+using Godot.Collections;
+#else
+using System.Collections.Generic;
+#endif
+
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IHasVariables
+ {
+#if GODOT
+ Array Variables { get; }
+#else
+ List Variables { get; }
+#endif
+ public string CustomId { get; }
+ public void AddVariable(Arcweave.Project.Variable variable);
+ }
+}
+
diff --git a/CSharp/Interpreter/INodes/IProject.cs b/CSharp/Interpreter/INodes/IProject.cs
index 12afd10..ec66040 100644
--- a/CSharp/Interpreter/INodes/IProject.cs
+++ b/CSharp/Interpreter/INodes/IProject.cs
@@ -18,6 +18,6 @@ public interface IProject
#endif
public Arcweave.Project.Element ElementWithId(string id);
- public Arcweave.Project.Variable GetVariable(string name);
+ public Arcweave.Project.Variable GetVariable(string name, string scope = null);
}
}
diff --git a/CSharp/Interpreter/INodes/IVariable.cs b/CSharp/Interpreter/INodes/IVariable.cs
index b6ec4d6..a0bab94 100644
--- a/CSharp/Interpreter/INodes/IVariable.cs
+++ b/CSharp/Interpreter/INodes/IVariable.cs
@@ -1,4 +1,5 @@
-#if GODOT
+using System;
+#if GODOT
using Godot;
#endif
@@ -7,6 +8,7 @@ namespace Arcweave.Interpreter.INodes
public interface IVariable
{
public string Name { get; set; }
+ public string Id { get; }
#if GODOT
Variant Value { get; }
Variant DefaultValue { get; }
@@ -14,6 +16,7 @@ public interface IVariable
object Value { get; }
object DefaultValue { get; }
#endif
+ IHasVariables Parent { get; }
public object ObjectValue { get; }
System.Type Type { get; }
diff --git a/CSharp/Project/Board.cs b/CSharp/Project/Board.cs
index e5f53ca..ff62618 100644
--- a/CSharp/Project/Board.cs
+++ b/CSharp/Project/Board.cs
@@ -7,17 +7,39 @@ namespace Arcweave.Project
public partial class Board
{
public string Id { get; set; }
+ public string CustomId { get; }
#if GODOT
[Export] public Array Elements { get; private set; }
#else
- public List Nodes { get; set; }
+ public List Nodes { get; private set; }
+ public List Variables { get; private set; }
#endif
- public Board(string id, List nodes)
+ public Board(string id, List nodes, string customId = null)
{
Id = id;
Nodes = nodes;
+ CustomId = customId;
+ Variables = new List();
+ }
+
+ public Board(string id, List nodes, List variables, string customId = null)
+ {
+ Id = id;
+ Nodes = nodes;
+ foreach (var variable in variables)
+ {
+ variable.Parent = this;
+ }
+ Variables = variables;
+ CustomId = customId;
}
public T NodeWithID(string id) where T : INode => Nodes.OfType().FirstOrDefault(x => x.Id == id);
+
+ public void AddVariable(Variable variable)
+ {
+ variable.Parent = this;
+ Variables.Add(variable);
+ }
}
}
\ No newline at end of file
diff --git a/CSharp/Project/Project.cs b/CSharp/Project/Project.cs
index 5b39846..d5dcdb8 100644
--- a/CSharp/Project/Project.cs
+++ b/CSharp/Project/Project.cs
@@ -15,9 +15,18 @@ public Project(List boards, List variables)
}
public Element ElementWithId(string id) => GetNodeWithID(id);
- public Variable GetVariable(string name)
+ public Variable GetVariable(string name, string scope = null)
{
- return Variables.FirstOrDefault(variable => variable.Name == name);
+ if (scope == null)
+ {
+ return Variables.FirstOrDefault(variable => variable.Name == name);
+ }
+ var board = Boards.FirstOrDefault(board => board.Id == scope);
+ if (board != null)
+ {
+ return board.Variables.FirstOrDefault(variable => variable.Name == name);
+ }
+ return null;
}
public T GetNodeWithID(string id) where T : INode {
diff --git a/CSharp/Project/Variable.cs b/CSharp/Project/Variable.cs
index 6b98db9..f44e5fc 100644
--- a/CSharp/Project/Variable.cs
+++ b/CSharp/Project/Variable.cs
@@ -1,23 +1,65 @@
-namespace Arcweave.Project
+using System;
+using Arcweave.Interpreter.INodes;
+
+namespace Arcweave.Project
{
public partial class Variable
{
public string Name { get; set; }
+ public string Id { get; }
public object Value { get; set; }
public object ObjectValue => Value;
+ public IHasVariables Parent { get; set; }
public System.Type Type { get; }
private object _defaultValue;
public object DefaultValue => _defaultValue;
- public Variable(string name, object value)
+ public Variable(string id, string name, object value)
{
+ Id = id;
Name = name;
Value = value;
_defaultValue = value;
Type = value.GetType();
}
+ public Variable(string id, string name, object value, IHasVariables parent)
+ {
+ Id = id;
+ Name = name;
+ Value = value;
+ _defaultValue = value;
+ Type = value.GetType();
+ Parent = parent;
+ }
public void ResetToDefaultValue()
{
Value = _defaultValue;
}
+
+ public Variable Clone()
+ {
+ var other = (Variable)MemberwiseClone();
+
+ if (Type == typeof(string))
+ {
+ other.Value = (string)Value;
+ other._defaultValue = (string)_defaultValue;
+ }
+ else if (Type == typeof(int))
+ {
+ other.Value = (int)Value;
+ other._defaultValue = (int)_defaultValue;
+ }
+ else if (Type == typeof(double))
+ {
+ other.Value = (double)Value;
+ other._defaultValue = (double)_defaultValue;
+ }
+ else if (Type == typeof(bool))
+ {
+ other.Value = (bool)Value;
+ other._defaultValue = (bool)_defaultValue;
+ }
+ return other;
+ }
}
}
diff --git a/CSharp/TestObjects.cs b/CSharp/TestObjects.cs
index 95d4a8c..8f80a12 100644
--- a/CSharp/TestObjects.cs
+++ b/CSharp/TestObjects.cs
@@ -8,7 +8,9 @@ public struct TestVariable
public string id;
public string name;
public string type;
- public object value;
+ public object defaultValue;
+ public string cType;
+ public string scope;
}
public struct EventData
@@ -26,6 +28,7 @@ public struct TestCase
public object result;
public string? error;
public Dictionary? visits;
+ public Dictionary>? values;
public List? events;
}
diff --git a/CSharp/UnitTests.cs b/CSharp/UnitTests.cs
index d34d0e8..09f29d2 100644
--- a/CSharp/UnitTests.cs
+++ b/CSharp/UnitTests.cs
@@ -12,25 +12,86 @@ namespace Arcweave
{
public class Tests
{
- struct TestData
+
+ private static Project.Project CreateProject(TestFile testFile, TestCase testCase)
{
- public readonly Dictionary Variables;
- public readonly List Cases;
+ var variables = new Dictionary();
+ var boards = new Dictionary();
+ foreach (var variable in testFile.initialVars.Values)
+ {
+ Variable v;
+ if (variable.defaultValue is long l)
+ {
+ v = new Variable(variable.id, variable.name, (int)l);
+ }
+ else
+ {
+ v = new Variable(variable.id, variable.name, variable.defaultValue);
+ }
- public TestData(Dictionary variables, List cases)
+ if (variable.cType == null || variable.cType == "global")
+ {
+ variables[variable.id] = v;
+ }
+ else if (variable.cType == "boards")
+ {
+ if (!boards.ContainsKey(variable.scope))
+ {
+ boards[variable.scope] = new Board(variable.scope, new List(), variable.scope);
+ }
+ boards[variable.scope].AddVariable(v);
+ }
+ }
+
+ if (testCase.values != null)
+ {
+ foreach (var scopeKvp in testCase.values)
+ {
+ foreach (var varDef in scopeKvp.Value)
+ {
+ var varId = varDef.Key;
+ var varValue = varDef.Value;
+ if (scopeKvp.Key == "global")
+ {
+ variables[varId].Value = varValue;
+ }
+ else
+ {
+ throw new NotImplementedException("Only global scope variable updates is supported in CreateProject");
+ }
+ }
+ }
+ }
+
+ var nodes = new List();
+
+ if (testCase.visits != null)
{
- Variables = variables;
- Cases = cases;
+ foreach (var kvp in testCase.visits)
+ {
+ var element = new Element(kvp.Key)
+ {
+ Visits = kvp.Value
+ };
+ nodes.Add(element);
+ }
}
+ var board = new Board("testBoardId", nodes);
+ boards["testBoardId"] = board;
+
+ var project = new Project.Project(boards.Values.ToList(), variables.Values.ToList());
+
+ return project;
}
private static IEnumerable GetValidTestData()
{
var tests = LoadJson("../../../__tests__/valid.json");
- foreach (var testCase in tests.Cases)
+ foreach (var testCase in tests.cases)
{
- yield return new TestCaseData(tests.Variables, testCase);
+ Project.Project project = CreateProject(tests, testCase);
+ yield return new TestCaseData(project, testCase, tests.initialVars);
}
}
@@ -38,9 +99,10 @@ private static IEnumerable GetConditionsTestData()
{
var tests = LoadJson("../../../__tests__/conditions.json");
- foreach (var validTestsCase in tests.Cases)
+ foreach (var testCase in tests.cases)
{
- yield return new TestCaseData(tests.Variables, validTestsCase);
+ Project.Project project = CreateProject(tests, testCase);
+ yield return new TestCaseData(project, testCase, tests.initialVars);
}
}
@@ -48,9 +110,10 @@ private static IEnumerable GetStringConcatTestData()
{
var tests = LoadJson("../../../__tests__/stringConcat.json");
- foreach (var stringConcatTestCase in tests.Cases)
+ foreach (var testCase in tests.cases)
{
- yield return new TestCaseData(tests.Variables, stringConcatTestCase);
+ Project.Project project = CreateProject(tests, testCase);
+ yield return new TestCaseData(project, testCase, tests.initialVars);
}
}
@@ -58,45 +121,43 @@ private static IEnumerable GetRuntimeErrorTestData()
{
var tests = LoadJson("../../../__tests__/runtimeErrors.json");
- foreach (var runtimeErrorTestCase in tests.Cases)
+ foreach (var testCase in tests.cases)
{
- yield return new TestCaseData(tests.Variables, runtimeErrorTestCase);
+ Project.Project project = CreateProject(tests, testCase);
+ yield return new TestCaseData(project, testCase, tests.initialVars);
}
}
private static IEnumerable GetParseErrorTestData()
{
var tests = LoadJson("../../../__tests__/parseErrors.json");
-
- foreach (var parseErrorTestCase in tests.Cases)
+
+ foreach (var testCase in tests.cases)
{
- yield return new TestCaseData(tests.Variables, parseErrorTestCase);
+ Project.Project project = CreateProject(tests, testCase);
+ yield return new TestCaseData(project, testCase, tests.initialVars);
}
}
- private static TestData LoadJson(string filePath)
+ private static IEnumerable GetMemberTestData()
{
- using var r = new StreamReader(filePath);
- var json = r.ReadToEnd();
- var testFile = JsonConvert.DeserializeObject(json);
- var variables = new Dictionary();
+ var tests = LoadJson("../../../__tests__/member.json");
- foreach (var variable in testFile.initialVars.Values)
+ foreach (var testCase in tests.cases)
{
- if (variable.value is long l)
- {
- variables[variable.id] = new Variable(variable.name, (int)l);
- }
- else
- {
- variables[variable.id] = new Variable(variable.name, variable.value);
- }
+ Project.Project project = CreateProject(tests, testCase);
+ yield return new TestCaseData(project, testCase, tests.initialVars);
}
+ }
- return new TestData(variables, testFile.cases);
+ private static TestFile LoadJson(string filePath)
+ {
+ using var r = new StreamReader(filePath);
+ var json = r.ReadToEnd();
+ return JsonConvert.DeserializeObject(json);
}
- private static Dictionary? ChangesByName(Dictionary variables,
+ private static Dictionary? ChangesByName(Project.Project project, Dictionary initialVars,
Dictionary? changes)
{
if (changes == null || changes.Count == 0)
@@ -107,7 +168,7 @@ private static TestData LoadJson(string filePath)
var changesByName = new Dictionary();
foreach (var varChange in changes)
{
- changesByName[variables[varChange.Key].Name] = varChange.Value;
+ changesByName[initialVars[varChange.Key].name] = varChange.Value;
}
return changesByName;
@@ -115,25 +176,10 @@ private static TestData LoadJson(string filePath)
[Test]
[TestCaseSource(nameof(GetValidTestData))]
- public void ValidTests(Dictionary variables, TestCase testCase)
+ public void ValidTests(Project.Project project, TestCase testCase, Dictionary initialVars)
{
- var nodes = new List();
- if (testCase.visits != null)
- {
- foreach (var kvp in testCase.visits)
- {
- var element = new Element(kvp.Key)
- {
- Visits = kvp.Value
- };
- nodes.Add(element);
- }
- }
- var board = new Board("testBoardId", nodes);
- var boards = new List { board };
-
Console.WriteLine("Testing Case: " + testCase.code);
- var project = new Project.Project(boards, variables.Values.ToList());
+
List events = new List();
void OnEvent(string eventName)
@@ -149,11 +195,10 @@ void OnEvent(string eventName)
{
Assert.That(output.Output, Is.EqualTo(testCase.output));
}
-
- var changesByName = ChangesByName(variables, testCase.changes);
- if (changesByName != null)
+
+ if (testCase.changes != null)
{
- Assert.That(output.Changes, Is.EqualTo(changesByName));
+ Assert.That(output.Changes, Is.EqualTo(testCase.changes));
}
if (testCase.events != null)
@@ -170,25 +215,10 @@ void OnEvent(string eventName)
[Test]
[TestCaseSource(nameof(GetConditionsTestData))]
- public void ConditionTests(Dictionary variables, TestCase testCase)
+ public void ConditionTests(Project.Project project, TestCase testCase, Dictionary initialVars)
{
- var nodes = new List();
- if (testCase.visits != null)
- {
- foreach (var kvp in testCase.visits)
- {
- var element = new Element(kvp.Key)
- {
- Visits = kvp.Value
- };
- nodes.Add(element);
- }
- }
- var board = new Board("testBoardId", nodes);
- var boards = new List { board };
-
Console.WriteLine("Testing Case: " + testCase.code);
- var project = new Project.Project(boards, variables.Values.ToList());
+
var i = new AwInterpreter(project, testCase.elementId);
var output = i.RunScript(testCase.code);
@@ -197,25 +227,9 @@ public void ConditionTests(Dictionary variables, TestCase test
[Test]
[TestCaseSource(nameof(GetStringConcatTestData))]
- public void StringConcatTests(Dictionary variables, TestCase testCase)
+ public void StringConcatTests(Project.Project project, TestCase testCase, Dictionary initialVars)
{
- var nodes = new List();
- if (testCase.visits != null)
- {
- foreach (var kvp in testCase.visits)
- {
- var element = new Element(kvp.Key)
- {
- Visits = kvp.Value
- };
- nodes.Add(element);
- }
- }
- var board = new Board("testBoardId", nodes);
- var boards = new List { board };
-
Console.WriteLine("Testing Case: " + testCase.code);
- var project = new Project.Project(boards, variables.Values.ToList());
var i = new AwInterpreter(project, testCase.elementId);
var output = i.RunScript(testCase.code);
@@ -223,63 +237,49 @@ public void StringConcatTests(Dictionary variables, TestCase t
{
Assert.That(output.Output, Is.EqualTo(testCase.output));
}
-
- var changesByName = ChangesByName(variables, testCase.changes);
- if (changesByName != null)
+
+ if (testCase.changes != null)
{
- Assert.That(output.Changes, Is.EqualTo(changesByName));
+ Assert.That(output.Changes, Is.EqualTo(testCase.changes));
}
}
[Test]
[TestCaseSource(nameof(GetRuntimeErrorTestData))]
- public void RuntimeErrorTests(Dictionary variables, TestCase testCase)
+ public void RuntimeErrorTests(Project.Project project, TestCase testCase, Dictionary initialVars)
{
- var nodes = new List();
- if (testCase.visits != null)
- {
- foreach (var kvp in testCase.visits)
- {
- var element = new Element(kvp.Key)
- {
- Visits = kvp.Value
- };
- nodes.Add(element);
- }
- }
- var board = new Board("testBoardId", nodes);
- var boards = new List { board };
-
Console.WriteLine("Testing Case: " + testCase.code);
- var project = new Project.Project(boards, variables.Values.ToList());
var i = new AwInterpreter(project, testCase.elementId);
Assert.Throws(() => i.RunScript(testCase.code));
}
[Test]
[TestCaseSource(nameof(GetParseErrorTestData))]
- public void ParseErrorTests(Dictionary variables, TestCase testCase)
+ public void ParseErrorTests(Project.Project project, TestCase testCase, Dictionary initialVars)
{
- var nodes = new List();
- if (testCase.visits != null)
- {
- foreach (var kvp in testCase.visits)
- {
- var element = new Element(kvp.Key)
- {
- Visits = kvp.Value
- };
- nodes.Add(element);
- }
- }
- var board = new Board("testBoardId", nodes);
- var boards = new List { board };
-
Console.WriteLine("Testing Case: " + testCase.code);
- var project = new Project.Project(boards, variables.Values.ToList());
var i = new AwInterpreter(project, testCase.elementId);
Assert.Throws(() => i.RunScript(testCase.code));
}
+
+ [Test]
+ [TestCaseSource(nameof(GetMemberTestData))]
+ public void MemberTests(Project.Project project, TestCase testCase, Dictionary initialVars)
+ {
+ Console.WriteLine("Testing Case: " + testCase.code);
+ var i = new AwInterpreter(project, testCase.elementId);
+ var output = i.RunScript(testCase.code);
+
+ if (testCase.output != null)
+ {
+ Assert.That(output.Output, Is.EqualTo(testCase.output));
+ }
+
+ if (testCase.changes != null)
+ {
+ Assert.That(output.Changes, Is.EqualTo(testCase.changes));
+ }
+ }
}
}
diff --git a/CSharp/__tests__/conditions.json b/CSharp/__tests__/conditions.json
index 88331f1..2720737 100644
--- a/CSharp/__tests__/conditions.json
+++ b/CSharp/__tests__/conditions.json
@@ -4,43 +4,43 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
}
},
"cases": [
@@ -95,6 +95,13 @@
{
"code": "x is not \"Dummy text\"
",
"result": true
+ },
+ {
+ "code": "visits(test_elem)
",
+ "visits": {
+ "b": 0
+ },
+ "result": false
}
]
}
\ No newline at end of file
diff --git a/CSharp/__tests__/member.json b/CSharp/__tests__/member.json
new file mode 100644
index 0000000..921379f
--- /dev/null
+++ b/CSharp/__tests__/member.json
@@ -0,0 +1,121 @@
+{
+ "initialVars": {
+ "var1": {
+ "id": "var1",
+ "name": "x",
+ "type": "integer",
+ "defaultValue": 14
+ },
+ "var2": {
+ "id": "var2",
+ "name": "y",
+ "type": "integer",
+ "defaultValue": 15
+ },
+ "var3": {
+ "id": "var3",
+ "name": "z",
+ "type": "integer",
+ "defaultValue": 0
+ },
+ "var4": {
+ "id": "var4",
+ "name": "w",
+ "type": "string",
+ "defaultValue": "Dummy text"
+ },
+ "var5": {
+ "id": "var5",
+ "name": "$c5",
+ "type": "integer",
+ "defaultValue": 0
+ },
+ "var6": {
+ "id": "var6",
+ "name": "_a",
+ "type": "boolean",
+ "defaultValue": false
+ },
+ "var7": {
+ "id": "var7",
+ "name": "xy",
+ "type": "integer",
+ "defaultValue": -1
+ },
+ "var8": {
+ "id": "var8",
+ "name": "man",
+ "type": "string",
+ "defaultValue": "Different Text"
+ },
+ "var9": {
+ "id": "var9",
+ "name": "a",
+ "type": "integer",
+ "defaultValue": 12,
+ "cType": "boards",
+ "scope": "board1"
+ },
+ "var10": {
+ "id": "var10",
+ "name": "b",
+ "type": "integer",
+ "defaultValue": 0,
+ "cType": "boards",
+ "scope": "board1"
+ },
+ "var11": {
+ "id": "var11",
+ "name": "a",
+ "type": "string",
+ "defaultValue": "Component Text",
+ "cType": "boards",
+ "scope": "board2"
+ }
+ },
+ "cases": [
+ {
+ "code": "board1.a = 42
",
+ "changes": {
+ "var9": 42
+ }
+ },
+ {
+ "code": "show(board1.a)
",
+ "output": "12
"
+ },
+ {
+ "code": "board1.a = 99
reset(board1.a, board1.b)
",
+ "changes": {
+ "var9": 12,
+ "var10": 0
+ }
+ },
+ {
+ "code": "x = board1.a
",
+ "changes": {
+ "var1": 12
+ }
+ },
+ {
+ "code": "board1.a = 55
board1.b = board1.a
",
+ "changes": {
+ "var9": 55,
+ "var10": 55
+ }
+ },
+ {
+ "code": "board1.a = 77
board1.b = board1.a
reset(board1.a, board1.b)
",
+ "changes": {
+ "var9": 12,
+ "var10": 0
+ }
+ },
+ {
+ "code": "board2.a = \"Hello World\"
",
+ "changes": {
+ "var11": "Hello World"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CSharp/__tests__/parseErrors.json b/CSharp/__tests__/parseErrors.json
index 07d79d9..96902ac 100644
--- a/CSharp/__tests__/parseErrors.json
+++ b/CSharp/__tests__/parseErrors.json
@@ -4,43 +4,43 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
}
},
"cases": [
diff --git a/CSharp/__tests__/replaceVariables.json b/CSharp/__tests__/replaceVariables.json
index 4fc89b0..3d7b467 100644
--- a/CSharp/__tests__/replaceVariables.json
+++ b/CSharp/__tests__/replaceVariables.json
@@ -4,43 +4,64 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
+ },
+ "var8": {
+ "id": "var8",
+ "name": "x",
+ "type": "string",
+ "defaultValue": "Different Text",
+ "scope": "comp1"
+ },
+ "var9": {
+ "id": "var9",
+ "name": "xyz",
+ "type": "string",
+ "defaultValue": "hihi",
+ "cType": "boards",
+ "scope": "boardOne"
+ },
+ "var10": {
+ "id": "var10",
+ "name": "xyz",
+ "type": "integer",
+ "defaultValue": 0
}
},
"cases": [
@@ -64,6 +85,49 @@
"var1": "zed"
},
"result": "b = \"x marks the spot\"
xy = 5 + zed
"
+ },
+ {
+ "code": "x = x + 2 * sqrt(x)
y = 5 + x
",
+ "variableChanges": {
+ "var8": "zed"
+ },
+ "result": "x = x + 2 * sqrt(x)
y = 5 + x
"
+ },
+ {
+ "code": "x = comp1.x + 2 * sqrt(x)
y = 5 + comp1.x
",
+ "variableChanges": {
+ "var8": "zed"
+ },
+ "result": "x = comp1.zed + 2 * sqrt(x)
y = 5 + comp1.zed
"
+ },
+ {
+ "code": "comp1.x = 'test'
x = 'test'
",
+ "variableChanges": {
+ "var1": "zed"
+ },
+ "result": "comp1.x = 'test'
zed = 'test'
"
+ },
+ {
+ "code": "comp1.x = 'test'
x = 'test'
",
+ "variableChanges": {
+ "var8": "zed"
+ },
+ "result": "comp1.zed = 'test'
x = 'test'
"
+ },
+ {
+ "code": "boardOne.xyz = 'fourtytwo'
xyz = 42
",
+ "variableChanges": {
+ "var9": "hohoho"
+ },
+ "result": "boardOne.hohoho = 'fourtytwo'
xyz = 42
"
+ },
+ {
+ "code": "x = comp1.x + 2 * sqrt(x)
y = 5 + comp1.x
",
+ "variableChanges": {
+ "var1": "zed",
+ "var8": "zed"
+ },
+ "result": "zed = comp1.zed + 2 * sqrt(zed)
y = 5 + comp1.zed
"
}
]
}
\ No newline at end of file
diff --git a/CSharp/__tests__/runtimeErrors.json b/CSharp/__tests__/runtimeErrors.json
index 9adb85e..4f6b72e 100644
--- a/CSharp/__tests__/runtimeErrors.json
+++ b/CSharp/__tests__/runtimeErrors.json
@@ -4,43 +4,43 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
}
},
"cases": [
diff --git a/CSharp/__tests__/stringConcat.json b/CSharp/__tests__/stringConcat.json
index d9b3503..4d2cd5d 100644
--- a/CSharp/__tests__/stringConcat.json
+++ b/CSharp/__tests__/stringConcat.json
@@ -4,49 +4,49 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
},
"var8": {
"id": "var8",
"name": "man",
"type": "string",
- "value": "Different Text"
+ "defaultValue": "Different Text"
}
},
"cases": [
diff --git a/CSharp/__tests__/valid.json b/CSharp/__tests__/valid.json
index d50ed3b..6929780 100644
--- a/CSharp/__tests__/valid.json
+++ b/CSharp/__tests__/valid.json
@@ -4,49 +4,49 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
},
"var8": {
"id": "var8",
"name": "man",
"type": "string",
- "value": "Different Text"
+ "defaultValue": "Different Text"
}
},
"cases": [
@@ -312,20 +312,16 @@
}
},
{
- "code": "x = random()
",
- "changes": {}
+ "code": "x = random()
"
},
{
- "code": "x = roll(6)
",
- "changes": {}
+ "code": "x = roll(6)
"
},
{
- "code": "x = roll(6, 5)
",
- "changes": {}
+ "code": "x = roll(6, 5)
"
},
{
- "code": "x = roll(y)
",
- "changes": {}
+ "code": "x = roll(y)
"
},
{
"code": "x = round(2.65)
",
@@ -412,7 +408,9 @@
},
{
"code": "How are you?
x = 1
I am fine, thank you.
",
- "changes": {},
+ "changes": {
+ "var1": 1
+ },
"output": "How are you?
I am fine, thank you.
"
},
{
@@ -536,7 +534,24 @@
},
{
"code": "w = \"String with \\\"quotes\\\" \\t tabs and \\n new lines\"
show(w)
",
- "output": "String with \"quotes\" \t tabs and \n new lines
"
+ "output": "String with \"quotes\" \t tabs and \n new lines
",
+ "changes": {
+ "var4": "String with \\\"quotes\\\" \\t tabs and \\n new lines"
+ }
+ },
+ {
+ "values": {
+ "global": {
+ "var1": 12,
+ "var2": 10
+ }
+ },
+ "code": "show(x,\" \", y)
reset(x, y)
",
+ "output": "12 10
",
+ "changes": {
+ "var1": 14,
+ "var2": 15
+ }
}
]
}
\ No newline at end of file
diff --git a/Cpp/CMakeLists.txt b/Cpp/CMakeLists.txt
index 5bcbad9..7bf26c3 100755
--- a/Cpp/CMakeLists.txt
+++ b/Cpp/CMakeLists.txt
@@ -123,7 +123,11 @@ if (WITH_TEST)
add_executable(ArcscriptTest ${CMAKE_CURRENT_SOURCE_DIR}/demo/ArcscriptTest.cpp)
include(FetchContent)
- FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz)
+ FetchContent_Declare(
+ json
+ URL https://github.com/nlohmann/json/releases/download/v3.12.0/json.tar.xz
+ DOWNLOAD_EXTRACT_TIMESTAMP 1
+ )
FetchContent_MakeAvailable(json)
target_link_libraries(ArcscriptTest PRIVATE nlohmann_json::nlohmann_json)
diff --git a/Cpp/demo/ArcscriptTest.cpp b/Cpp/demo/ArcscriptTest.cpp
index eb5a561..00e8f20 100644
--- a/Cpp/demo/ArcscriptTest.cpp
+++ b/Cpp/demo/ArcscriptTest.cpp
@@ -28,6 +28,9 @@ UVariable* getInitialVars(json initialVarsJson) {
initVars[i].id = strdup(id.c_str());
initVars[i].name = strdup(name.c_str());
+ if (it.value().contains("scope")) {
+ initVars[i].scope = strdup(it.value()["scope"].get().c_str());
+ }
initVars[i].type = VariableType::AW_ANY;
if (type == "string") {
initVars[i].type = VariableType::AW_STRING;
@@ -335,6 +338,7 @@ int main(int argc, char* argv[])
// Create an array of the test path files
std::vector testPaths = {
"./tests/valid.json",
+ "./tests/member.json",
"./tests/conditions.json",
"./tests/stringConcat.json",
"./tests/runtimeErrors.json",
diff --git a/Cpp/demo/tests/member.json b/Cpp/demo/tests/member.json
new file mode 100644
index 0000000..6ddeb66
--- /dev/null
+++ b/Cpp/demo/tests/member.json
@@ -0,0 +1,118 @@
+{
+ "initialVars": {
+ "var1": {
+ "id": "var1",
+ "name": "x",
+ "type": "integer",
+ "value": 14
+ },
+ "var2": {
+ "id": "var2",
+ "name": "y",
+ "type": "integer",
+ "value": 15
+ },
+ "var3": {
+ "id": "var3",
+ "name": "z",
+ "type": "integer",
+ "value": 0
+ },
+ "var4": {
+ "id": "var4",
+ "name": "w",
+ "type": "string",
+ "value": "Dummy text"
+ },
+ "var5": {
+ "id": "var5",
+ "name": "$c5",
+ "type": "integer",
+ "value": 0
+ },
+ "var6": {
+ "id": "var6",
+ "name": "_a",
+ "type": "boolean",
+ "value": false
+ },
+ "var7": {
+ "id": "var7",
+ "name": "xy",
+ "type": "integer",
+ "value": -1
+ },
+ "var8": {
+ "id": "var8",
+ "name": "man",
+ "type": "string",
+ "value": "Different Text"
+ },
+ "var9": {
+ "id": "var9",
+ "name": "a",
+ "type": "integer",
+ "value": 12,
+ "scope": "comp1"
+ },
+ "var10": {
+ "id": "var10",
+ "name": "b",
+ "type": "integer",
+ "value": 0,
+ "scope": "comp1"
+ },
+ "var11": {
+ "id": "var11",
+ "name": "a",
+ "type": "string",
+ "value": "Component Text",
+ "scope": "comp2"
+ }
+ },
+ "cases": [
+ {
+ "code": "comp1.a = 42
",
+ "changes": {
+ "var9": 42
+ }
+ },
+ {
+ "code": "show(comp1.a)
",
+ "output": "12
"
+ },
+ {
+ "code": "comp1.a = 99
reset(comp1.a, comp1.b)
",
+ "changes": {
+ "var9": 12,
+ "var10": 0
+ }
+ },
+ {
+ "code": "x = comp1.a
",
+ "changes": {
+ "var1": 12
+ }
+ },
+ {
+ "code": "comp1.a = 55
comp1.b = comp1.a
",
+ "changes": {
+ "var9": 55,
+ "var10": 55
+ }
+ },
+ {
+ "code": "comp1.a = 77
comp1.b = comp1.a
reset(comp1.a, comp1.b)
",
+ "changes": {
+ "var9": 12,
+ "var10": 0
+ }
+ },
+ {
+ "code": "comp2.a = \"Hello World\"
",
+ "changes": {
+ "var11": "Hello World"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Cpp/src/ArcscriptExpression.cpp b/Cpp/src/ArcscriptExpression.cpp
index fefd147..b1b55d5 100755
--- a/Cpp/src/ArcscriptExpression.cpp
+++ b/Cpp/src/ArcscriptExpression.cpp
@@ -80,7 +80,10 @@ bool Expression::valueToBool(std::any value) {
if (value.type() == typeid(std::string)) {
return (!std::any_cast(value).empty());
}
- return (std::any_cast(value));
+ if (value.type() == typeid(bool)) {
+ return (std::any_cast(value));
+ }
+ return false;
}
Expression Expression::operator+ (const Expression &other) const {
@@ -245,6 +248,19 @@ Expression Expression::operator%= (const Expression &other) {
return *this;
}
+Expression Expression::operator- () const {
+ if (value.type() == typeid(std::string)) {
+ throw RuntimeErrorException("Cannot negate a string");
+ }
+ if (value.type() == typeid(bool)) {
+ throw RuntimeErrorException("Cannot negate a boolean");
+ }
+ if (value.type() == typeid(int)) {
+ return Expression(-std::any_cast(value));
+ }
+ return Expression(-std::any_cast(value));
+}
+
bool Expression::operator== (const Expression &other) const {
if (value.type() == typeid(int) || value.type() == typeid(double)) {
NumberValues values = doubleValues(value, other.value);
diff --git a/Cpp/src/ArcscriptExpression.h b/Cpp/src/ArcscriptExpression.h
index 380eb21..ea23100 100755
--- a/Cpp/src/ArcscriptExpression.h
+++ b/Cpp/src/ArcscriptExpression.h
@@ -63,6 +63,7 @@ class Expression {
Expression operator*= (const Expression &other);
Expression operator/= (const Expression &other);
Expression operator%= (const Expression &other);
+ Expression operator- () const;
bool operator== (const Expression &other) const;
bool operator== (double other) const;
@@ -83,6 +84,10 @@ class Expression {
bool operator! () const;
bool operator&& (const Expression &other) const;
bool operator|| (const Expression &other) const;
+
+ [[nodiscard]] bool isTruthy() const {
+ return valueToBool(value);
+ }
};
}
diff --git a/Cpp/src/ArcscriptFunctions.cpp b/Cpp/src/ArcscriptFunctions.cpp
index 326180e..63f4390 100755
--- a/Cpp/src/ArcscriptFunctions.cpp
+++ b/Cpp/src/ArcscriptFunctions.cpp
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include "ArcscriptErrorExceptions.h"
@@ -59,8 +60,8 @@ namespace Arcweave {
{ "max", { 2, -1 }},
{ "min", { 2, -1 }},
{ "random", { 0, 0 }},
- { "reset", { 1, -1 }},
- { "resetAll", { 0, -1} },
+ { "reset", { 1, -1, "variable" }},
+ { "resetAll", { 0, -1, "variable" }},
{ "roll", { 1, 2 } },
{ "round", { 1, 1} },
{ "show", { 1, -1 } },
@@ -243,6 +244,9 @@ namespace Arcweave {
std::any ArcscriptFunctions::Reset(std::vector args) {
std::vector variables;
for (std::any arg : args) {
+ if (arg.type() != typeid(Variable)) {
+ throw RuntimeErrorException("The arguments of reset must be variables.");
+ }
variables.push_back(std::any_cast(arg));
}
_state->resetVars(variables);
@@ -252,6 +256,9 @@ namespace Arcweave {
std::any ArcscriptFunctions::ResetAll(std::vector args) {
std::vector except;
for (std::any arg : args) {
+ if (arg.type() != typeid(Variable)) {
+ throw RuntimeErrorException("The arguments of reset must be variables.");
+ }
except.push_back(std::any_cast(arg));
}
_state->resetAllVars(except);
diff --git a/Cpp/src/ArcscriptFunctions.h b/Cpp/src/ArcscriptFunctions.h
index 931343a..e1db9d5 100755
--- a/Cpp/src/ArcscriptFunctions.h
+++ b/Cpp/src/ArcscriptFunctions.h
@@ -2,6 +2,7 @@
#include "ArcscriptHelpers.h"
#include
+#include
namespace Arcweave {
class ArcscriptFunctions {
@@ -12,6 +13,7 @@ class ArcscriptFunctions {
struct FunctionInfo {
int minArgs;
int maxArgs;
+ std::string argsType;
};
static std::map functions;
diff --git a/Cpp/src/ArcscriptHelpers.h b/Cpp/src/ArcscriptHelpers.h
index 492899d..fcbfde6 100755
--- a/Cpp/src/ArcscriptHelpers.h
+++ b/Cpp/src/ArcscriptHelpers.h
@@ -6,6 +6,7 @@
#include
#include
#include
+
#include "ArcscriptOutputs.h"
namespace Arcweave {
@@ -18,11 +19,17 @@ enum VariableType {
AW_ANY
};
+ struct IdentifierDef {
+ std::string name;
+ std::string scope;
+ };
+
struct Variable {
std::string id;
std::string name;
VariableType type;
std::any value;
+ std::string scope;
};
class ArcscriptState {
@@ -37,39 +44,42 @@ class ArcscriptState {
std::function emit;
ArcscriptState(std::string elementId, std::map varValues, std::map _visits, std::function _emit) {
- currentElement = elementId;
- variableValues = varValues;
- for(const auto var : variableValues) {
- varNameToID[var.second.name] = var.first;
+ currentElement = std::move(elementId);
+ variableValues = std::move(varValues);
+ for(const auto&[varId, variable] : variableValues) {
+ std::string name = variable.name;
+ if (variable.scope != "") {
+ name = variable.scope + "." + variable.name;
+ }
+ varNameToID[name] = varId;
}
- visits = _visits;
+ visits = std::move(_visits);
emit = _emit;
}
- inline Variable getVar(std::string name) {
- std::string varId = varNameToID[name];
- return variableValues[varId];
+ inline static std::string getVarFullName(IdentifierDef identifier) {
+ if (identifier.scope != "") {
+ return identifier.scope + "." + identifier.name;
+ }
+ return identifier.name;
}
- inline std::any getVarValue(std::string name) {
- std::string varId = varNameToID[name];
- if(variableChanges.count(varId)) {
+ inline std::any getVarValue(IdentifierDef identifier) {
+ std::string varId = varNameToID[getVarFullName(identifier)];
+ if (variableChanges.count(varId)) {
return variableChanges[varId];
}
return variableValues[varId].value;
}
- inline VariableType getVarType(std::string name) {
- return variableValues[varNameToID[name]].type;
+
+ inline Variable getVar(IdentifierDef identifier) {
+ return variableValues[varNameToID[getVarFullName(identifier)]];
}
- inline void setVarValue(std::string name, std::any value) {
- std::string varId = varNameToID[name];
+
+ inline void setVarValue(IdentifierDef identifier, std::any value) {
+ std::string varId = varNameToID[getVarFullName(identifier)];
variableChanges[varId] = value;
}
- inline void setVarValues(std::vector names, std::vector values) {
- for (size_t i = 0; i < names.size(); i++) {
- variableChanges[names[i]] = values[i];
- }
- }
inline void resetVars(std::vector vars) {
for (Variable var : vars) {
diff --git a/Cpp/src/ArcscriptParserBase.cpp b/Cpp/src/ArcscriptParserBase.cpp
index 2b9788b..8b00b6c 100755
--- a/Cpp/src/ArcscriptParserBase.cpp
+++ b/Cpp/src/ArcscriptParserBase.cpp
@@ -7,13 +7,12 @@
using namespace antlr4;
-bool ArcscriptParserBase::assertVariable(antlr4::Token *variable) {
- std::string variableName = variable->getText();
+bool ArcscriptParserBase::assertVariable(antlr4::ParserRuleContext *identifierContext) const {
+ std::string variableName = identifierContext->getText();
if (_state->varNameToID.count(variableName) > 0) {
return true;
}
throw Arcweave::ParseErrorException("Unknown variable \"" + variableName + "\"");
- return false;
}
inline bool ends_with(std::string const & value, std::string const & ending)
@@ -57,11 +56,9 @@ bool ArcscriptParserBase::assertMention(std::any attrCtxList) {
}
if (!classFound) {
throw Arcweave::ParseErrorException("Invalid mention type");
- return false;
}
if (attrs["data-type"] != "element") {
throw Arcweave::ParseErrorException("Invalid mention type");
- return false;
}
if (_state->visits.count(attrs["data-id"]) == 0) {
throw Arcweave::ParseErrorException("Invalid element mention");
@@ -70,27 +67,31 @@ bool ArcscriptParserBase::assertMention(std::any attrCtxList) {
return true;
}
-bool ArcscriptParserBase::assertFunctionArguments(Token *fname, std::any argumentList) {
+bool ArcscriptParserBase::assertFunctionArguments(Token *fname, std::any listContext) {
int argListLength = 0;
std::string functionName = fname->getText();
int min = Arcweave::ArcscriptFunctions::functions[functionName].minArgs;
int max = Arcweave::ArcscriptFunctions::functions[functionName].maxArgs;
- if (argumentList.type() == typeid(Arcweave::ArcscriptParser::Argument_listContext*)) {
- Arcweave::ArcscriptParser::Argument_listContext *argumentListCtx = std::any_cast(argumentList);
+ std::string argsType = Arcweave::ArcscriptFunctions::functions[functionName].argsType;
+ if (listContext.type() == typeid(Arcweave::ArcscriptParser::Argument_listContext*)) {
+ auto *argumentListCtx = std::any_cast(listContext);
if (argumentListCtx != NULL) {
argListLength = static_cast(argumentListCtx->argument().size());
}
}
- if (argumentList.type() == typeid(Arcweave::ArcscriptParser::Variable_listContext*)) {
- Arcweave::ArcscriptParser::Variable_listContext *variableListCtx = std::any_cast(argumentList);
+ if (listContext.type() == typeid(Arcweave::ArcscriptParser::Identifier_listContext*)) {
+ auto *variableListCtx = std::any_cast(listContext);
if (variableListCtx != NULL) {
- argListLength = static_cast(variableListCtx->VARIABLE().size());
+ argListLength = static_cast(variableListCtx->identifier().size());
}
}
+ if (argsType == "variable" && listContext.type() == typeid(Arcweave::ArcscriptParser::Argument_listContext*)) {
+ throw Arcweave::ParseErrorException("Invalid arguments type");
+ }
+
if ((min != -1 && argListLength < min) || (max != -1 && argListLength > max)) {
throw Arcweave::ParseErrorException("Wrong number of arguments in \""+ functionName + "\".");
- return false;
}
return true;
diff --git a/Cpp/src/ArcscriptParserBase.h b/Cpp/src/ArcscriptParserBase.h
index 83e0af4..7673d28 100755
--- a/Cpp/src/ArcscriptParserBase.h
+++ b/Cpp/src/ArcscriptParserBase.h
@@ -11,8 +11,8 @@ class ArcscriptParserBase : public antlr4::Parser {
int currentLine = 0;
ArcscriptParserBase(antlr4::TokenStream *input) : Parser(input) { }
inline void setArcscriptState(Arcweave::ArcscriptState *state) { _state = state; };
- bool assertVariable(antlr4::Token *variable);
+ bool assertVariable(antlr4::ParserRuleContext *variable) const;
bool assertMention(std::any attrCtxList);
- bool assertFunctionArguments(antlr4::Token *fname, std::any argumentList);
+ bool assertFunctionArguments(antlr4::Token *fname, std::any listContext);
void setLineStart(antlr4::Token *token);
};
diff --git a/Cpp/src/ArcscriptTranspiler.cpp b/Cpp/src/ArcscriptTranspiler.cpp
index 62d9965..1d0a091 100755
--- a/Cpp/src/ArcscriptTranspiler.cpp
+++ b/Cpp/src/ArcscriptTranspiler.cpp
@@ -75,6 +75,10 @@ UTranspilerOutput* runScriptExport(const char* code, const char* elId, UVariable
var.id = std::string(variables[i].id);
var.name = std::string(variables[i].name);
var.type = variables[i].type;
+
+ if (variables[i].scope != nullptr) {
+ var.scope = std::string(variables[i].scope);
+ }
if (var.type == VariableType::AW_STRING) {
var.value = std::string(variables[i].string_val);
@@ -104,7 +108,11 @@ UTranspilerOutput* runScriptExport(const char* code, const char* elId, UVariable
uTranspilerOutput->type = transpilerOutput.type;
if (transpilerOutput.type == InputType::CONDITION) {
- uTranspilerOutput->conditionResult = std::any_cast(transpilerOutput.result);
+ if (!transpilerOutput.result.has_value()) {
+ uTranspilerOutput->conditionResult = false;
+ } else {
+ uTranspilerOutput->conditionResult = std::any_cast(transpilerOutput.result);
+ }
}
size_t changesLen = transpilerOutput.changes.size();
diff --git a/Cpp/src/ArcscriptTranspiler.h b/Cpp/src/ArcscriptTranspiler.h
index ea00940..9291182 100755
--- a/Cpp/src/ArcscriptTranspiler.h
+++ b/Cpp/src/ArcscriptTranspiler.h
@@ -103,6 +103,7 @@ namespace Arcweave
double double_val;
const char* string_val;
bool bool_val;
+ const char* scope;
UVariable() {
id = nullptr;
@@ -112,6 +113,7 @@ namespace Arcweave
double_val = 0;
string_val = nullptr;
bool_val = false;
+ scope = nullptr;
}
};
diff --git a/Cpp/src/ArcscriptVisitor.cpp b/Cpp/src/ArcscriptVisitor.cpp
index 75f7ec4..c738cb5 100755
--- a/Cpp/src/ArcscriptVisitor.cpp
+++ b/Cpp/src/ArcscriptVisitor.cpp
@@ -8,17 +8,21 @@ using namespace Arcweave;
std::any ArcscriptVisitor::visitInput(ArcscriptParser::InputContext * ctx)
{
- if (ctx->script() != NULL) {
+ if (ctx->script() != nullptr) {
return visitScript(ctx->script());
}
// Condition
- Expression comp_cond = std::any_cast(visitCompound_condition_or(ctx->compound_condition_or()));
+ auto comp_cond = std::any_cast(visitCondition(ctx->condition()));
return comp_cond.isTruthy();
}
+std::any ArcscriptVisitor::visitCondition(ArcscriptParser::ConditionContext * ctx) {
+ return visit(ctx->expression());
+}
+
std::any ArcscriptVisitor::visitScript_section(ArcscriptParser::Script_sectionContext *ctx) {
- if (ctx == NULL) {
- return std::any();
+ if (ctx == nullptr) {
+ return {};
}
if (const auto blockquote_contexts = ctx->blockquote(); !blockquote_contexts.empty())
@@ -54,8 +58,8 @@ std::any ArcscriptVisitor::visitBlockquote(ArcscriptParser::BlockquoteContext* c
std::any ArcscriptVisitor::visitParagraph(ArcscriptParser::ParagraphContext* context)
{
- auto paragraph_end = context->PARAGRAPHEND()->getText();
- auto paragraph_content = paragraph_end.substr(0, paragraph_end.size() - 4); // size of ""
+ const auto paragraph_end = context->PARAGRAPHEND()->getText();
+ const auto paragraph_content = paragraph_end.substr(0, paragraph_end.size() - 4); // size of ""
state->outputs.AddParagraph(paragraph_content);
return context->getText();
}
@@ -68,33 +72,33 @@ std::any ArcscriptVisitor::visitAssignment_segment(ArcscriptParser::Assignment_s
std::any ArcscriptVisitor::visitFunction_call_segment(ArcscriptParser::Function_call_segmentContext *ctx) {
state->outputs.AddScript();
- return visitStatement_function_call(ctx->statement_function_call());
+ return visitFunction_call(ctx->function_call());
}
std::any ArcscriptVisitor::visitConditional_section(ArcscriptParser::Conditional_sectionContext *ctx) {
state->outputs.AddScript();
- ConditionalSection ifSection = std::any_cast(visitIf_section(ctx->if_section()));
+ auto ifSection = std::any_cast(visitIf_section(ctx->if_section()));
if (ifSection.clause) {
state->outputs.AddScript();
return ifSection.script;
}
for (ArcscriptParser::Else_if_sectionContext *else_if_section : ctx->else_if_section()) {
- ConditionalSection elif_section = std::any_cast(visitElse_if_section(else_if_section));
+ auto elif_section = std::any_cast(visitElse_if_section(else_if_section));
if (elif_section.clause) {
return elif_section.script;
}
}
- if (ctx->else_section() != NULL) {
- ConditionalSection elseSection = std::any_cast(visitElse_section(ctx->else_section()));
+ if (ctx->else_section() != nullptr) {
+ const auto elseSection = std::any_cast(visitElse_section(ctx->else_section()));
state->outputs.AddScript();
return elseSection.script;
}
state->outputs.AddScript();
- return std::any();
+ return {};
}
std::any ArcscriptVisitor::visitIf_section(ArcscriptParser::If_sectionContext *ctx) {
- Expression result = std::any_cast(visitIf_clause(ctx->if_clause()));
+ const auto result = std::any_cast(visitIf_clause(ctx->if_clause()));
ConditionalSection ifSection;
ifSection.clause = false;
if (result == true) {
@@ -105,7 +109,7 @@ std::any ArcscriptVisitor::visitIf_section(ArcscriptParser::If_sectionContext *c
}
std::any ArcscriptVisitor::visitElse_if_section(ArcscriptParser::Else_if_sectionContext *ctx) {
- Expression result = std::any_cast(visitElse_if_clause(ctx->else_if_clause()));
+ const auto result = std::any_cast(visitElse_if_clause(ctx->else_if_clause()));
ConditionalSection elseSection;
elseSection.clause = false;
if (result == true) {
@@ -124,292 +128,205 @@ std::any ArcscriptVisitor::visitElse_section(ArcscriptParser::Else_sectionContex
}
std::any ArcscriptVisitor::visitIf_clause(ArcscriptParser::If_clauseContext *ctx) {
- return visitCompound_condition_or(ctx->compound_condition_or());
+ return visit(ctx->expression());
}
std::any ArcscriptVisitor::visitElse_if_clause(ArcscriptParser::Else_if_clauseContext *ctx) {
- return visitCompound_condition_or(ctx->compound_condition_or());
+ return visit(ctx->expression());
}
std::any ArcscriptVisitor::visitStatement_assignment(ArcscriptParser::Statement_assignmentContext *ctx) {
- std::string variableName = ctx->VARIABLE()->getText();
- Expression compound_condition_or = std::any_cast(visitCompound_condition_or(ctx->compound_condition_or()));
- if (ctx->ASSIGN() != NULL) {
- state->setVarValue(variableName, compound_condition_or.value);
- return std::any();
- }
+ auto identifier = std::any_cast(visitAssignable(ctx->assignable()));
+ auto identifierExpr = Expression();
+ identifierExpr.setValue(state->getVarValue(identifier));
- Expression varValue;
- varValue.setValue(state->getVarValue(variableName));
+ auto re = std::any_cast(visit(ctx->expression()));
- if (ctx->ASSIGNADD() != NULL) {
- varValue += compound_condition_or;
- }
- if (ctx->ASSIGNSUB() != NULL) {
- varValue -= compound_condition_or;
- }
- if (ctx->ASSIGNMUL() != NULL) {
- varValue *= compound_condition_or;
- }
- if (ctx->ASSIGNDIV() != NULL) {
- varValue /= compound_condition_or;
- }
- if (ctx->ASSIGNMOD() != NULL) {
- varValue %= compound_condition_or;
+ if (ctx->ASSIGN() != nullptr) {
+ state->setVarValue(identifier, re.value);
+ return {};
}
- state->setVarValue(variableName, varValue.value);
- return std::any();
+ Expression varValue;
+ varValue.setValue(state->getVarValue(identifier));
+
+ if (ctx->ASSIGNADD() != nullptr) {
+ varValue += re;
+ } else if (ctx->ASSIGNSUB() != nullptr) {
+ varValue -= re;
+ } else if (ctx->ASSIGNMUL() != nullptr) {
+ varValue *= re;
+ } else if (ctx->ASSIGNDIV() != nullptr) {
+ varValue /= re;
+ } else if (ctx->ASSIGNMOD() != nullptr) {
+ varValue %= re;
+ }
+
+ state->setVarValue(identifier, varValue.value);
+ return {};
}
-std::any ArcscriptVisitor::visitCompound_condition_or(ArcscriptParser::Compound_condition_orContext *ctx) {
- std::any cond_any = visitCompound_condition_and(ctx->compound_condition_and());
- Expression compound_condition_and = std::any_cast(cond_any);
- if (ctx->compound_condition_or() != NULL) {
- Expression compound_condition_or = std::any_cast(visitCompound_condition_or(ctx->compound_condition_or()));
- Expression result(compound_condition_and || compound_condition_or);
- return result;
- }
- return compound_condition_and;
+std::any ArcscriptVisitor::visitAssignable(ArcscriptParser::AssignableContext *ctx) {
+ return visitIdentifier(ctx->identifier());
}
-std::any ArcscriptVisitor::visitCompound_condition_and(ArcscriptParser::Compound_condition_andContext *ctx) {
- std::any cond_any = visitNegated_unary_condition(ctx->negated_unary_condition());
- Expression negated_unary_condition = std::any_cast(cond_any);
- if (ctx->compound_condition_and() != NULL) {
- Expression compound_condition_and = std::any_cast(visitCompound_condition_and(ctx->compound_condition_and()));
- Expression result(negated_unary_condition && compound_condition_and);
- return result;
+std::any ArcscriptVisitor::visitComparisonExpression(ArcscriptParser::ComparisonExpressionContext *ctx) {
+ auto left = std::any_cast(visit(ctx->expression(0)));
+ if (ctx->AND() != nullptr || ctx->ANDKEYWORD() != nullptr) {
+ if (!left.isTruthy()) {
+ return Expression(false);
+ }
+ auto right = std::any_cast(visit(ctx->expression(1)));
+ return Expression(right.isTruthy());
}
- return negated_unary_condition;
-}
-std::any ArcscriptVisitor::visitNegated_unary_condition(ArcscriptParser::Negated_unary_conditionContext *ctx) {
- Expression unary_condition = std::any_cast(visitUnary_condition(ctx->unary_condition()));
- if (ctx->NEG() != NULL || ctx->NOTKEYWORD() != NULL) {
- return Expression(!unary_condition);
+ if (ctx->OR() != nullptr || ctx->ORKEYWORD() != nullptr) {
+ if (left.isTruthy()) {
+ return Expression(true);
+ }
+ auto right = std::any_cast(visit(ctx->expression(1)));
+ return right;
}
- return unary_condition;
-}
-std::any ArcscriptVisitor::visitCondition(ArcscriptParser::ConditionContext *ctx) {
- if (ctx->expression().size() == 1) {
- return visitExpression(ctx->expression()[0]);
+ auto right = std::any_cast(visit(ctx->expression(1)));
- // Expression expr = std::any_cast(exprAny);
- // if (expr.type() == typeid(double)) {
- // return expr > 0.0;
- // }
- // else if (expr.type() == typeid(int)) {
- // return expr > 0;
- // }
- // else if (expr.type() == typeid(std::string)) {
- // return expr != "";
- // }
- // return std::any_cast(expr.value);
+ if (ctx->EQ() != nullptr || (ctx->ISKEYWORD() != nullptr && ctx->NOTKEYWORD() == nullptr)) {
+ return Expression(left == right);
}
- ArcscriptParser::Conditional_operatorContext *cond_operator_ctx = ctx->conditional_operator();
- Expression exp0 = std::any_cast(visitExpression(ctx->expression()[0]));
- Expression exp1 = std::any_cast(visitExpression(ctx->expression()[1]));
- bool result = false;
- if (cond_operator_ctx->GT() != NULL) {
- result = exp0 > exp1;
+ if (ctx->NE() != nullptr || (ctx->ISKEYWORD() != nullptr && ctx->NOTKEYWORD() != nullptr)) {
+ return Expression(left != right);
}
- else if (cond_operator_ctx->GE() != NULL) {
- result = exp0 >= exp1;
+
+ if (ctx->LT() != nullptr) {
+ return Expression(left < right);
}
- else if (cond_operator_ctx->LT() != NULL) {
- result = exp0 < exp1;
+
+ if (ctx->GT() != nullptr) {
+ return Expression(left > right);
}
- else if (cond_operator_ctx->LE() != NULL) {
- result = exp0 <= exp1;
+
+ if (ctx->LE() != nullptr) {
+ return Expression(left <= right);
+ }
+
+ if (ctx->GE() != nullptr) {
+ return Expression(left >= right);
}
- else if (cond_operator_ctx->EQ() != NULL) {
- result = exp0 == exp1;
+
+ throw RuntimeErrorException("Invalid comparison operator");
+}
+
+std::any ArcscriptVisitor::visitUnaryExpression(ArcscriptParser::UnaryExpressionContext *ctx) {
+ if (ctx->NOTKEYWORD() != nullptr || ctx->NEG() != nullptr) {
+ auto operand = std::any_cast(visit(ctx->expression()));
+ return Expression(!operand);
}
- else if (cond_operator_ctx->NE() != NULL) {
- result = exp0 != exp1;
+ if (ctx->ADD() != nullptr) {
+ return visit(ctx->expression());
}
- else if (cond_operator_ctx->ISKEYWORD() != NULL) {
- if (cond_operator_ctx->NOTKEYWORD() != NULL) {
- result = exp0 != exp1;
- } else {
- result = exp0 == exp1;
- }
+ if (ctx->SUB() != nullptr) {
+ auto operand = std::any_cast(visit(ctx->expression()));
+ return -operand;
}
- return Expression(result);
+ throw RuntimeErrorException("Invalid unary operator");
}
-std::any ArcscriptVisitor::visitExpression(ArcscriptParser::ExpressionContext *ctx) {
- if (ctx->STRING() != NULL) {
- std::string result = ctx->STRING()->getText();
- result = result.substr(1, result.size() - 2);
- return Expression(result);
+std::any ArcscriptVisitor::visitMultiplicativeExpression(ArcscriptParser::MultiplicativeExpressionContext *ctx) {
+ auto left = std::any_cast(visit(ctx->expression(0)));
+ auto right = std::any_cast(visit(ctx->expression(1)));
+ if (ctx->MUL() != nullptr) {
+ return left * right;
}
- if (ctx->BOOLEAN() != NULL) {
- return Expression(ctx->BOOLEAN()->getText() == "true");
+ if (ctx->DIV() != nullptr) {
+ return left / right;
}
-
- return visitAdditive_numeric_expression(ctx->additive_numeric_expression());
+ if (ctx->MOD() != nullptr) {
+ return left % right;
+ }
+ throw RuntimeErrorException("Invalid multiplicative operator");
}
-std::any ArcscriptVisitor::visitAdditive_numeric_expression(ArcscriptParser::Additive_numeric_expressionContext *ctx) {
- if (ctx->additive_numeric_expression() != NULL) {
- Expression result = std::any_cast(visitAdditive_numeric_expression(ctx->additive_numeric_expression()));
- Expression mult_num_expr = std::any_cast(visitMultiplicative_numeric_expression(ctx->multiplicative_numeric_expression()));
-
- if (ctx->ADD() != NULL) {
- mult_num_expr = result + mult_num_expr;
- }
- else if (ctx->SUB() != NULL) {
- mult_num_expr = result - mult_num_expr;
- }
- return mult_num_expr;
+std::any ArcscriptVisitor::visitAdditiveExpression(ArcscriptParser::AdditiveExpressionContext *ctx) {
+ auto left = std::any_cast(visit(ctx->expression(0)));
+ auto right = std::any_cast(visit(ctx->expression(1)));
+ if (ctx->ADD() != nullptr) {
+ return left + right;
}
- return std::any_cast(visitMultiplicative_numeric_expression(ctx->multiplicative_numeric_expression()));
+ if (ctx->SUB() != nullptr) {
+ return left - right;
+ }
+ throw RuntimeErrorException("Invalid additive operator");
}
-std::any ArcscriptVisitor::visitMultiplicative_numeric_expression(ArcscriptParser::Multiplicative_numeric_expressionContext *ctx) {
- if (ctx->multiplicative_numeric_expression() != NULL) {
- Expression result = std::any_cast(visitMultiplicative_numeric_expression(ctx->multiplicative_numeric_expression()));
- Expression signed_unary_num_expr = std::any_cast(visitSigned_unary_numeric_expression(ctx->signed_unary_numeric_expression()));
-
- if (ctx->MUL() != NULL) {
- signed_unary_num_expr = result * signed_unary_num_expr;
- }
- else if (ctx->DIV() != NULL) {
- signed_unary_num_expr = result / signed_unary_num_expr;
- } else if (ctx->MOD() != NULL) {
- signed_unary_num_expr = result % signed_unary_num_expr;
- }
- return signed_unary_num_expr;
- }
+std::any ArcscriptVisitor::visitParenthesizedExpression(ArcscriptParser::ParenthesizedExpressionContext *ctx) {
+ return visit(ctx->expression());
+}
- return std::any_cast(visitSigned_unary_numeric_expression(ctx->signed_unary_numeric_expression()));
+std::any ArcscriptVisitor::visitIdentifierExpression(ArcscriptParser::IdentifierExpressionContext *ctx) {
+ auto idDef = std::any_cast(visitIdentifier(ctx->identifier()));
+ auto result = Expression();
+ result.setValue(state->getVarValue(idDef));
+ return result;
}
-std::any ArcscriptVisitor::visitSigned_unary_numeric_expression(ArcscriptParser::Signed_unary_numeric_expressionContext *ctx) {
- std::any expr_any = visitUnary_numeric_expression(ctx->unary_numeric_expression());
- Expression unary_num_expr = std::any_cast(expr_any);
- ArcscriptParser::SignContext *sign = ctx->sign();
+std::any ArcscriptVisitor::visitLiteralExpression(ArcscriptParser::LiteralExpressionContext *ctx) {
+ return visitLiteral(ctx->literal());
+}
- if (sign != NULL) {
- if (sign->ADD() != NULL) {
- return unary_num_expr;
- }
- // Else MINUS
- unary_num_expr = unary_num_expr * (-1);
- }
- return unary_num_expr;
+std::any ArcscriptVisitor::visitFunctionCallExpression(ArcscriptParser::FunctionCallExpressionContext *ctx) {
+ auto result = visitFunction_call(ctx->function_call());
+ Expression e;
+ e.setValue(result);
+ return e;
}
-std::any ArcscriptVisitor::visitUnary_numeric_expression(ArcscriptParser::Unary_numeric_expressionContext *ctx) {
- if (ctx->FLOAT() != NULL) {
- Expression result(std::stod(ctx->FLOAT()->getText()));
- return result;
- }
- if (ctx->INTEGER() != NULL) {
- Expression result(std::stoi(ctx->INTEGER()->getText()));
- return result;
- }
- if (ctx->STRING() != NULL) {
+std::any ArcscriptVisitor::visitLiteral(ArcscriptParser::LiteralContext *ctx) {
+ if (ctx->STRING() != nullptr) {
std::string result = ctx->STRING()->getText();
result = result.substr(1, result.size() - 2);
return Expression(result);
}
- if (ctx->BOOLEAN() != NULL) {
+ if (ctx->BOOLEAN() != nullptr) {
return Expression(ctx->BOOLEAN()->getText() == "true");
}
- if (ctx->VARIABLE() != NULL) {
- std::string variableName = ctx->VARIABLE()->getText();
- std::any varValue = state->getVarValue(variableName);
- if (varValue.type() == typeid(std::string)) {
- Expression result(std::any_cast(varValue));
- return result;
- }
- if (varValue.type() == typeid(bool)) {
- Expression result(std::any_cast(varValue));
- return result;
- }
- if (varValue.type() == typeid(int)) {
- Expression result(std::any_cast(varValue));
- return result;
- }
- if (varValue.type() == typeid(double)) {
- Expression result(std::any_cast(varValue));
- return result;
- }
- }
- if (ctx->function_call() != NULL) {
- std::any resultValue = visitFunction_call(ctx->function_call());
- if (resultValue.type() == typeid(std::string)) {
- Expression result(std::any_cast(resultValue));
- return result;
- }
- if (resultValue.type() == typeid(bool)) {
- Expression result(std::any_cast(resultValue));
- return result;
- }
- if (resultValue.type() == typeid(int)) {
- Expression result(std::any_cast(resultValue));
- return result;
- }
- if (resultValue.type() == typeid(double)) {
- Expression result(std::any_cast(resultValue));
- return result;
- }
- throw Arcweave::RuntimeErrorException("Unknown result type from function call: " + ctx->function_call()->getText() + "\nType: " + resultValue.type().name());
- }
- return visitCompound_condition_or(ctx->compound_condition_or());
+ return visitNumeric_literal(ctx->numeric_literal());
}
-std::any ArcscriptVisitor::visitVoid_function_call(ArcscriptParser::Void_function_callContext *ctx) {
- std::string fname = "";
- std::any result;
- if (ctx->VFNAME() != NULL) {
- fname = ctx->VFNAME()->getText();
- std::vector argument_list_result;
- if (ctx->argument_list() != NULL) {
- argument_list_result = std::any_cast>(visitArgument_list(ctx->argument_list()));
- }
- result = functions->Call(fname, argument_list_result);
- }
-
- if (ctx->VFNAMEVARS() != NULL) {
- fname = ctx->VFNAMEVARS()->getText();
- std::vector variable_list_result;
-
- if (ctx->variable_list() != NULL) {
- variable_list_result = std::any_cast>(visitVariable_list(ctx->variable_list()));
- }
- result = functions->Call(fname, variable_list_result);
+std::any ArcscriptVisitor::visitNumeric_literal(ArcscriptParser::Numeric_literalContext *ctx) {
+ if (ctx->FLOAT() != nullptr) {
+ return Expression(std::stod(ctx->FLOAT()->getText()));
}
- // std::any result = std::invoke(functions->functions[fname], functions, argument_list_result);
-
- return result;
+ return Expression(std::stoi(ctx->INTEGER()->getText()));;
}
std::any ArcscriptVisitor::visitFunction_call(ArcscriptParser::Function_callContext *ctx) {
std::vector argument_list_result;
- if (ctx->argument_list() != NULL) {
+ std::string fname = ctx->FNAME()->getText();
+
+ if (ctx->argument_list() != nullptr) {
argument_list_result = std::any_cast>(visitArgument_list(ctx->argument_list()));
}
- std::string fname = ctx->FNAME()->getText();
- // std::any result = std::invoke(functions->functions[fname], functions, argument_list_result);
+ if (ctx->identifier_list() != nullptr) {
+ auto identifierList = std::any_cast>(visitIdentifier_list(ctx->identifier_list()));
+ if (ArcscriptFunctions::functions[fname].argsType == "variable") {
+ for (size_t i = 0; i < identifierList.size(); i++) {
+ argument_list_result.push_back(state->getVar(identifierList[i]));
+ }
+ } else {
+ for (size_t i = 0; i < identifierList.size(); i++) {
+ auto e = Expression();
+ e.setValue(state->getVarValue(identifierList[i]));
+ argument_list_result.push_back(e);
+ }
+ }
+ }
+
std::any result = functions->Call(fname, argument_list_result);
return result;
}
-std::any ArcscriptVisitor::visitVariable_list(ArcscriptParser::Variable_listContext *ctx) {
- std::vector variables;
- for (antlr4::tree::TerminalNode *variable : ctx->VARIABLE()) {
- variables.push_back(state->getVar(variable->getText()));
- }
- return variables;
-}
-
std::any ArcscriptVisitor::visitArgument_list(ArcscriptParser::Argument_listContext *ctx) {
std::vector arguments;
for (ArcscriptParser::ArgumentContext *argument : ctx->argument()) {
@@ -419,27 +336,48 @@ std::any ArcscriptVisitor::visitArgument_list(ArcscriptParser::Argument_listCont
}
std::any ArcscriptVisitor::visitArgument(ArcscriptParser::ArgumentContext *ctx) {
- if (ctx->STRING() != NULL) {
- std::string result = ctx->STRING()->getText();
- result = result.substr(1, result.size() - 2);
- return Expression(result);
+ if (ctx->expression() != nullptr) {
+ return visit(ctx->expression());
}
- if (ctx->mention() != NULL) {
- Mention mention_result = std::any_cast(visitMention(ctx->mention()));
+ if (ctx->mention() != nullptr) {
+ auto mention_result = std::any_cast(visitMention(ctx->mention()));
return mention_result;
}
- return visitAdditive_numeric_expression(ctx->additive_numeric_expression());
+ throw RuntimeErrorException("Invalid function argument");
+}
+
+std::any ArcscriptVisitor::visitIdentifier_list(ArcscriptParser::Identifier_listContext *ctx) {
+ std::vector identifiers;
+ for (auto *id : ctx->identifier()) {
+ identifiers.push_back(std::any_cast(visitIdentifier(id)));
+ }
+ return identifiers;
+}
+
+std::any ArcscriptVisitor::visitIdentifier(ArcscriptParser::IdentifierContext *ctx) {
+ std::string name;
+ std::string scope;
+ if (ctx->IDENTIFIER().size() == 1) {
+ name = ctx->IDENTIFIER(0)->getText();
+ } else {
+ scope = ctx->IDENTIFIER(0)->getText();
+ name = ctx->IDENTIFIER(1)->getText();
+ }
+ IdentifierDef def;
+ def.name = name;
+ def.scope = scope;
+ return def;
}
std::any ArcscriptVisitor::visitMention(ArcscriptParser::MentionContext *ctx) {
std::map attrs;
for (ArcscriptParser::Mention_attributesContext *attr : ctx->mention_attributes()) {
- std::map res = std::any_cast>(visitMention_attributes(attr));
+ auto res = std::any_cast>(visitMention_attributes(attr));
attrs[res["name"]] = res["value"];
}
- std::string label = "";
- if (ctx->MENTION_LABEL() != NULL) {
+ std::string label;
+ if (ctx->MENTION_LABEL() != nullptr) {
label = ctx->MENTION_LABEL()->getText();
}
Mention mention(label, attrs);
@@ -457,10 +395,10 @@ std::any ArcscriptVisitor::visitMention_attributes(ArcscriptParser::Mention_attr
antlr4::tree::TerminalNode *valueNode = ctx->ATTR_VALUE();
std::string value(name);
- if (valueNode != NULL) {
+ if (valueNode != nullptr) {
std::string strvalue = valueNode->getText();
- if ((strvalue.rfind("\"", 0) == 0 && ends_with(strvalue, "\"")) ||
- (strvalue.rfind("'", 0) == 0 && ends_with(strvalue, "'"))) {
+ if ((strvalue.rfind('\"', 0) == 0 && ends_with(strvalue, "\"")) ||
+ (strvalue.rfind('\'', 0) == 0 && ends_with(strvalue, "'"))) {
strvalue = strvalue.substr(1, strvalue.size() - 2);
diff --git a/Cpp/src/ArcscriptVisitor.h b/Cpp/src/ArcscriptVisitor.h
index 41fa6e4..7cbe9f6 100755
--- a/Cpp/src/ArcscriptVisitor.h
+++ b/Cpp/src/ArcscriptVisitor.h
@@ -24,6 +24,9 @@ class ArcscriptVisitor : public ArcscriptParserBaseVisitor {
std::any visitInput(ArcscriptParser::InputContext *ctx) override;
std::any visitScript_section(ArcscriptParser::Script_sectionContext *ctx) override;
+ std::any visitCondition(ArcscriptParser::ConditionContext *ctx) override;
+ std::any visitParagraph(ArcscriptParser::ParagraphContext* context) override;
+ std::any visitBlockquote(ArcscriptParser::BlockquoteContext* context) override;
std::any visitAssignment_segment(ArcscriptParser::Assignment_segmentContext *ctx) override;
std::any visitFunction_call_segment(ArcscriptParser::Function_call_segmentContext * ctx) override;
std::any visitConditional_section(ArcscriptParser::Conditional_sectionContext *ctx) override;
@@ -33,22 +36,22 @@ class ArcscriptVisitor : public ArcscriptParserBaseVisitor {
std::any visitIf_clause(ArcscriptParser::If_clauseContext *ctx) override;
std::any visitElse_if_clause(ArcscriptParser::Else_if_clauseContext *ctx) override;
std::any visitStatement_assignment(ArcscriptParser::Statement_assignmentContext *ctx) override;
- std::any visitCompound_condition_or(ArcscriptParser::Compound_condition_orContext *ctx) override;
- std::any visitCompound_condition_and(ArcscriptParser::Compound_condition_andContext *ctx) override;
- std::any visitNegated_unary_condition(ArcscriptParser::Negated_unary_conditionContext *ctx) override;
- std::any visitCondition(ArcscriptParser::ConditionContext *ctx) override;
- std::any visitExpression(ArcscriptParser::ExpressionContext *ctx) override;
- std::any visitAdditive_numeric_expression(ArcscriptParser::Additive_numeric_expressionContext *ctx) override;
- std::any visitMultiplicative_numeric_expression(ArcscriptParser::Multiplicative_numeric_expressionContext *ctx) override;
- std::any visitSigned_unary_numeric_expression(ArcscriptParser::Signed_unary_numeric_expressionContext *ctx) override;
- std::any visitUnary_numeric_expression(ArcscriptParser::Unary_numeric_expressionContext *ctx) override;
- std::any visitVoid_function_call(ArcscriptParser::Void_function_callContext *ctx) override;
+ std::any visitAssignable(ArcscriptParser::AssignableContext *context) override;
+ std::any visitIdentifier(ArcscriptParser::IdentifierContext *context) override;
+ std::any visitComparisonExpression(ArcscriptParser::ComparisonExpressionContext *context) override;
+ std::any visitUnaryExpression(ArcscriptParser::UnaryExpressionContext *context) override;
+ std::any visitMultiplicativeExpression(ArcscriptParser::MultiplicativeExpressionContext *ctx) override;
+ std::any visitAdditiveExpression(ArcscriptParser::AdditiveExpressionContext *context) override;
+ std::any visitParenthesizedExpression(ArcscriptParser::ParenthesizedExpressionContext *context) override;
+ std::any visitIdentifierExpression(ArcscriptParser::IdentifierExpressionContext *context) override;
+ std::any visitLiteralExpression(ArcscriptParser::LiteralExpressionContext *context) override;
+ std::any visitFunctionCallExpression(ArcscriptParser::FunctionCallExpressionContext *context) override;
+ std::any visitLiteral(ArcscriptParser::LiteralContext *context) override;
+ std::any visitNumeric_literal(ArcscriptParser::Numeric_literalContext *context) override;
std::any visitFunction_call(ArcscriptParser::Function_callContext *ctx) override;
- std::any visitVariable_list(ArcscriptParser::Variable_listContext *ctx) override;
std::any visitArgument_list(ArcscriptParser::Argument_listContext *ctx) override;
std::any visitArgument(ArcscriptParser::ArgumentContext *ctx) override;
+ std::any visitIdentifier_list(ArcscriptParser::Identifier_listContext *context) override;
std::any visitMention(ArcscriptParser::MentionContext *ctx) override;
std::any visitMention_attributes(ArcscriptParser::Mention_attributesContext *ctx) override;
- std::any visitParagraph(ArcscriptParser::ParagraphContext* context) override;
- std::any visitBlockquote(ArcscriptParser::BlockquoteContext* context) override;
};
diff --git a/TypeScript/package.json b/TypeScript/package.json
index c9011c2..3a8acf8 100644
--- a/TypeScript/package.json
+++ b/TypeScript/package.json
@@ -45,6 +45,7 @@
"jiti": "^2.6.1",
"pkg-pr-new": "^0.0.63",
"prettier": "3.8.1",
+ "typescript": "^5.9.3",
"typescript-eslint": "^8.56.0",
"vitest": "^4.0.18"
},
diff --git a/TypeScript/pnpm-lock.yaml b/TypeScript/pnpm-lock.yaml
index ad4a31b..ba7bd33 100644
--- a/TypeScript/pnpm-lock.yaml
+++ b/TypeScript/pnpm-lock.yaml
@@ -45,6 +45,9 @@ importers:
prettier:
specifier: 3.8.1
version: 3.8.1
+ typescript:
+ specifier: ^5.9.3
+ version: 5.9.3
typescript-eslint:
specifier: ^8.56.0
version: 8.56.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
diff --git a/TypeScript/src/ArcscriptFunctions.ts b/TypeScript/src/ArcscriptFunctions.ts
index cdbe6d8..cb6d1c5 100644
--- a/TypeScript/src/ArcscriptFunctions.ts
+++ b/TypeScript/src/ArcscriptFunctions.ts
@@ -1,6 +1,7 @@
import { RuntimeError } from './errors/index.js';
import ArcscriptState from './ArcscriptState.js';
import { MentionResult, VarValue } from './types.js';
+import ArcscriptVariable from './ArcscriptVariable.js';
export type FunctionName = keyof ArcscriptFunctions;
@@ -19,7 +20,7 @@ export type ArcscriptNonVoidFunctionKeys = Exclude<
ArcscriptVoidFunctionKeys
>;
-type ArgumentTypes = (VarValue | MentionResult)[];
+type ArgumentTypes = (VarValue | MentionResult | ArcscriptVariable)[];
export default class ArcscriptFunctions {
private state: ArcscriptState;
@@ -104,37 +105,29 @@ export default class ArcscriptFunctions {
}
reset(...args: ArgumentTypes): void {
- const ids = args.map(name => {
- if (typeof name !== 'string' || !this.state.getVar(name)) {
+ args.forEach(variable => {
+ if (!(variable instanceof ArcscriptVariable)) {
throw new RuntimeError(
- `Invalid argument ${name} in function resetAll. Expected a variable`
+ `Invalid argument ${variable} in function reset. Expected a variable`
);
}
- const v = this.state.getVar(name);
- if (!v) {
- throw new RuntimeError(`Variable ${name} not found`);
- }
- return v.id;
+ variable.reset();
});
- this.state.resetVarValues(ids);
}
resetAll(...args: ArgumentTypes): void {
- const except = args.map(name => {
- if (typeof name !== 'string' || !this.state.getVar(name)) {
+ const except = args.map(variable => {
+ if (!(variable instanceof ArcscriptVariable)) {
throw new RuntimeError(
- `Invalid argument ${name} in function resetAll. Expected a variable`
+ `Invalid argument ${variable} in function resetAll. Expected a variable`
);
}
- const v = this.state.getVar(name);
- if (!v) {
- throw new RuntimeError(`Variable ${name} not found`);
- }
- return v.id;
+ return variable.id;
});
- const all = Object.keys(this.state.getInitialVarValues());
- const resetIds = all.filter(id => !except.includes(id));
- this.state.resetVarValues(resetIds);
+ const variablesToReset = Object.values(this.state.variables).filter(
+ v => !except.includes(v.id)
+ );
+ variablesToReset.forEach(variable => variable.reset());
}
round(...args: ArgumentTypes): number {
@@ -157,7 +150,7 @@ export default class ArcscriptFunctions {
visits(...args: ArgumentTypes): number {
let elementId = this.state.currentElement;
if (args.length > 0) {
- const mention = args[0];
+ const mention = args[0] as MentionResult;
if (
typeof mention !== 'object' ||
typeof mention.attrs['data-id'] !== 'string'
@@ -185,7 +178,10 @@ export default class ArcscriptFunctions {
* @param {string} name The function name
* @param {VarValue} arg The argument to check
*/
- private assertNumber(name: string, arg: VarValue | MentionResult) {
+ private assertNumber(
+ name: string,
+ arg: VarValue | MentionResult | ArcscriptVariable
+ ) {
if (typeof arg !== 'number' || Number.isNaN(arg)) {
throw new RuntimeError(
`Invalid argument ${arg} in function ${name}. Expected number (integer or float)`
@@ -198,7 +194,10 @@ export default class ArcscriptFunctions {
* @param {string} name The function name
* @param {VarValue} arg The argument to check
*/
- private assertPositiveInteger(name: string, arg: VarValue | MentionResult) {
+ private assertPositiveInteger(
+ name: string,
+ arg: VarValue | MentionResult | ArcscriptVariable
+ ) {
if (typeof arg !== 'number' || Number.isNaN(arg)) {
throw new RuntimeError(
`Invalid argument ${arg} in function ${name}. Expected number (integer)`
diff --git a/TypeScript/src/ArcscriptState.ts b/TypeScript/src/ArcscriptState.ts
index 56906e7..0169f43 100644
--- a/TypeScript/src/ArcscriptState.ts
+++ b/TypeScript/src/ArcscriptState.ts
@@ -1,5 +1,46 @@
-import cloneDeep from 'lodash.clonedeep';
-import { VarObject, VarValue } from './types.js';
+import { ArcscriptStateDef, VarDef, VarValue } from './types.js';
+import ArcscriptVariable from './ArcscriptVariable.js';
+
+function hasProperty(obj: T, prop: keyof T): boolean {
+ if (Object.hasOwn) {
+ return Object.hasOwn(obj, prop);
+ }
+ return Object.prototype.hasOwnProperty.call(obj, prop);
+}
+
+function validateVarDef(variableId: string, varDef: VarDef) {
+ if (!hasProperty(varDef, 'id')) {
+ throw new Error(`Variable ${varDef.name} is missing id property`);
+ }
+ if (varDef.id !== variableId) {
+ throw new Error(
+ `Variable key ${variableId} does not match id property ${varDef.id}`
+ );
+ }
+ if (!hasProperty(varDef, 'name')) {
+ throw new Error(`Variable ${varDef.id} is missing name property`);
+ }
+ if (!hasProperty(varDef, 'type')) {
+ throw new Error(`Variable ${varDef.id} is missing type property`);
+ }
+ if (!hasProperty(varDef, 'defaultValue')) {
+ throw new Error(`Variable ${varDef.id} is missing defaultValue property`);
+ }
+ if (
+ hasProperty(varDef, 'scope') &&
+ varDef.scope !== undefined &&
+ varDef.scope !== null &&
+ typeof varDef.scope !== 'string'
+ ) {
+ throw new Error(`Variable ${varDef.id} has invalid scope property`);
+ }
+}
+
+function validateStateDef(stateDef: ArcscriptStateDef) {
+ Object.entries(stateDef).forEach(([id, varDef]) => {
+ validateVarDef(id, varDef);
+ });
+}
type OutputObject = {
output?: string;
@@ -10,13 +51,12 @@ type OutputObject = {
};
export default class ArcscriptState {
- varValues: Record;
- varObjects: Record;
+ variables: Record;
+
elementVisits: Record;
currentElement: string;
outputs: OutputObject[];
conditionDepth: number;
- changes: Record;
emit: (event: string, data?: unknown) => void;
outputDoc: Document;
rootElement: HTMLElement;
@@ -24,19 +64,18 @@ export default class ArcscriptState {
insertBlockquote: boolean;
constructor(
- varValues: Record,
- varObjects: Record,
+ arcscriptVariables: ArcscriptStateDef,
elementVisits: Record,
currentElement: string,
emit: (event: string, data?: unknown) => void
) {
- this.varValues = varValues;
- this.varObjects = varObjects;
+ this.variables = this.initializeVariables(arcscriptVariables);
+
this.elementVisits = elementVisits;
this.currentElement = currentElement;
this.outputs = [];
this.conditionDepth = 0;
- this.changes = {};
+
this.emit = emit;
this.outputDoc = document.implementation.createHTMLDocument();
@@ -46,33 +85,53 @@ export default class ArcscriptState {
this.insertBlockquote = false;
}
- getVar(name: string) {
- return Object.values(this.varObjects).find(v => v.name === name);
+ initializeVariables(arcscriptVariables: ArcscriptStateDef) {
+ validateStateDef(arcscriptVariables);
+ const variables: Record = {};
+ Object.entries(arcscriptVariables).forEach(([id, varDef]) => {
+ variables[id] = new ArcscriptVariable({
+ id,
+ name: varDef.name,
+ type: varDef.type,
+ defaultValue: varDef.defaultValue,
+ value: varDef.value,
+ scope: varDef.scope,
+ });
+ });
+ return variables;
}
- getVarValue(id: string) {
- if (id in this.changes) return this.changes[id];
- return this.varValues[id];
+ getVar(name: string, scope: string | null = null): ArcscriptVariable {
+ const variable = Object.values(this.variables).find(v => {
+ if (scope) {
+ return v.name === name && v.scope === scope;
+ }
+ return v.name === name;
+ });
+ if (!variable) {
+ throw new Error(`Variable ${name} not found`);
+ }
+ return variable;
}
- setVarValues(ids: string[], values: VarValue[]) {
- ids.forEach((id, index) => {
- this.changes[id] = values[index];
+ setVarValues(values: Record) {
+ Object.entries(values).forEach(([id, value]) => {
+ if (this.variables[id]) {
+ this.variables[id].setValue(value);
+ }
});
}
- getInitialVarValues() {
- return Object.fromEntries(
- Object.entries(this.varObjects)
- .filter(([, v]) => !v.children)
- .map(([k, v]) => [k, v.value])
+ getChanges() {
+ const changes: Record = {};
+ Object.entries(this.variables).forEach(
+ ([id, variable]: [string, ArcscriptVariable]) => {
+ if (variable.changed) {
+ changes[id] = variable.getValue();
+ }
+ }
);
- }
-
- resetVarValues(ids: string[]) {
- const initial = cloneDeep(this.getInitialVarValues());
- const values = ids.map(id => initial[id]);
- this.setVarValues(ids, values);
+ return changes;
}
/**
@@ -210,7 +269,7 @@ export default class ArcscriptState {
* Concatenates the outputs and transforms them to a single string
* @returns {String} The output to be shown
*/
- generateOutput() {
+ generateOutput(): string {
return this.rootElement.innerHTML;
}
diff --git a/TypeScript/src/ArcscriptVariable.ts b/TypeScript/src/ArcscriptVariable.ts
new file mode 100644
index 0000000..11e2f9b
--- /dev/null
+++ b/TypeScript/src/ArcscriptVariable.ts
@@ -0,0 +1,60 @@
+import { VarValue } from './types.js';
+
+type ArcscriptVariableParams = {
+ id: string;
+ name: string;
+ type: string;
+ defaultValue: VarValue;
+ value?: VarValue;
+ scope?: string | null;
+};
+
+export default class ArcscriptVariable {
+ id: string;
+ name: string;
+ type: string;
+ value: VarValue;
+ defaultValue: VarValue;
+ changed = false;
+ scope: string | null;
+
+ constructor({
+ id,
+ name,
+ defaultValue,
+ type,
+ value,
+ scope,
+ }: ArcscriptVariableParams) {
+ this.id = id;
+ this.name = name;
+ this.type = type;
+ this.defaultValue = defaultValue;
+ this.scope = scope || null;
+
+ if (value !== undefined) {
+ this.value = value;
+ this.changed = true;
+ } else {
+ this.value = defaultValue;
+ }
+ }
+
+ reset() {
+ this.value = this.defaultValue;
+ this.changed = true;
+ }
+
+ getValue() {
+ return this.value;
+ }
+
+ setValue(newValue: VarValue) {
+ this.value = newValue;
+ this.changed = true;
+ }
+
+ getType() {
+ return this.type;
+ }
+}
diff --git a/TypeScript/src/ArcscriptVisitor.ts b/TypeScript/src/ArcscriptVisitor.ts
index ded70ea..86987f8 100644
--- a/TypeScript/src/ArcscriptVisitor.ts
+++ b/TypeScript/src/ArcscriptVisitor.ts
@@ -2,65 +2,56 @@ import BigNumber from 'bignumber.js';
import ArcscriptParserVisitor from './Generated/ArcscriptParserVisitor.js';
import ArcscriptFunctions, {
ArcscriptNonVoidFunctionKeys,
- ArcscriptVoidFunctionKeys,
} from './ArcscriptFunctions.js';
import ArcscriptState from './ArcscriptState.js';
import { RuntimeError } from './errors/index.js';
-import { MentionResult, VarObject, VarValue } from './types.js';
+import { MentionResult, VarValue } from './types.js';
import {
- Additive_numeric_expressionContext,
+ AdditiveExpressionContext,
Argument_listContext,
ArgumentContext,
+ AssignableContext,
Assignment_segmentContext,
BlockquoteContext,
- Compound_condition_andContext,
- Compound_condition_orContext,
+ ComparisonExpressionContext,
Conditional_sectionContext,
ConditionContext,
Else_if_clauseContext,
Else_if_sectionContext,
Else_sectionContext,
- ExpressionContext,
Function_call_segmentContext,
Function_callContext,
+ FunctionCallExpressionContext,
+ Identifier_listContext,
+ IdentifierContext,
+ IdentifierExpressionContext,
If_clauseContext,
If_sectionContext,
InputContext,
+ LiteralContext,
+ LiteralExpressionContext,
Mention_attributesContext,
MentionContext,
- Multiplicative_numeric_expressionContext,
- Negated_unary_conditionContext,
+ MultiplicativeExpressionContext,
+ Numeric_literalContext,
ParagraphContext,
+ ParenthesizedExpressionContext,
Script_sectionContext,
- Signed_unary_numeric_expressionContext,
Statement_assignmentContext,
- Unary_conditionContext,
- Unary_numeric_expressionContext,
- Variable_listContext,
- Void_function_callContext,
+ UnaryExpressionContext,
} from './Generated/ArcscriptParser.js';
+import ArcscriptVariable from './ArcscriptVariable.js';
+import ArcscriptParserBase from './Generated/ArcscriptParserBase.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default class ArcscriptVisitor extends ArcscriptParserVisitor {
state: ArcscriptState;
functions: ArcscriptFunctions;
- constructor(
- varValues: Record,
- varObjects: Record,
- elementVisits: Record,
- currentElement: string,
- emit: (event: string, data: unknown) => void
- ) {
+ constructor(state: ArcscriptState) {
super();
- this.state = new ArcscriptState(
- varValues,
- varObjects,
- elementVisits,
- currentElement,
- emit
- );
+ this.state = state;
this.functions = new ArcscriptFunctions(this.state);
}
@@ -70,8 +61,12 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor {
return { script: this.visitChildren(ctx.script()) };
}
+ return this.visitCondition(ctx.condition());
+ };
+
+ visitCondition = (ctx: ConditionContext) => {
return {
- condition: this.visitCompound_condition_or(ctx.compound_condition_or()),
+ condition: !!this.visit(ctx.expression()),
};
};
@@ -123,7 +118,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor {
visitFunction_call_segment = (ctx: Function_call_segmentContext) => {
this.state.addScript();
- return this.visitChildren(ctx.statement_function_call());
+ return this.visitFunction_call(ctx.function_call());
};
visitConditional_section = (ctx: Conditional_sectionContext) => {
@@ -181,38 +176,36 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor {
return this.visitChildren(ctx.script());
};
- visitIf_clause = (ctx: If_clauseContext) => {
- return this.visitCompound_condition_or(ctx.compound_condition_or());
+ visitIf_clause = (ctx: If_clauseContext): boolean => {
+ return this.visit(ctx.expression()) as boolean;
};
- visitElse_if_clause = (ctx: Else_if_clauseContext) => {
- return this.visitCompound_condition_or(ctx.compound_condition_or());
+ visitElse_if_clause = (ctx: Else_if_clauseContext): boolean => {
+ return this.visit(ctx.expression()) as boolean;
};
visitStatement_assignment = (ctx: Statement_assignmentContext) => {
- const variableName = ctx.VARIABLE().getText();
- const variableObject = this.state.getVar(variableName);
- if (!variableObject) {
- throw new RuntimeError(`Variable ${variableName} not found`);
- }
+ const assignable = this.visitAssignable(ctx.assignable());
- let variableValue = this.state.getVarValue(variableObject.id);
- let compound_condition_or = this.visitCompound_condition_or(
- ctx.compound_condition_or()
- );
+ let variableValue = assignable.getValue();
+ let expressionValue = this.visit(ctx.expression());
+ if (expressionValue === undefined) {
+ throw new RuntimeError('The right part of the assigment has no value');
+ }
let result: VarValue = 0;
if (
typeof variableValue === 'string' ||
- typeof compound_condition_or === 'string'
+ typeof expressionValue === 'string'
) {
if (ctx.ASSIGNADD()) {
- result = ((variableValue as string) + compound_condition_or) as string;
+ result = ((variableValue as string) + expressionValue) as string;
} else if (ctx.ASSIGN()) {
- result = compound_condition_or;
+ result = expressionValue;
} else {
throw new RuntimeError('Invalid operation with string');
}
- this.state.setVarValues([variableObject.id], [result]);
+
+ assignable.setValue(result);
return null;
}
if (
@@ -225,352 +218,251 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor {
if (typeof variableValue === 'boolean') {
variableValue = variableValue ? 1 : 0;
}
- if (typeof compound_condition_or === 'boolean') {
- compound_condition_or = compound_condition_or ? 1 : 0;
+ if (typeof expressionValue === 'boolean') {
+ expressionValue = expressionValue ? 1 : 0;
}
}
+
+ const leftValue = new BigNumber(variableValue as number);
+ const rightValue = new BigNumber(expressionValue as number);
+
if (ctx.ASSIGNADD()) {
- result = new BigNumber(variableValue as number)
- .plus(new BigNumber(compound_condition_or as number))
- .toNumber();
+ result = leftValue.plus(rightValue).toNumber();
} else if (ctx.ASSIGNSUB()) {
- result = new BigNumber(variableValue as number)
- .minus(new BigNumber(compound_condition_or as number))
- .toNumber();
+ result = leftValue.minus(rightValue).toNumber();
} else if (ctx.ASSIGNMUL()) {
- result = new BigNumber(variableValue as number)
- .multipliedBy(new BigNumber(compound_condition_or as number))
- .toNumber();
+ result = leftValue.multipliedBy(rightValue).toNumber();
} else if (ctx.ASSIGNDIV()) {
- result = new BigNumber(variableValue as number)
- .dividedBy(new BigNumber(compound_condition_or as number))
- .toNumber();
+ result = leftValue.dividedBy(rightValue).toNumber();
if (!Number.isFinite(result)) {
throw new RuntimeError(`Invalid division by zero`);
}
} else if (ctx.ASSIGNMOD()) {
- result = new BigNumber(variableValue as number)
- .modulo(new BigNumber(compound_condition_or as number))
- .toNumber();
+ result = leftValue.modulo(rightValue).toNumber();
if (!Number.isFinite(result)) {
throw new RuntimeError(`Invalid division by zero`);
}
} else if (ctx.ASSIGN()) {
- result = compound_condition_or;
+ result = expressionValue;
}
- this.state.setVarValues([variableObject.id], [result]);
+ assignable.setValue(result);
return null;
};
- visitVoid_function_call = (ctx: Void_function_callContext) => {
- let argument_list: (VarValue | MentionResult)[] = [];
- let fname: ArcscriptVoidFunctionKeys;
- if (ctx.VFNAME()) {
- fname = ctx.VFNAME().getText() as ArcscriptVoidFunctionKeys;
- if (ctx.argument_list()) {
- argument_list = this.visitArgument_list(ctx.argument_list());
+ visitAssignable = (ctx: AssignableContext): ArcscriptVariable => {
+ return this.visitIdentifier(ctx.identifier());
+ };
+
+ visitComparisonExpression = (ctx: ComparisonExpressionContext): boolean => {
+ const left = this.visit(ctx.expression(0));
+ if (ctx.AND() || ctx.ANDKEYWORD()) {
+ if (!left) {
+ return false;
}
+ const right = this.visit(ctx.expression(1));
+ return Boolean(left) && Boolean(right);
}
- if (ctx.VFNAMEVARS()) {
- fname = ctx.VFNAMEVARS().getText() as ArcscriptVoidFunctionKeys;
- if (ctx.variable_list()) {
- argument_list = this.visitVariable_list(ctx.variable_list());
+ if (ctx.OR() || ctx.ORKEYWORD()) {
+ if (left) {
+ return true;
}
+ const right = this.visit(ctx.expression(1));
+ return Boolean(left) || Boolean(right);
}
+ const right = this.visit(ctx.expression(1));
- this.functions[fname!](...argument_list);
- };
-
- visitVariable_list = (ctx: Variable_listContext): string[] => {
- return ctx.VARIABLE_list().map(variable => variable.getText());
- };
-
- visitCompound_condition_or = (
- ctx: Compound_condition_orContext
- ): VarValue => {
- const compound_condition_and = this.visitCompound_condition_and(
- ctx.compound_condition_and()
- );
-
- if (ctx.compound_condition_or()) {
- return (
- compound_condition_and ||
- this.visitCompound_condition_or(ctx.compound_condition_or())
- );
+ if (ctx.EQ() || (ctx.ISKEYWORD() && !ctx.NOTKEYWORD())) {
+ return left === right;
}
-
- return compound_condition_and;
- };
-
- visitCompound_condition_and = (
- ctx: Compound_condition_andContext
- ): VarValue => {
- const negated_unary_condition = this.visitNegated_unary_condition(
- ctx.negated_unary_condition()
- );
-
- if (ctx.compound_condition_and()) {
- return (
- negated_unary_condition &&
- this.visitCompound_condition_and(ctx.compound_condition_and())
- );
+ if (ctx.NE() || (ctx.ISKEYWORD() && ctx.NOTKEYWORD())) {
+ return left !== right;
}
-
- return negated_unary_condition;
- };
-
- visitNegated_unary_condition = (ctx: Negated_unary_conditionContext) => {
- const unary_condition = this.visitUnary_condition(ctx.unary_condition());
-
- if (ctx.NEG() || ctx.NOTKEYWORD()) {
- return !unary_condition;
+ if (ctx.LT()) {
+ return left < right;
}
-
- return unary_condition;
- };
-
- visitUnary_condition = (ctx: Unary_conditionContext) => {
- return this.visitCondition(ctx.condition());
- };
-
- visitCondition = (ctx: ConditionContext): VarValue => {
- if (ctx.expression_list().length === 1) {
- return this.visitExpression(ctx.expression(0));
+ if (ctx.GT()) {
+ return left > right;
}
- const conditional_operator = ctx.conditional_operator();
- const exp0 = this.visitExpression(ctx.expression(0));
- const exp1 = this.visitExpression(ctx.expression(1));
+ if (ctx.LE()) {
+ return left <= right;
+ }
+ if (ctx.GE()) {
+ return left >= right;
+ }
+ throw new RuntimeError('Invalid comparison operator');
+ };
- if (conditional_operator.GT()) {
- return exp0 > exp1;
+ visitUnaryExpression = (ctx: UnaryExpressionContext) => {
+ if (ctx.NOTKEYWORD() || ctx.NEG()) {
+ return !this.visit(ctx.expression());
}
- if (conditional_operator.GE()) {
- return exp0 >= exp1;
+ if (ctx.ADD()) {
+ return this.visit(ctx.expression());
}
- if (conditional_operator.LT()) {
- return exp0 < exp1;
+ if (ctx.SUB()) {
+ return -this.visit(ctx.expression());
}
- if (conditional_operator.LE()) {
- return exp0 <= exp1;
+ throw new RuntimeError('Invalid unary operator');
+ };
+
+ visitMultiplicativeExpression = (ctx: MultiplicativeExpressionContext) => {
+ const left = this.visit(ctx.expression(0));
+ const right = this.visit(ctx.expression(1));
+ if (typeof left === 'string' || typeof right === 'string') {
+ throw new RuntimeError('Invalid operation with string');
}
- if (conditional_operator.EQ()) {
- return exp0 === exp1;
+ const leftValue = new BigNumber(left as number);
+ const rightValue = new BigNumber(right as number);
+
+ if (ctx.MUL()) {
+ return leftValue.multipliedBy(rightValue).toNumber();
}
- if (conditional_operator.NE()) {
- return exp0 !== exp1;
+ if (ctx.DIV()) {
+ if (rightValue.isZero()) {
+ throw new RuntimeError('Invalid division by zero');
+ }
+ return leftValue.dividedBy(rightValue).toNumber();
}
- if (conditional_operator.ISKEYWORD()) {
- if (conditional_operator.NOTKEYWORD()) {
- return exp0 !== exp1;
+ if (ctx.MOD()) {
+ if (rightValue.isZero()) {
+ throw new RuntimeError('Invalid division by zero');
}
- return exp0 === exp1;
+ return leftValue.modulo(rightValue).toNumber();
}
-
- return this.visitChildren(ctx) as VarValue;
+ throw new RuntimeError('Invalid multiplicative operator');
};
- visitExpression = (ctx: ExpressionContext): VarValue => {
- if (ctx.STRING()) {
- let result = ctx.STRING().getText();
- result = result.replace(/^['"](.*)['"]$/, '$1');
- return result;
- }
- if (ctx.BOOLEAN()) {
- if (ctx.BOOLEAN().getText() === 'true') {
- return true;
+ visitAdditiveExpression = (ctx: AdditiveExpressionContext) => {
+ let left = this.visit(ctx.expression(0));
+ let right = this.visit(ctx.expression(1));
+ if (typeof left === 'string' || typeof right === 'string') {
+ if (ctx.ADD()) {
+ return (left as string) + (right as string);
}
- return false;
+ throw new RuntimeError('Invalid operation with string');
}
+ left = Number(left);
+ right = Number(right);
- return this.visitAdditive_numeric_expression(
- ctx.additive_numeric_expression()
- );
- };
-
- visitAdditive_numeric_expression = (
- ctx: Additive_numeric_expressionContext
- ): VarValue => {
- if (ctx.additive_numeric_expression()) {
- let additive_numeric_expression = this.visitAdditive_numeric_expression(
- ctx.additive_numeric_expression()
- );
-
- let multiplicative_numeric_expression =
- this.visitMultiplicative_numeric_expression(
- ctx.multiplicative_numeric_expression()
- );
-
- let hasString = typeof multiplicative_numeric_expression === 'string';
+ const leftValue = new BigNumber(left as number);
+ const rightValue = new BigNumber(right as number);
- hasString = hasString || typeof additive_numeric_expression === 'string';
-
- if (!hasString) {
- if (typeof multiplicative_numeric_expression === 'boolean') {
- multiplicative_numeric_expression = multiplicative_numeric_expression
- ? 1
- : 0;
- }
- if (typeof additive_numeric_expression === 'boolean') {
- additive_numeric_expression = additive_numeric_expression ? 1 : 0;
- }
- }
-
- if (ctx.ADD()) {
- if (hasString) {
- return ((additive_numeric_expression as string) +
- multiplicative_numeric_expression) as string;
- }
- return new BigNumber(additive_numeric_expression as number)
- .plus(new BigNumber(multiplicative_numeric_expression as number))
- .toNumber();
- }
- if (hasString) {
- throw new RuntimeError('Invalid subtraction with string');
- }
- return new BigNumber(additive_numeric_expression as number)
- .minus(new BigNumber(multiplicative_numeric_expression as number))
- .toNumber();
- }
-
- const multiplicative_numeric_expression =
- this.visitMultiplicative_numeric_expression(
- ctx.multiplicative_numeric_expression()
- );
-
- return multiplicative_numeric_expression;
- };
-
- visitMultiplicative_numeric_expression = (
- ctx: Multiplicative_numeric_expressionContext
- ): VarValue => {
- if (ctx.multiplicative_numeric_expression()) {
- let multiplicative_numeric_expression =
- this.visitMultiplicative_numeric_expression(
- ctx.multiplicative_numeric_expression()
- );
- let signed_unary_numeric_expression =
- this.visitSigned_unary_numeric_expression(
- ctx.signed_unary_numeric_expression()
- );
- if (typeof signed_unary_numeric_expression === 'boolean') {
- signed_unary_numeric_expression = signed_unary_numeric_expression
- ? 1
- : 0;
- }
- if (typeof multiplicative_numeric_expression === 'boolean') {
- multiplicative_numeric_expression = multiplicative_numeric_expression
- ? 1
- : 0;
- }
- if (ctx.MUL()) {
- return new BigNumber(multiplicative_numeric_expression)
- .multipliedBy(new BigNumber(signed_unary_numeric_expression))
- .toNumber();
- }
- if (ctx.DIV()) {
- const result = new BigNumber(multiplicative_numeric_expression)
- .dividedBy(new BigNumber(signed_unary_numeric_expression))
- .toNumber();
- if (!Number.isFinite(result)) {
- throw new RuntimeError(`Invalid division by zero`);
- }
- return result;
- }
- // else MOD
- const result = new BigNumber(multiplicative_numeric_expression)
- .modulo(new BigNumber(signed_unary_numeric_expression))
- .toNumber();
- if (!Number.isFinite(result)) {
- throw new RuntimeError(`Invalid division by zero`);
- }
- return result;
+ if (ctx.ADD()) {
+ return leftValue.plus(rightValue).toNumber();
}
+ if (ctx.SUB()) {
+ return leftValue.minus(rightValue).toNumber();
+ }
+ throw new RuntimeError('Invalid additive operator');
+ };
+
+ visitParenthesizedExpression = (ctx: ParenthesizedExpressionContext) => {
+ return this.visit(ctx.expression());
+ };
- const signed_unary_numeric_expression =
- this.visitSigned_unary_numeric_expression(
- ctx.signed_unary_numeric_expression()
- );
+ visitIdentifierExpression = (ctx: IdentifierExpressionContext) => {
+ const identifier = this.visitIdentifier(ctx.identifier());
- return signed_unary_numeric_expression;
+ return identifier.getValue();
};
- visitSigned_unary_numeric_expression = (
- ctx: Signed_unary_numeric_expressionContext
- ): VarValue => {
- const unary_numeric_expression = this.visitUnary_numeric_expression(
- ctx.unary_numeric_expression()
- );
+ visitLiteralExpression = (ctx: LiteralExpressionContext) => {
+ return this.visitLiteral(ctx.literal());
+ };
- const sign = ctx.sign();
- if (sign) {
- if (sign.ADD()) {
- return +unary_numeric_expression;
- }
- return -unary_numeric_expression;
- }
- return unary_numeric_expression;
+ visitFunctionCallExpression = (ctx: FunctionCallExpressionContext) => {
+ return this.visitFunction_call(ctx.function_call());
};
- visitUnary_numeric_expression = (
- ctx: Unary_numeric_expressionContext
- ): VarValue => {
- if (ctx.FLOAT()) {
- return parseFloat(ctx.FLOAT().getText());
+ visitIdentifier = (ctx: IdentifierContext): ArcscriptVariable => {
+ let name: string;
+ let scope: string | null = null;
+ if (ctx.IDENTIFIER_list().length === 1) {
+ name = ctx.IDENTIFIER(0).getText();
+ } else {
+ scope = ctx.IDENTIFIER(0).getText();
+ name = ctx.IDENTIFIER(1).getText();
}
- if (ctx.INTEGER()) {
- return parseInt(ctx.INTEGER().getText(), 10);
+ const variableObject = this.state.getVar(name, scope);
+ if (!variableObject) {
+ throw new RuntimeError(`Variable ${name} not found`);
}
- if (ctx.VARIABLE()) {
- const variableName = ctx.VARIABLE().getText();
- const variableObject = this.state.getVar(variableName);
- if (!variableObject) {
- throw new RuntimeError(`Variable ${variableName} not found`);
+ return variableObject;
+ };
+
+ visitLiteral = (ctx: LiteralContext) => {
+ if (ctx.BOOLEAN()) {
+ if (ctx.BOOLEAN().getText() === 'true') {
+ return true;
}
- return this.state.getVarValue(variableObject.id);
+ return false;
}
if (ctx.STRING()) {
let result = ctx.STRING().getText();
result = result.replace(/^['"](.*)['"]$/, '$1');
return result;
}
- if (ctx.BOOLEAN()) {
- if (ctx.BOOLEAN().getText() === 'true') {
- return true;
- }
- return false;
- }
- if (ctx.function_call()) {
- return this.visitFunction_call(ctx.function_call());
+ return this.visitNumeric_literal(ctx.numeric_literal());
+ };
+
+ visitNumeric_literal = (ctx: Numeric_literalContext) => {
+ if (ctx.FLOAT()) {
+ return parseFloat(ctx.FLOAT().getText());
}
- return this.visitCompound_condition_or(ctx.compound_condition_or());
+ return parseInt(ctx.INTEGER().getText(), 10);
};
visitFunction_call = (ctx: Function_callContext) => {
- let argument_list: (VarValue | MentionResult)[] = [];
+ let argument_list: (VarValue | MentionResult | ArcscriptVariable)[] = [];
+ const function_name = ctx.FNAME().getText() as ArcscriptNonVoidFunctionKeys;
if (ctx.argument_list()) {
argument_list = this.visitArgument_list(ctx.argument_list());
}
+ if (ctx.identifier_list()) {
+ argument_list = this.visitIdentifier_list(ctx.identifier_list());
+ if (
+ ArcscriptParserBase.arcscriptFunctionsInfo[function_name].argType ===
+ 'variable'
+ ) {
+ argument_list = argument_list.map(variable => {
+ if (variable instanceof ArcscriptVariable) {
+ return variable;
+ }
+ throw new RuntimeError('Expected a variable');
+ });
+ } else {
+ argument_list = argument_list.map(variable => {
+ if (variable instanceof ArcscriptVariable) {
+ return variable.getValue();
+ }
+ throw new RuntimeError('Expected a variable');
+ });
+ }
+ }
- const function_name = ctx.FNAME().getText() as ArcscriptNonVoidFunctionKeys;
+ // TODO: remove non void function keys
return this.functions[function_name](...argument_list);
};
+ visitIdentifier_list = (ctx: Identifier_listContext): ArcscriptVariable[] => {
+ return ctx.identifier_list().map(identifier => {
+ return this.visitIdentifier(identifier);
+ });
+ };
+
visitArgument_list = (ctx: Argument_listContext) => {
return ctx.argument_list().map(argument => this.visitArgument(argument));
};
visitArgument = (ctx: ArgumentContext): VarValue | MentionResult => {
- if (ctx.STRING()) {
- let result = ctx.STRING().getText();
- result = result.replace(/^['"](.*)['"]$/, '$1');
- return result;
+ if (ctx.expression()) {
+ return this.visit(ctx.expression());
}
if (ctx.mention()) {
return this.visitMention(ctx.mention());
}
- return this.visitAdditive_numeric_expression(
- ctx.additive_numeric_expression()
- );
+
+ throw new RuntimeError('Invalid argument');
};
visitMention = (ctx: MentionContext): MentionResult => {
diff --git a/TypeScript/src/Generated/ArcscriptParserBase.ts b/TypeScript/src/Generated/ArcscriptParserBase.ts
index 12960d9..df5e327 100644
--- a/TypeScript/src/Generated/ArcscriptParserBase.ts
+++ b/TypeScript/src/Generated/ArcscriptParserBase.ts
@@ -1,13 +1,14 @@
import { Parser, TerminalNode, Token, TokenStream } from 'antlr4';
import ArcscriptParser, {
Argument_listContext,
+ Identifier_listContext,
+ IdentifierContext,
Mention_attributesContext,
- Variable_listContext,
} from './ArcscriptParser.js';
-import { VarObject } from '../types.js';
+import { ArcscriptStateDef } from '../types.js';
type ArcscriptParserOptions = {
- varObjects: Record;
+ arcscriptVariables: ArcscriptStateDef;
elementVisits: Record;
currentElement: string;
};
@@ -15,26 +16,38 @@ type ArcscriptParserOptions = {
type ArcscriptFunctionInfo = {
minArgs: number;
maxArgs: number | null;
+ returnType?: 'number' | 'void';
+ argType?: 'number' | 'variable' | 'mention';
};
export default class ArcscriptParserBase extends Parser {
- static arcscriptFunctionsArgLength: Record = {
- abs: { minArgs: 1, maxArgs: 1 },
- max: { minArgs: 2, maxArgs: null },
- min: { minArgs: 2, maxArgs: null },
- random: { minArgs: 0, maxArgs: 0 },
- roll: { minArgs: 1, maxArgs: 2 },
- round: { minArgs: 1, maxArgs: 1 },
- sqr: { minArgs: 1, maxArgs: 1 },
- sqrt: { minArgs: 1, maxArgs: 1 },
- visits: { minArgs: 0, maxArgs: 1 },
- show: { minArgs: 1, maxArgs: null },
- reset: { minArgs: 1, maxArgs: null },
- resetAll: { minArgs: 0, maxArgs: null },
- resetVisits: { minArgs: 0, maxArgs: 0 },
+ static arcscriptFunctionsInfo: Record = {
+ abs: { minArgs: 1, maxArgs: 1, returnType: 'number' },
+ max: { minArgs: 2, maxArgs: null, returnType: 'number' },
+ min: { minArgs: 2, maxArgs: null, returnType: 'number' },
+ random: { minArgs: 0, maxArgs: 0, returnType: 'number' },
+ roll: { minArgs: 1, maxArgs: 2, returnType: 'number' },
+ round: { minArgs: 1, maxArgs: 1, returnType: 'number' },
+ sqr: { minArgs: 1, maxArgs: 1, returnType: 'number' },
+ sqrt: { minArgs: 1, maxArgs: 1, returnType: 'number' },
+ visits: { minArgs: 0, maxArgs: 1, returnType: 'number' },
+ show: { minArgs: 1, maxArgs: null, returnType: 'void' },
+ reset: {
+ minArgs: 1,
+ maxArgs: null,
+ returnType: 'void',
+ argType: 'variable',
+ },
+ resetAll: {
+ minArgs: 0,
+ maxArgs: null,
+ returnType: 'void',
+ argType: 'variable',
+ },
+ resetVisits: { minArgs: 0, maxArgs: 0, returnType: 'void' },
};
- varObjects: Record = {};
+ arcscriptVariableNames: string[] = [];
elementVisits: Record = {};
currentElement: string = '';
currentLine: number = 0;
@@ -45,7 +58,15 @@ export default class ArcscriptParserBase extends Parser {
}
setOptions(options: ArcscriptParserOptions) {
- this.varObjects = options.varObjects;
+ const variableNames = Object.values(options.arcscriptVariables).map(
+ variable => {
+ if (variable.scope) {
+ return `${variable.scope}.${variable.name}`;
+ }
+ return variable.name;
+ }
+ );
+ this.arcscriptVariableNames = variableNames;
this.elementVisits = options.elementVisits;
this.currentElement = options.currentElement;
}
@@ -56,25 +77,24 @@ export default class ArcscriptParserBase extends Parser {
assertFunctionArguments(
token: Token,
- argListCtx: Argument_listContext | Variable_listContext | null = null
+ argListCtx: Argument_listContext | Identifier_listContext | null = null
) {
let argListLength = 0;
if (argListCtx) {
if (argListCtx instanceof Argument_listContext) {
argListLength = argListCtx.argument_list().length;
- } else if (argListCtx instanceof Variable_listContext) {
- argListLength = argListCtx.VARIABLE_list().length;
+ } else if (argListCtx instanceof Identifier_listContext) {
+ argListLength = argListCtx.identifier_list().length;
}
}
- const min =
- ArcscriptParserBase.arcscriptFunctionsArgLength[token.text].minArgs;
- const max =
- ArcscriptParserBase.arcscriptFunctionsArgLength[token.text].maxArgs;
+
+ const { minArgs, maxArgs, argType } =
+ ArcscriptParserBase.arcscriptFunctionsInfo[token.text];
if (
- (min !== null && argListLength < min) ||
- (max !== null && argListLength > max)
+ (minArgs !== null && argListLength < minArgs) ||
+ (maxArgs !== null && argListLength > maxArgs)
) {
this.notifyErrorListeners(
`Invalid number of arguments for function ${token.text}`,
@@ -82,18 +102,26 @@ export default class ArcscriptParserBase extends Parser {
undefined
);
}
+
+ if (argType && argListCtx) {
+ if (argType === 'variable') {
+ if (!(argListCtx instanceof Identifier_listContext)) {
+ this.notifyErrorListeners(
+ `Invalid argument type for function ${token.text}. Expected variables.`,
+ token,
+ undefined
+ );
+ }
+ }
+ }
}
- assertVariable(variableToken: Token) {
- const variableName = variableToken.text;
- if (
- !Object.values(this.varObjects).find(
- variable => variable.name === variableName
- )
- ) {
+ assertVariable(identifierCtx: IdentifierContext) {
+ const variableName = identifierCtx.getText();
+ if (!this.arcscriptVariableNames.includes(variableName)) {
this.notifyErrorListeners(
`The variable ${variableName} does not exist`,
- variableToken,
+ identifierCtx.start,
undefined
);
}
diff --git a/TypeScript/src/__tests__/antlr4.interpreter.test.ts b/TypeScript/src/__tests__/antlr4.interpreter.test.ts
index 4fb19e8..fa27e82 100644
--- a/TypeScript/src/__tests__/antlr4.interpreter.test.ts
+++ b/TypeScript/src/__tests__/antlr4.interpreter.test.ts
@@ -6,9 +6,12 @@ import runtimeErrorTests from './runtimeErrors.json';
import conditionTests from './conditions.json';
import replaceVariableTests from './replaceVariables.json';
import stringTests from './stringConcat.json';
-import { VarObject, VarValue } from '../types.js';
+import memberTests from './member.json';
+import { ArcscriptStateDef, VarValue } from '../types.js';
+import cloneDeep from 'lodash.clonedeep';
type TestCase = {
+ values?: Record>;
code: string;
changes?: Record;
output?: string;
@@ -19,40 +22,53 @@ type TestCase = {
variableChanges?: Record;
};
-function setVarValues(vars: Record) {
- return Object.fromEntries(
- Object.entries(vars)
- .filter(([, v]) => !v.children)
- .map(([k, v]) => [k, v.value])
- );
-}
+type TestSuite = {
+ initialVars: ArcscriptStateDef;
+ cases: TestCase[];
+};
describe('Interprete valid scripts', () => {
- const varObjects = validTests.initialVars as Record;
- const varValues = setVarValues(varObjects);
- const cases = validTests.cases as TestCase[];
+ const cases: TestCase[] = (validTests as unknown as TestSuite).cases;
test.each(cases)(
'Tests script: $code',
({
+ values,
code,
- changes: expectedChanges = {},
+ changes: expectedChanges,
output: expectedOutput = '',
events = null,
visits = {},
elementId = '',
}) => {
const eventHandler = vi.fn();
-
- const interpreter = new Interpreter(
- varValues,
- varObjects,
- visits,
- elementId,
- eventHandler
+ const initVars: ArcscriptStateDef = cloneDeep(
+ (validTests as unknown as TestSuite).initialVars
);
+ if (values?.global) {
+ Object.entries(values.global as Record).forEach(
+ ([id, value]) => {
+ if (
+ initVars[id] &&
+ (initVars[id].scope === 'global' ||
+ initVars[id].scope === undefined ||
+ initVars[id].scope === null)
+ ) {
+ initVars[id].value = value;
+ }
+ }
+ );
+ }
+ const interpreter = new Interpreter({
+ state: initVars,
+ elementVisits: visits,
+ currentElement: elementId,
+ eventHandler,
+ });
const { changes, output } = interpreter.runScript(code);
- expect(changes).toMatchObject(expectedChanges);
+ if (expectedChanges !== undefined) {
+ expect(changes).toEqual(expectedChanges);
+ }
expect(output).toEqual(expectedOutput);
if (events) {
@@ -66,10 +82,30 @@ describe('Interprete valid scripts', () => {
);
});
-describe('Interprete string test scripts', () => {
- const varObjects = stringTests.initialVars as Record;
- const varValues = setVarValues(varObjects);
+describe('Object members variables', () => {
+ test.each(memberTests.cases as TestCase[])(
+ 'Tests script: $code',
+ ({
+ code,
+ changes: expectedChanges = {},
+ output: expectedOutput = '',
+ visits,
+ elementId = '',
+ }) => {
+ const interpreter = new Interpreter({
+ state: (memberTests as unknown as TestSuite).initialVars,
+ elementVisits: visits,
+ currentElement: elementId,
+ });
+ const { changes, output } = interpreter.runScript(code);
+
+ expect(changes).toEqual(expectedChanges);
+ expect(output).toEqual(expectedOutput);
+ }
+ );
+});
+describe('Interprete string test scripts', () => {
test.each(stringTests.cases as unknown as TestCase[])(
'Tests script: $code',
({
@@ -79,32 +115,27 @@ describe('Interprete string test scripts', () => {
visits,
elementId = '',
}) => {
- const interpreter = new Interpreter(
- varValues,
- varObjects,
- visits,
- elementId
- );
+ const interpreter = new Interpreter({
+ state: (stringTests as unknown as TestSuite).initialVars,
+ elementVisits: visits,
+ currentElement: elementId,
+ });
const { changes, output } = interpreter.runScript(code);
- expect(changes).toMatchObject(expectedChanges);
+ expect(changes).toEqual(expectedChanges);
expect(output).toEqual(expectedOutput);
}
);
});
describe('Interprete script with parse errors', () => {
- const varObjects = parseErrorTests.initialVars as Record;
- const varValues = setVarValues(varObjects);
-
test.each(parseErrorTests.cases as TestCase[])(
'Test error script: $code',
({ code, visits, elementId = '' }) => {
- const interpreter = new Interpreter(
- varValues,
- varObjects,
- visits,
- elementId
- );
+ const interpreter = new Interpreter({
+ state: (parseErrorTests as unknown as TestSuite).initialVars,
+ elementVisits: visits,
+ currentElement: elementId,
+ });
expect(() => {
interpreter.parse(code);
}).toThrow(ParseError);
@@ -113,18 +144,14 @@ describe('Interprete script with parse errors', () => {
});
describe('Interprete script with runtime errors', () => {
- const varObjects = runtimeErrorTests.initialVars as Record;
- const varValues = setVarValues(varObjects);
-
test.each(runtimeErrorTests.cases as TestCase[])(
'Test error script: $code',
({ code, visits, elementId = '' }) => {
- const interpreter = new Interpreter(
- varValues,
- varObjects,
- visits,
- elementId
- );
+ const interpreter = new Interpreter({
+ state: (runtimeErrorTests as unknown as TestSuite).initialVars,
+ elementVisits: visits,
+ currentElement: elementId,
+ });
expect(() => {
interpreter.runScript(code);
}).toThrow(RuntimeError);
@@ -133,18 +160,14 @@ describe('Interprete script with runtime errors', () => {
});
describe('Interprete condition', () => {
- const varObjects = conditionTests.initialVars as Record;
- const varValues = setVarValues(varObjects);
-
test.each(conditionTests.cases as TestCase[])(
'Tests condition: $code',
({ code, visits, elementId = '', result: expectedResult }) => {
- const interpreter = new Interpreter(
- varValues,
- varObjects,
- visits,
- elementId
- );
+ const interpreter = new Interpreter({
+ state: (conditionTests as unknown as TestSuite).initialVars,
+ elementVisits: visits,
+ currentElement: elementId,
+ });
const { result } = interpreter.runScript(code);
expect(result.condition).toStrictEqual(expectedResult);
@@ -153,17 +176,13 @@ describe('Interprete condition', () => {
});
describe('Replace variables', () => {
- const varObjects = replaceVariableTests.initialVars as Record<
- string,
- VarObject
- >;
- const varValues = setVarValues(varObjects);
-
- test.each(replaceVariableTests.cases as TestCase[])(
+ test.each(replaceVariableTests.cases as unknown as TestCase[])(
'Tests replace: $code',
({ code, variableChanges = {}, result: expectedResult }) => {
// Parse and check the condition
- const interpreter = new Interpreter(varValues, varObjects);
+ const interpreter = new Interpreter({
+ state: (replaceVariableTests as unknown as TestSuite).initialVars,
+ });
const result = interpreter.replaceVariables(code, variableChanges);
// The given condition should match the expected evaluation
@@ -171,3 +190,128 @@ describe('Replace variables', () => {
}
);
});
+
+describe('Replace scopes', () => {
+ test('replaces only scope qualifiers and keeps global variables untouched', () => {
+ const interpreter = new Interpreter({
+ state: (replaceVariableTests as unknown as TestSuite).initialVars,
+ });
+ const result = interpreter.replaceScope(
+ `boardOne.xyz = 'fourtytwo'
xyz = boardOne.xyz
w = "boardOne.xyz"
`,
+ 'boardOne',
+ 'boardTwo'
+ );
+
+ expect(result).toBe(
+ `boardTwo.xyz = 'fourtytwo'
xyz = boardTwo.xyz
w = "boardOne.xyz"
`
+ );
+ });
+
+ test('supports replacing multiple scopes in one pass', () => {
+ const interpreter = new Interpreter({
+ state: (replaceVariableTests as unknown as TestSuite).initialVars,
+ });
+ const result = interpreter.replaceScopes(
+ 'comp1.x = boardOne.xyz + x
',
+ {
+ comp1: 'comp2',
+ boardOne: 'boardTwo',
+ }
+ );
+
+ expect(result).toBe(
+ 'comp2.x = boardTwo.xyz + x
'
+ );
+ });
+});
+
+describe('Scope inference', () => {
+ const initialVars: ArcscriptStateDef = {
+ var1: {
+ id: 'var1',
+ name: 'x',
+ type: 'integer',
+ defaultValue: 1,
+ },
+ var2: {
+ id: 'var2',
+ name: 'y',
+ type: 'integer',
+ defaultValue: 2,
+ scope: null,
+ },
+ var3: {
+ id: 'var3',
+ name: 'z',
+ type: 'integer',
+ defaultValue: 3,
+ scope: 'comp1',
+ },
+ };
+
+ test('infers global for missing and null scope', () => {
+ const interpreter = new Interpreter({
+ state: initialVars,
+ });
+ const { changes } = interpreter.runScript(
+ 'x=5
y=6
comp1.z=7
'
+ );
+
+ expect(changes).toEqual({
+ var1: 5,
+ var2: 6,
+ var3: 7,
+ });
+ });
+
+ test('replaceVariables infers global for missing and null scope', () => {
+ const interpreter = new Interpreter({
+ state: initialVars,
+ });
+ const result = interpreter.replaceVariables(
+ 'x=y+comp1.z
',
+ {
+ var1: 'a',
+ var2: 'b',
+ var3: 'c',
+ }
+ );
+
+ expect(result).toBe('a=b+comp1.c
');
+ });
+});
+
+describe('runScript overrides on subsequent calls', () => {
+ test('reuses the same interpreter instance with override changes', () => {
+ const initialVars: ArcscriptStateDef = {
+ var1: {
+ id: 'var1',
+ name: 'x',
+ type: 'integer',
+ defaultValue: 1,
+ },
+ var2: {
+ id: 'var2',
+ name: 'y',
+ type: 'integer',
+ defaultValue: 0,
+ },
+ };
+
+ const interpreter = new Interpreter({
+ state: initialVars,
+ });
+
+ const { changes: firstChanges } = interpreter.runScript(
+ 'x = x + 1
'
+ );
+ expect(firstChanges).toEqual({ var1: 2 });
+
+ const { changes: secondChanges } = interpreter.runScript(
+ 'y = x + 5
',
+ firstChanges
+ );
+
+ expect(secondChanges).toStrictEqual({ var1: 2, var2: 7 });
+ });
+});
diff --git a/TypeScript/src/__tests__/conditions.json b/TypeScript/src/__tests__/conditions.json
index 88331f1..2720737 100644
--- a/TypeScript/src/__tests__/conditions.json
+++ b/TypeScript/src/__tests__/conditions.json
@@ -4,43 +4,43 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
}
},
"cases": [
@@ -95,6 +95,13 @@
{
"code": "x is not \"Dummy text\"
",
"result": true
+ },
+ {
+ "code": "visits(test_elem)
",
+ "visits": {
+ "b": 0
+ },
+ "result": false
}
]
}
\ No newline at end of file
diff --git a/TypeScript/src/__tests__/member.json b/TypeScript/src/__tests__/member.json
new file mode 100644
index 0000000..05c170c
--- /dev/null
+++ b/TypeScript/src/__tests__/member.json
@@ -0,0 +1,118 @@
+{
+ "initialVars": {
+ "var1": {
+ "id": "var1",
+ "name": "x",
+ "type": "integer",
+ "defaultValue": 14
+ },
+ "var2": {
+ "id": "var2",
+ "name": "y",
+ "type": "integer",
+ "defaultValue": 15
+ },
+ "var3": {
+ "id": "var3",
+ "name": "z",
+ "type": "integer",
+ "defaultValue": 0
+ },
+ "var4": {
+ "id": "var4",
+ "name": "w",
+ "type": "string",
+ "defaultValue": "Dummy text"
+ },
+ "var5": {
+ "id": "var5",
+ "name": "$c5",
+ "type": "integer",
+ "defaultValue": 0
+ },
+ "var6": {
+ "id": "var6",
+ "name": "_a",
+ "type": "boolean",
+ "defaultValue": false
+ },
+ "var7": {
+ "id": "var7",
+ "name": "xy",
+ "type": "integer",
+ "defaultValue": -1
+ },
+ "var8": {
+ "id": "var8",
+ "name": "man",
+ "type": "string",
+ "defaultValue": "Different Text"
+ },
+ "var9": {
+ "id": "var9",
+ "name": "a",
+ "type": "integer",
+ "defaultValue": 12,
+ "scope": "comp1"
+ },
+ "var10": {
+ "id": "var10",
+ "name": "b",
+ "type": "integer",
+ "defaultValue": 0,
+ "scope": "comp1"
+ },
+ "var11": {
+ "id": "var11",
+ "name": "a",
+ "type": "string",
+ "defaultValue": "Component Text",
+ "scope": "comp2"
+ }
+ },
+ "cases": [
+ {
+ "code": "comp1.a = 42
",
+ "changes": {
+ "var9": 42
+ }
+ },
+ {
+ "code": "show(comp1.a)
",
+ "output": "12
"
+ },
+ {
+ "code": "comp1.a = 99
reset(comp1.a, comp1.b)
",
+ "changes": {
+ "var9": 12,
+ "var10": 0
+ }
+ },
+ {
+ "code": "x = comp1.a
",
+ "changes": {
+ "var1": 12
+ }
+ },
+ {
+ "code": "comp1.a = 55
comp1.b = comp1.a
",
+ "changes": {
+ "var9": 55,
+ "var10": 55
+ }
+ },
+ {
+ "code": "comp1.a = 77
comp1.b = comp1.a
reset(comp1.a, comp1.b)
",
+ "changes": {
+ "var9": 12,
+ "var10": 0
+ }
+ },
+ {
+ "code": "comp2.a = \"Hello World\"
",
+ "changes": {
+ "var11": "Hello World"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/TypeScript/src/__tests__/parseErrors.json b/TypeScript/src/__tests__/parseErrors.json
index 07d79d9..96902ac 100644
--- a/TypeScript/src/__tests__/parseErrors.json
+++ b/TypeScript/src/__tests__/parseErrors.json
@@ -4,43 +4,43 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
}
},
"cases": [
diff --git a/TypeScript/src/__tests__/replaceVariables.json b/TypeScript/src/__tests__/replaceVariables.json
index 4fc89b0..de4cda6 100644
--- a/TypeScript/src/__tests__/replaceVariables.json
+++ b/TypeScript/src/__tests__/replaceVariables.json
@@ -4,43 +4,63 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
+ },
+ "var8": {
+ "id": "var8",
+ "name": "x",
+ "type": "string",
+ "defaultValue": "Different Text",
+ "scope": "comp1"
+ },
+ "var9": {
+ "id": "var9",
+ "name": "xyz",
+ "type": "string",
+ "defaultValue": "hihi",
+ "scope": "boardOne"
+ },
+ "var10": {
+ "id": "var10",
+ "name": "xyz",
+ "type": "integer",
+ "defaultValue": 0
}
},
"cases": [
@@ -64,6 +84,49 @@
"var1": "zed"
},
"result": "b = \"x marks the spot\"
xy = 5 + zed
"
+ },
+ {
+ "code": "x = x + 2 * sqrt(x)
y = 5 + x
",
+ "variableChanges": {
+ "var8": "zed"
+ },
+ "result": "x = x + 2 * sqrt(x)
y = 5 + x
"
+ },
+ {
+ "code": "x = comp1.x + 2 * sqrt(x)
y = 5 + comp1.x
",
+ "variableChanges": {
+ "var8": "zed"
+ },
+ "result": "x = comp1.zed + 2 * sqrt(x)
y = 5 + comp1.zed
"
+ },
+ {
+ "code": "comp1.x = 'test'
x = 'test'
",
+ "variableChanges": {
+ "var1": "zed"
+ },
+ "result": "comp1.x = 'test'
zed = 'test'
"
+ },
+ {
+ "code": "comp1.x = 'test'
x = 'test'
",
+ "variableChanges": {
+ "var8": "zed"
+ },
+ "result": "comp1.zed = 'test'
x = 'test'
"
+ },
+ {
+ "code": "boardOne.xyz = 'fourtytwo'
xyz = 42
",
+ "variableChanges": {
+ "var9": "hohoho"
+ },
+ "result": "boardOne.hohoho = 'fourtytwo'
xyz = 42
"
+ },
+ {
+ "code": "x = comp1.x + 2 * sqrt(x)
y = 5 + comp1.x
",
+ "variableChanges": {
+ "var1": "zed",
+ "var8": "zed"
+ },
+ "result": "zed = comp1.zed + 2 * sqrt(zed)
y = 5 + comp1.zed
"
}
]
}
\ No newline at end of file
diff --git a/TypeScript/src/__tests__/runtimeErrors.json b/TypeScript/src/__tests__/runtimeErrors.json
index 9adb85e..4f6b72e 100644
--- a/TypeScript/src/__tests__/runtimeErrors.json
+++ b/TypeScript/src/__tests__/runtimeErrors.json
@@ -4,43 +4,43 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
}
},
"cases": [
diff --git a/TypeScript/src/__tests__/stringConcat.json b/TypeScript/src/__tests__/stringConcat.json
index d9b3503..f6a8bc3 100644
--- a/TypeScript/src/__tests__/stringConcat.json
+++ b/TypeScript/src/__tests__/stringConcat.json
@@ -1,132 +1,132 @@
{
- "initialVars": {
- "var1": {
- "id": "var1",
- "name": "x",
- "type": "integer",
- "value": 14
- },
- "var2": {
- "id": "var2",
- "name": "y",
- "type": "integer",
- "value": 15
- },
- "var3": {
- "id": "var3",
- "name": "z",
- "type": "integer",
- "value": 0
- },
- "var4": {
- "id": "var4",
- "name": "w",
- "type": "string",
- "value": "Dummy text"
- },
- "var5": {
- "id": "var5",
- "name": "$c5",
- "type": "integer",
- "value": 0
- },
- "var6": {
- "id": "var6",
- "name": "_a",
- "type": "boolean",
- "value": false
- },
- "var7": {
- "id": "var7",
- "name": "xy",
- "type": "integer",
- "value": -1
- },
- "var8": {
- "id": "var8",
- "name": "man",
- "type": "string",
- "value": "Different Text"
- }
- },
- "cases": [
- {
- "code": "w = \"test\"
w = w + \"ing\"
",
- "changes": {
- "var4": "testing"
- }
- },
- {
- "code": "w = \"test\" + \"ing\"
",
- "changes": {
- "var4": "testing"
- }
- },
- {
- "code": "w = 0.42 + \"ing\"
",
- "changes": {
- "var4": "0.42ing"
- }
- },
- {
- "code": "w = 0.42 + \"\"
",
- "changes": {
- "var4": "0.42"
- }
- },
- {
- "code": "w = \"test\" + 44
",
- "changes": {
- "var4": "test44"
- }
- },
- {
- "code": "w = \"test\" + false
",
- "changes": {
- "var4": "testfalse"
- }
- },
- {
- "code": "w = \"test\" + true + true
",
- "changes": {
- "var4": "testtruetrue"
- }
- },
- {
- "code": "w = true + true + \"test\"
",
- "changes": {
- "var4": "2test"
- }
- },
- {
- "code": "w += \" test\"
",
- "changes": {
- "var4": "Dummy text test"
- }
- },
- {
- "code": "x += \"test\"
",
- "changes": {
- "var1": "14test"
- }
- },
- {
- "code": "w += 42
",
- "changes": {
- "var4": "Dummy text42"
- }
- },
- {
- "code": "w += _a
",
- "changes": {
- "var4": "Dummy textfalse"
- }
- },
- {
- "code": "w += man
",
- "changes": {
- "var4": "Dummy textDifferent Text"
- }
- }
- ]
+ "initialVars": {
+ "var1": {
+ "id": "var1",
+ "name": "x",
+ "type": "integer",
+ "defaultValue": 14
+ },
+ "var2": {
+ "id": "var2",
+ "name": "y",
+ "type": "integer",
+ "defaultValue": 15
+ },
+ "var3": {
+ "id": "var3",
+ "name": "z",
+ "type": "integer",
+ "defaultValue": 0
+ },
+ "var4": {
+ "id": "var4",
+ "name": "w",
+ "type": "string",
+ "defaultValue": "Dummy text"
+ },
+ "var5": {
+ "id": "var5",
+ "name": "$c5",
+ "type": "integer",
+ "defaultValue": 0
+ },
+ "var6": {
+ "id": "var6",
+ "name": "_a",
+ "type": "boolean",
+ "defaultValue": false
+ },
+ "var7": {
+ "id": "var7",
+ "name": "xy",
+ "type": "integer",
+ "defaultValue": -1
+ },
+ "var8": {
+ "id": "var8",
+ "name": "man",
+ "type": "string",
+ "defaultValue": "Different Text"
+ }
+ },
+ "cases": [
+ {
+ "code": "w = \"test\"
w = w + \"ing\"
",
+ "changes": {
+ "var4": "testing"
+ }
+ },
+ {
+ "code": "w = \"test\" + \"ing\"
",
+ "changes": {
+ "var4": "testing"
+ }
+ },
+ {
+ "code": "w = 0.42 + \"ing\"
",
+ "changes": {
+ "var4": "0.42ing"
+ }
+ },
+ {
+ "code": "w = 0.42 + \"\"
",
+ "changes": {
+ "var4": "0.42"
+ }
+ },
+ {
+ "code": "w = \"test\" + 44
",
+ "changes": {
+ "var4": "test44"
+ }
+ },
+ {
+ "code": "w = \"test\" + false
",
+ "changes": {
+ "var4": "testfalse"
+ }
+ },
+ {
+ "code": "w = \"test\" + true + true
",
+ "changes": {
+ "var4": "testtruetrue"
+ }
+ },
+ {
+ "code": "w = true + true + \"test\"
",
+ "changes": {
+ "var4": "2test"
+ }
+ },
+ {
+ "code": "w += \" test\"
",
+ "changes": {
+ "var4": "Dummy text test"
+ }
+ },
+ {
+ "code": "x += \"test\"
",
+ "changes": {
+ "var1": "14test"
+ }
+ },
+ {
+ "code": "w += 42
",
+ "changes": {
+ "var4": "Dummy text42"
+ }
+ },
+ {
+ "code": "w += _a
",
+ "changes": {
+ "var4": "Dummy textfalse"
+ }
+ },
+ {
+ "code": "w += man
",
+ "changes": {
+ "var4": "Dummy textDifferent Text"
+ }
+ }
+ ]
}
\ No newline at end of file
diff --git a/TypeScript/src/__tests__/valid.json b/TypeScript/src/__tests__/valid.json
index d50ed3b..6929780 100644
--- a/TypeScript/src/__tests__/valid.json
+++ b/TypeScript/src/__tests__/valid.json
@@ -4,49 +4,49 @@
"id": "var1",
"name": "x",
"type": "integer",
- "value": 14
+ "defaultValue": 14
},
"var2": {
"id": "var2",
"name": "y",
"type": "integer",
- "value": 15
+ "defaultValue": 15
},
"var3": {
"id": "var3",
"name": "z",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var4": {
"id": "var4",
"name": "w",
"type": "string",
- "value": "Dummy text"
+ "defaultValue": "Dummy text"
},
"var5": {
"id": "var5",
"name": "$c5",
"type": "integer",
- "value": 0
+ "defaultValue": 0
},
"var6": {
"id": "var6",
"name": "_a",
"type": "boolean",
- "value": false
+ "defaultValue": false
},
"var7": {
"id": "var7",
"name": "xy",
"type": "integer",
- "value": -1
+ "defaultValue": -1
},
"var8": {
"id": "var8",
"name": "man",
"type": "string",
- "value": "Different Text"
+ "defaultValue": "Different Text"
}
},
"cases": [
@@ -312,20 +312,16 @@
}
},
{
- "code": "x = random()
",
- "changes": {}
+ "code": "x = random()
"
},
{
- "code": "x = roll(6)
",
- "changes": {}
+ "code": "x = roll(6)
"
},
{
- "code": "x = roll(6, 5)
",
- "changes": {}
+ "code": "x = roll(6, 5)
"
},
{
- "code": "x = roll(y)
",
- "changes": {}
+ "code": "x = roll(y)
"
},
{
"code": "x = round(2.65)
",
@@ -412,7 +408,9 @@
},
{
"code": "How are you?
x = 1
I am fine, thank you.
",
- "changes": {},
+ "changes": {
+ "var1": 1
+ },
"output": "How are you?
I am fine, thank you.
"
},
{
@@ -536,7 +534,24 @@
},
{
"code": "w = \"String with \\\"quotes\\\" \\t tabs and \\n new lines\"
show(w)
",
- "output": "String with \"quotes\" \t tabs and \n new lines
"
+ "output": "String with \"quotes\" \t tabs and \n new lines
",
+ "changes": {
+ "var4": "String with \\\"quotes\\\" \\t tabs and \\n new lines"
+ }
+ },
+ {
+ "values": {
+ "global": {
+ "var1": 12,
+ "var2": 10
+ }
+ },
+ "code": "show(x,\" \", y)
reset(x, y)
",
+ "output": "12 10
",
+ "changes": {
+ "var1": 14,
+ "var2": 15
+ }
}
]
}
\ No newline at end of file
diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts
index f936f71..2aaf52e 100644
--- a/TypeScript/src/antlr4.interpreter.ts
+++ b/TypeScript/src/antlr4.interpreter.ts
@@ -4,60 +4,55 @@ import ArcscriptLexer from './Generated/ArcscriptLexer.js';
import ArcscriptParser from './Generated/ArcscriptParser.js';
import ArcscriptVisitor from './ArcscriptVisitor.js';
import ErrorListener from './ErrorListener.js';
-import { VarObject, VarValue } from './types.js';
+import { ArcscriptStateDef, VarValue } from './types.js';
+import ArcscriptState from './ArcscriptState.js';
+
+type ArcscriptInterpreterOptions = {
+ state: ArcscriptStateDef;
+ elementVisits?: Record;
+ currentElement?: string;
+ eventHandler?: (event: string, data?: unknown) => void;
+};
export default class Interpreter {
- varValues: Record;
- varObjects: Record;
+ arcscriptVariables: ArcscriptStateDef;
+ state: ArcscriptState | null = null;
elementVisits: Record;
currentElement: string;
variableOffsets: { start: number; end: number }[];
emit: (event: string, data?: unknown) => void;
- /**
- * Interpreter constructor
- * @param varValues
- * @param varObjects
- * @param elementVisits The current element visits
- * @param currentElement The current element ID
- * @param eventHandler The event callback
- */
- constructor(
- varValues: Record,
- varObjects: Record,
- elementVisits: Record = {},
- currentElement: string = '',
- eventHandler: (event: string, data?: unknown) => void = () => {}
- ) {
- this.varValues = varValues;
- this.varObjects = varObjects;
- this.elementVisits = elementVisits;
- this.currentElement = currentElement;
+ constructor(options = {} as ArcscriptInterpreterOptions) {
+ this.arcscriptVariables = options.state;
+ this.elementVisits = options.elementVisits || {};
+ this.currentElement = options.currentElement || '';
this.variableOffsets = [];
- this.emit = eventHandler;
+ this.emit = options.eventHandler || (() => {});
}
runScript(code: string, varValues: Record = {}) {
this.variableOffsets = [];
- const { tree } = this.parse(code);
- const visitor = new ArcscriptVisitor(
- this.varValues,
- this.varObjects,
+ this.state = new ArcscriptState(
+ this.arcscriptVariables,
this.elementVisits,
this.currentElement,
this.emit
);
- Object.entries(varValues).forEach(([key, value]) => {
- visitor.state.changes[key] = value;
- });
+
+ const { tree } = this.parse(code);
+
+ const visitor = new ArcscriptVisitor(this.state);
+
+ this.state.setVarValues(varValues);
+
const result = tree.accept(visitor);
let output = visitor.state.generateOutput();
output = clearBlockStyle(output);
return {
- changes: visitor.state.changes,
+ changes: visitor.state.getChanges(),
output,
result,
};
@@ -72,7 +67,7 @@ export default class Interpreter {
const tokens = new antlr4.CommonTokenStream(lexer);
const parser = new ArcscriptParser(tokens);
parser.setOptions({
- varObjects: this.varObjects,
+ arcscriptVariables: this.arcscriptVariables,
elementVisits: this.elementVisits,
currentElement: this.currentElement,
});
@@ -91,29 +86,64 @@ export default class Interpreter {
};
}
- replaceVariables(code: string, variables: Record) {
+ private parseTokens(code: string) {
const chars = new CharStream(code);
const lexer = new ArcscriptLexer(chars);
const errorListener = new ErrorListener();
lexer.removeErrorListeners();
lexer.addErrorListener(errorListener);
- const tokenTypeNames = lexer.getSymbolicNames();
- const allTokens = lexer.getAllTokens();
- const variableTokens = allTokens.filter(
- token => tokenTypeNames[token.type] === 'VARIABLE'
- );
+ return {
+ tokenTypeNames: lexer.getSymbolicNames(),
+ allTokens: lexer.getAllTokens(),
+ };
+ }
+ replaceVariables(code: string, variables: Record) {
+ const { tokenTypeNames, allTokens } = this.parseTokens(code);
const tokenIdMap = new Map();
- variableTokens.forEach(varToken => {
- const targetVar = Object.values(this.varObjects).find(
- variable => variable.name === varToken.text
- );
- if (targetVar?.id) {
- tokenIdMap.set(varToken, targetVar.id);
+ const stateVars = Object.values(this.arcscriptVariables);
+ const isGlobalScope = (scope: string | null | undefined) => {
+ return scope === undefined || scope === null || scope === 'global';
+ };
+
+ allTokens.forEach((token, index) => {
+ if (tokenTypeNames[token.type] !== 'IDENTIFIER') {
+ return;
+ }
+
+ // Identifier followed by dot is a scope qualifier (e.g. comp1.a).
+ const nextToken = allTokens[index + 1];
+ if (nextToken && tokenTypeNames[nextToken.type] === 'DOT') {
+ return;
+ }
+
+ let targetVar: (typeof stateVars)[number] | null = null;
+ const previousToken = allTokens[index - 1];
+ if (previousToken && tokenTypeNames[previousToken.type] === 'DOT') {
+ const scopeToken = allTokens[index - 2];
+ if (scopeToken && tokenTypeNames[scopeToken.type] === 'IDENTIFIER') {
+ targetVar =
+ stateVars.find(
+ variable =>
+ variable.scope === scopeToken.text &&
+ variable.name === token.text
+ ) ?? null;
+ }
+ } else {
+ targetVar =
+ stateVars.find(
+ variable =>
+ variable.name === token.text && isGlobalScope(variable.scope)
+ ) ?? null;
+ }
+
+ if (targetVar) {
+ tokenIdMap.set(token, targetVar.id);
}
});
+ const variableTokens = allTokens.filter(token => tokenIdMap.has(token));
const f = variableTokens
.filter(varToken =>
tokenIdMap.has(varToken)
@@ -130,4 +160,52 @@ export default class Interpreter {
});
return newCode;
}
+
+ replaceScopes(code: string, scopes: Record) {
+ const { tokenTypeNames, allTokens } = this.parseTokens(code);
+ const stateVars = Object.values(this.arcscriptVariables);
+
+ const targetScopeTokens = allTokens
+ .filter((token, index) => {
+ if (tokenTypeNames[token.type] !== 'IDENTIFIER') {
+ return false;
+ }
+
+ const dotToken = allTokens[index + 1];
+ const variableToken = allTokens[index + 2];
+ if (
+ !dotToken ||
+ tokenTypeNames[dotToken.type] !== 'DOT' ||
+ !variableToken ||
+ tokenTypeNames[variableToken.type] !== 'IDENTIFIER'
+ ) {
+ return false;
+ }
+
+ return stateVars.some(
+ variable =>
+ variable.scope === token.text && variable.name === variableToken.text
+ );
+ })
+ .filter(scopeToken =>
+ Object.prototype.hasOwnProperty.call(scopes, scopeToken.text)
+ )
+ .sort((a, b) => b.start - a.start);
+
+ let newCode = code;
+ targetScopeTokens.forEach(scopeToken => {
+ const start = scopeToken.start;
+ const end = start + scopeToken.text.length;
+ const replace = scopes[scopeToken.text];
+ newCode = newCode.slice(0, start) + replace + newCode.slice(end);
+ });
+
+ return newCode;
+ }
+
+ replaceScope(code: string, scope: string, replacement: string) {
+ return this.replaceScopes(code, {
+ [scope]: replacement,
+ });
+ }
}
diff --git a/TypeScript/src/types.ts b/TypeScript/src/types.ts
index 8c49687..f559a3d 100644
--- a/TypeScript/src/types.ts
+++ b/TypeScript/src/types.ts
@@ -1,15 +1,18 @@
export type VarValue = string | number | boolean;
export type VarType = 'string' | 'integer' | 'float' | 'boolean';
-export type VarObject = {
+export type VarDef = {
id: string;
name: string;
type: VarType;
- value: VarValue;
- children?: string[];
+ defaultValue: VarValue;
+ value?: VarValue;
+ scope?: string | null;
};
export type MentionResult = {
attrs: Record;
label: string;
};
+
+export type ArcscriptStateDef = Record;
diff --git a/TypeScript/tsconfig.json b/TypeScript/tsconfig.json
index f8bf209..0080e94 100644
--- a/TypeScript/tsconfig.json
+++ b/TypeScript/tsconfig.json
@@ -1,9 +1,9 @@
{
"compilerOptions": {
- "target": "ES2020",
- "module": "es2020",
+ "target": "ES2022",
+ "module": "es2022",
"lib": [
- "ES2020",
+ "ES2022",
"DOM"
],
"outDir": "./dist",
diff --git a/grammar/ArcscriptLexer.g4 b/grammar/ArcscriptLexer.g4
index 92a9a29..86eb754 100644
--- a/grammar/ArcscriptLexer.g4
+++ b/grammar/ArcscriptLexer.g4
@@ -1,9 +1,11 @@
lexer grammar ArcscriptLexer;
-CODESTART: ''))* '>'))* '>' -> pushMode(CODESEGMENT);
+CODESTART:
+ ''))* '>'))* '>' -> pushMode(CODESEGMENT);
-PARAGRAPHSTART: (('')|('
'))* '>')) -> pushMode(PARAGRAPH);
-BLOCKQUOTESTART: '
'))* '>' -> pushMode(BLOCKQUOTE);
+PARAGRAPHSTART: (('') | ('
'))* '>')) -> pushMode(PARAGRAPH);
+BLOCKQUOTESTART:
+ '
'))* '>' -> pushMode(BLOCKQUOTE);
NORMAL_WHITESPACE: [ \t\r\n]+ -> skip;
@@ -12,8 +14,9 @@ mode PARAGRAPH;
PARAGRAPHEND: .*? '' -> popMode;
mode BLOCKQUOTE;
-BQ_CODESTART: ''))* '>'))* '>' -> pushMode(CODESEGMENT);
-BQ_PARAGRAPHSTART: (('')|('
'))* '>')) -> pushMode(PARAGRAPH);
+BQ_CODESTART:
+ '
'))* '>'))* '>' -> pushMode(CODESEGMENT);
+BQ_PARAGRAPHSTART: (('') | ('
'))* '>')) -> pushMode(PARAGRAPH);
BLOCKQUOTEEND: '
' -> popMode;
BQ_WHITESPACE: [ \t\r\n]+ -> skip;
@@ -23,7 +26,7 @@ CODEEND: '
' -> popMode;
MENTION_TAG_OPEN: ' pushMode(MENTIONSEGMENT);
-FLOAT: DIGIT*'.'DIGIT+;
+FLOAT: DIGIT* '.' DIGIT+;
INTEGER: DIGIT+;
@@ -59,25 +62,27 @@ OR: '||';
ASSIGN: '=';
NEG: '!';
+DOT: '.';
COMMA: ',';
LBRACE: '{';
RBRACE: '}';
BOOLEAN: 'true' | 'false';
-FNAME: 'abs'|
- 'max'|
- 'min'|
- 'random'|
- 'roll'|
- 'round'|
- 'sqr'|
- 'sqrt'|
- 'visits';
-
-VFNAME: 'show' | 'resetVisits';
-
-VFNAMEVARS: 'reset' | 'resetAll';
+FNAME:
+ 'abs'
+ | 'max'
+ | 'min'
+ | 'random'
+ | 'roll'
+ | 'round'
+ | 'sqr'
+ | 'sqrt'
+ | 'visits'
+ | 'show'
+ | 'resetVisits'
+ | 'reset'
+ | 'resetAll';
IFKEYWORD: 'if';
@@ -95,16 +100,13 @@ ISKEYWORD: 'is';
NOTKEYWORD: 'not';
-STRING
- : '"' STRING_CONTENT* '"'
- | '\'' STRING_CONTENT* '\''
- ;
-fragment STRING_CONTENT
- : ~[\\\r\n'"]
- | '\\' [abfnrtv'"\\]
- ;
+STRING: '"' STRING_CONTENT* '"' | '\'' STRING_CONTENT* '\'';
+fragment STRING_CONTENT: ~[\\\r\n'"] | '\\' [abfnrtv'"\\];
+
+IDENTIFIER: IDENTIFIER_START IDENTIFIER_PART*;
-VARIABLE: [A-Za-z$_][0-9A-Za-z$_]*;
+fragment IDENTIFIER_START: [A-Za-z$_];
+fragment IDENTIFIER_PART: [0-9A-Za-z$_];
WHITESPACE: [ \t\r\n]+ -> skip;
@@ -128,48 +130,35 @@ MENTION_LABEL: ~'<'+;
mode MENTION_ATTR_SEGMENT;
-ATTR_VALUE
- : ' '* ATTRIBUTE -> popMode;
-
-ATTRIBUTE
- : DOUBLE_QUOTE_STRING
- | SINGLE_QUOTE_STRING
- | ATTCHARS
- | HEXCHARS
- | DECCHARS
- ;
-
-fragment ATTCHARS
- : ATTCHAR+ ' '?
- ;
-
-fragment ATTCHAR
- : '-'
- | '_'
- | '.'
- | '/'
- | '+'
- | ','
- | '?'
- | '='
- | ':'
- | ';'
- | '#'
- | [0-9a-zA-Z]
- ;
-
-fragment HEXCHARS
- : '#' [0-9a-fA-F]+
- ;
-
-fragment DECCHARS
- : [0-9]+ '%'?
- ;
-
-fragment DOUBLE_QUOTE_STRING
- : '"' ~[<"]* '"'
- ;
-
-fragment SINGLE_QUOTE_STRING
- : '\'' ~[<']* '\''
- ;
+ATTR_VALUE: ' '* ATTRIBUTE -> popMode;
+
+ATTRIBUTE:
+ DOUBLE_QUOTE_STRING
+ | SINGLE_QUOTE_STRING
+ | ATTCHARS
+ | HEXCHARS
+ | DECCHARS;
+
+fragment ATTCHARS: ATTCHAR+ ' '?;
+
+fragment ATTCHAR:
+ '-'
+ | '_'
+ | '.'
+ | '/'
+ | '+'
+ | ','
+ | '?'
+ | '='
+ | ':'
+ | ';'
+ | '#'
+ | [0-9a-zA-Z];
+
+fragment HEXCHARS: '#' [0-9a-fA-F]+;
+
+fragment DECCHARS: [0-9]+ '%'?;
+
+fragment DOUBLE_QUOTE_STRING: '"' ~[<"]* '"';
+
+fragment SINGLE_QUOTE_STRING: '\'' ~[<']* '\'';
\ No newline at end of file
diff --git a/grammar/ArcscriptParser.g4 b/grammar/ArcscriptParser.g4
index 6b8a569..b7559f2 100644
--- a/grammar/ArcscriptParser.g4
+++ b/grammar/ArcscriptParser.g4
@@ -7,7 +7,9 @@ options {
superClass = ArcscriptParserBase;
}
-input: script EOF | codestart compound_condition_or codeend EOF;
+input: condition EOF | script EOF;
+
+condition: codestart expression codeend;
script: script_section+;
@@ -39,8 +41,7 @@ codeend: CODEEND;
assignment_segment: codestart statement_assignment codeend;
-function_call_segment:
- codestart statement_function_call codeend;
+function_call_segment: codestart function_call codeend;
conditional_section:
if_section else_if_section* else_section? endif_segment;
@@ -51,27 +52,32 @@ else_if_section: codestart else_if_clause codeend script;
else_section: codestart ELSEKEYWORD codeend script;
-if_clause: IFKEYWORD compound_condition_or;
+if_clause: IFKEYWORD expression;
-else_if_clause: ELSEIFKEYWORD compound_condition_or;
+else_if_clause: ELSEIFKEYWORD expression;
endif_segment: codestart ENDIFKEYWORD codeend;
statement_assignment:
- VARIABLE (
+ assignable (
ASSIGNADD
| ASSIGNSUB
| ASSIGNMUL
| ASSIGNDIV
| ASSIGNMOD
| ASSIGN
- ) compound_condition_or {this.assertVariable($VARIABLE);};
+ ) expression;
+
+assignable: identifier {this.assertVariable($identifier.ctx);};
-statement_function_call: void_function_call;
+identifier_list:
+ identifier {this.assertVariable($identifier.ctx);} (
+ ',' identifier {this.assertVariable($identifier.ctx);}
+ )*;
argument_list: argument (',' argument)*;
-argument: additive_numeric_expression | STRING | mention;
+argument: expression | mention;
mention:
MENTION_TAG_OPEN attr += mention_attributes* '>' MENTION_LABEL? TAG_OPEN MENTION_TAG_CLOSE {this.assertMention($attr)
@@ -79,64 +85,29 @@ mention:
mention_attributes: ATTR_NAME (TAG_EQUALS ATTR_VALUE)?;
-additive_numeric_expression:
- multiplicative_numeric_expression
- | additive_numeric_expression (ADD | SUB) multiplicative_numeric_expression;
+expression:
+ (NEG | NOTKEYWORD) expression # UnaryExpression
+ | ADD expression # UnaryExpression
+ | SUB expression # UnaryExpression
+ | expression (MUL | DIV | MOD) expression # MultiplicativeExpression
+ | expression (ADD | SUB) expression # AdditiveExpression
+ | expression (LT | GT | LE | GE) expression # ComparisonExpression
+ | expression (EQ | NE | (ISKEYWORD NOTKEYWORD) | ISKEYWORD) expression # ComparisonExpression
+ | expression (AND | ANDKEYWORD) expression # ComparisonExpression
+ | expression (OR | ORKEYWORD) expression # ComparisonExpression
+ | identifier {this.assertVariable($identifier.ctx);} # IdentifierExpression
+ | '(' expression ')' # ParenthesizedExpression
+ | function_call # FunctionCallExpression
+ | literal # LiteralExpression;
-multiplicative_numeric_expression:
- signed_unary_numeric_expression
- | multiplicative_numeric_expression (MUL | DIV | MOD) signed_unary_numeric_expression;
+literal: BOOLEAN | STRING | numeric_literal;
-signed_unary_numeric_expression:
- sign unary_numeric_expression
- | unary_numeric_expression;
+numeric_literal: FLOAT | INTEGER;
-unary_numeric_expression:
- FLOAT
- | VARIABLE {this.assertVariable($VARIABLE);}
- | INTEGER
- | STRING
- | BOOLEAN
- | function_call
- | LPAREN compound_condition_or RPAREN;
+identifier: IDENTIFIER | IDENTIFIER '.' IDENTIFIER;
function_call:
- FNAME LPAREN argument_list? RPAREN {this.assertFunctionArguments($FNAME, $argument_list.ctx);};
-
-void_function_call:
- VFNAME LPAREN argument_list? RPAREN {this.assertFunctionArguments($VFNAME, $argument_list.ctx);}
- | VFNAMEVARS LPAREN variable_list? RPAREN {this.assertFunctionArguments($VFNAMEVARS, $variable_list.ctx);
- };
-
-sign: ADD | SUB;
-
-variable_list:
- VARIABLE (',' VARIABLE)* {this.assertVariable($VARIABLE);};
-
-compound_condition_or:
- compound_condition_and (
- ( OR | ORKEYWORD) compound_condition_or
- )?;
-
-compound_condition_and:
- negated_unary_condition (
- (AND | ANDKEYWORD) compound_condition_and
- )?;
-
-negated_unary_condition: (NEG | NOTKEYWORD)? unary_condition;
-
-unary_condition: condition;
-
-condition: expression (conditional_operator expression)?;
-
-conditional_operator:
- GT
- | GE
- | LT
- | LE
- | EQ
- | NE
- | ISKEYWORD
- | ISKEYWORD NOTKEYWORD;
-
-expression: STRING | BOOLEAN | additive_numeric_expression;
\ No newline at end of file
+ FNAME LPAREN identifier_list? RPAREN {this.assertFunctionArguments($FNAME, $identifier_list.ctx);
+ }
+ | FNAME LPAREN argument_list? RPAREN {this.assertFunctionArguments($FNAME, $argument_list.ctx);}
+ ;
\ No newline at end of file