From 42e7c37bdb3641f205a05c19288bbad21ff99d12 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Tue, 17 Feb 2026 15:31:13 +0200 Subject: [PATCH 01/21] feat: add ArcscriptVariable class --- TypeScript/src/ArcscriptFunctions.ts | 4 +- TypeScript/src/ArcscriptState.ts | 69 +++++++++++++------ TypeScript/src/ArcscriptVariable.ts | 56 +++++++++++++++ .../src/Generated/ArcscriptParserBase.ts | 35 +++++----- TypeScript/src/antlr4.interpreter.ts | 6 +- 5 files changed, 125 insertions(+), 45 deletions(-) create mode 100644 TypeScript/src/ArcscriptVariable.ts diff --git a/TypeScript/src/ArcscriptFunctions.ts b/TypeScript/src/ArcscriptFunctions.ts index cdbe6d8..2456d14 100644 --- a/TypeScript/src/ArcscriptFunctions.ts +++ b/TypeScript/src/ArcscriptFunctions.ts @@ -107,7 +107,7 @@ export default class ArcscriptFunctions { const ids = args.map(name => { if (typeof name !== 'string' || !this.state.getVar(name)) { throw new RuntimeError( - `Invalid argument ${name} in function resetAll. Expected a variable` + `Invalid argument ${name} in function reset. Expected a variable` ); } const v = this.state.getVar(name); @@ -132,7 +132,7 @@ export default class ArcscriptFunctions { } return v.id; }); - const all = Object.keys(this.state.getInitialVarValues()); + const all = Object.keys(this.state.variables); const resetIds = all.filter(id => !except.includes(id)); this.state.resetVarValues(resetIds); } diff --git a/TypeScript/src/ArcscriptState.ts b/TypeScript/src/ArcscriptState.ts index 56906e7..88ec841 100644 --- a/TypeScript/src/ArcscriptState.ts +++ b/TypeScript/src/ArcscriptState.ts @@ -1,5 +1,5 @@ -import cloneDeep from 'lodash.clonedeep'; import { VarObject, VarValue } from './types.js'; +import ArcscriptVariable from './ArcscriptVariable.js'; type OutputObject = { output?: string; @@ -10,13 +10,11 @@ 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; @@ -30,13 +28,13 @@ export default class ArcscriptState { currentElement: string, emit: (event: string, data?: unknown) => void ) { - this.varValues = varValues; - this.varObjects = varObjects; + this.variables = this.initializeVariables(varObjects, varValues); + this.elementVisits = elementVisits; this.currentElement = currentElement; this.outputs = []; this.conditionDepth = 0; - this.changes = {}; + this.emit = emit; this.outputDoc = document.implementation.createHTMLDocument(); @@ -46,33 +44,62 @@ export default class ArcscriptState { this.insertBlockquote = false; } + /** + * + * @param {*} varObjects + * @param {*} varValues + * @returns {Record} An object with the variables of the script, where the keys are the variable IDs and the values are ArcscriptVariable instances + */ + initializeVariables( + varObjects: Record, + varValues: Record + ): Record { + const variables: Record = {}; + Object.entries(varObjects).forEach(([, varObject]) => { + if (varObject.children) return; + + const variable = new ArcscriptVariable({ + id: varObject.id, + name: varObject.name, + type: varObject.type, + value: varValues[varObject.id], + defaultValue: varValues[varObject.id], + }); + variables[varObject.id] = variable; + }); + return variables; + } + getVar(name: string) { - return Object.values(this.varObjects).find(v => v.name === name); + return Object.values(this.variables).find(v => v.name === name); } getVarValue(id: string) { - if (id in this.changes) return this.changes[id]; - return this.varValues[id]; + return this.variables[id].getValue(); } setVarValues(ids: string[], values: VarValue[]) { ids.forEach((id, index) => { - this.changes[id] = values[index]; + this.variables[id].setValue(values[index]); }); } - getInitialVarValues() { - return Object.fromEntries( - Object.entries(this.varObjects) - .filter(([, v]) => !v.children) - .map(([k, v]) => [k, v.value]) - ); + resetVarValues(ids: string[]): void { + Object.entries(this.variables).forEach(([id, variable]) => { + if (ids.includes(id)) { + variable.reset(); + } + }); } - resetVarValues(ids: string[]) { - const initial = cloneDeep(this.getInitialVarValues()); - const values = ids.map(id => initial[id]); - this.setVarValues(ids, values); + getChanges(): Record { + const changes: Record = {}; + Object.entries(this.variables).forEach(([id, variable]) => { + if (variable.changed) { + changes[id] = variable.getValue(); + } + }); + return changes; } /** diff --git a/TypeScript/src/ArcscriptVariable.ts b/TypeScript/src/ArcscriptVariable.ts new file mode 100644 index 0000000..2a16572 --- /dev/null +++ b/TypeScript/src/ArcscriptVariable.ts @@ -0,0 +1,56 @@ +import { VarValue } from './types.js'; + +type ArcscriptVariableParams = { + id: string; + name: string; + type: string; + defaultValue: VarValue; + value?: VarValue; +}; + +export default class ArcscriptVariable { + id: string; + name: string; + type: string; + value: VarValue; + defaultValue: VarValue; + changed = false; + + constructor({ + id, + name, + defaultValue, + type, + value, + }: ArcscriptVariableParams) { + this.id = id; + this.name = name; + this.type = type; + this.defaultValue = defaultValue; + + 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/Generated/ArcscriptParserBase.ts b/TypeScript/src/Generated/ArcscriptParserBase.ts index 12960d9..0a72a3d 100644 --- a/TypeScript/src/Generated/ArcscriptParserBase.ts +++ b/TypeScript/src/Generated/ArcscriptParserBase.ts @@ -15,23 +15,24 @@ type ArcscriptParserOptions = { type ArcscriptFunctionInfo = { minArgs: number; maxArgs: number | null; + returnType?: 'number' | 'void'; }; 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' }, + resetAll: { minArgs: 0, maxArgs: null, returnType: 'void' }, + resetVisits: { minArgs: 0, maxArgs: 0, returnType: 'void' }, }; varObjects: Record = {}; @@ -67,10 +68,8 @@ export default class ArcscriptParserBase extends Parser { argListLength = argListCtx.VARIABLE_list().length; } } - const min = - ArcscriptParserBase.arcscriptFunctionsArgLength[token.text].minArgs; - const max = - ArcscriptParserBase.arcscriptFunctionsArgLength[token.text].maxArgs; + const min = ArcscriptParserBase.arcscriptFunctionsInfo[token.text].minArgs; + const max = ArcscriptParserBase.arcscriptFunctionsInfo[token.text].maxArgs; if ( (min !== null && argListLength < min) || diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index f936f71..b5e042b 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -48,16 +48,14 @@ export default class Interpreter { this.currentElement, this.emit ); - Object.entries(varValues).forEach(([key, value]) => { - visitor.state.changes[key] = value; - }); + const result = tree.accept(visitor); let output = visitor.state.generateOutput(); output = clearBlockStyle(output); return { - changes: visitor.state.changes, + changes: visitor.state.getChanges(), output, result, }; From bed14ac45a603db167c64ec502d8ae49ab3a2765 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Tue, 17 Feb 2026 18:02:13 +0200 Subject: [PATCH 02/21] chore: lint lexer --- grammar/ArcscriptLexer.g4 | 121 +++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 68 deletions(-) diff --git a/grammar/ArcscriptLexer.g4 b/grammar/ArcscriptLexer.g4 index 92a9a29..0f798fc 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+; @@ -65,15 +68,16 @@ RBRACE: '}'; BOOLEAN: 'true' | 'false'; -FNAME: 'abs'| - 'max'| - 'min'| - 'random'| - 'roll'| - 'round'| - 'sqr'| - 'sqrt'| - 'visits'; +FNAME: + 'abs' + | 'max' + | 'min' + | 'random' + | 'roll' + | 'round' + | 'sqr' + | 'sqrt' + | 'visits'; VFNAME: 'show' | 'resetVisits'; @@ -95,14 +99,8 @@ 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'"\\]; VARIABLE: [A-Za-z$_][0-9A-Za-z$_]*; @@ -128,48 +126,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 From 252b1c3b722155a700f7f4155b41c8805a89980e Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Wed, 18 Feb 2026 19:58:31 +0200 Subject: [PATCH 03/21] fix: add missing typescript dependency --- TypeScript/package.json | 1 + TypeScript/pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) 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) From 6fe0b35b60597a186d2e2f5a44e986f3457d7430 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Fri, 20 Feb 2026 15:13:20 +0200 Subject: [PATCH 04/21] feat: refactor grammar to support objects --- TypeScript/src/ArcscriptFunctions.ts | 15 +- TypeScript/src/ArcscriptVisitor.ts | 527 ++++++++---------- .../src/Generated/ArcscriptParserBase.ts | 70 ++- TypeScript/src/antlr4.interpreter.ts | 2 +- grammar/ArcscriptLexer.g4 | 16 +- grammar/ArcscriptParser.g4 | 104 ++-- 6 files changed, 356 insertions(+), 378 deletions(-) diff --git a/TypeScript/src/ArcscriptFunctions.ts b/TypeScript/src/ArcscriptFunctions.ts index 2456d14..d1c8fd2 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; @@ -157,7 +158,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 +186,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 +202,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/ArcscriptVisitor.ts b/TypeScript/src/ArcscriptVisitor.ts index ded70ea..f441746 100644 --- a/TypeScript/src/ArcscriptVisitor.ts +++ b/TypeScript/src/ArcscriptVisitor.ts @@ -2,43 +2,46 @@ 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 { - 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, + IdentifierExpressionContext, If_clauseContext, If_sectionContext, InputContext, + LiteralContext, + LiteralExpressionContext, + MemberExpressionContext, 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 { @@ -71,7 +74,9 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { } return { - condition: this.visitCompound_condition_or(ctx.compound_condition_or()), + condition: this.visitComparisonExpression( + ctx.expression() as ComparisonExpressionContext + ), }; }; @@ -181,38 +186,34 @@ 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.visitExpression(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.visitExpression(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(assignable.id); + let expressionValue = this.visitExpression(ctx.expression()); - let variableValue = this.state.getVarValue(variableObject.id); - let compound_condition_or = this.visitCompound_condition_or( - ctx.compound_condition_or() - ); 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]); + + this.state.setVarValues([assignable.id], [result]); return null; } if ( @@ -225,352 +226,294 @@ 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]); + this.state.setVarValues([assignable.id], [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()); - } - } - if (ctx.VFNAMEVARS()) { - fname = ctx.VFNAMEVARS().getText() as ArcscriptVoidFunctionKeys; - if (ctx.variable_list()) { - argument_list = this.visitVariable_list(ctx.variable_list()); - } + visitAssignable = (ctx: AssignableContext): ArcscriptVariable => { + const identifier = ctx.IDENTIFIER().getText(); + const variableObject = this.state.getVar(identifier); + if (!variableObject) { + throw new RuntimeError(`Variable ${identifier} not found`); } - - this.functions[fname!](...argument_list); + return variableObject; }; - 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()) - ); + visitExpression(ctx: ExpressionContext): VarValue { + if (ctx.constructor === ComparisonExpressionContext) { + return this.visitComparisonExpression(ctx); } - - 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.constructor === UnaryExpressionContext) { + return this.visitUnaryExpression(ctx); } - - 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.constructor === MultiplicativeExpressionContext) { + return this.visitMultiplicativeExpression(ctx); } - - 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.constructor === AdditiveExpressionContext) { + return this.visitAdditiveExpression(ctx); } - const conditional_operator = ctx.conditional_operator(); - const exp0 = this.visitExpression(ctx.expression(0)); - const exp1 = this.visitExpression(ctx.expression(1)); - - if (conditional_operator.GT()) { - return exp0 > exp1; + if (ctx.constructor === ParenthesizedExpressionContext) { + return this.visitParenthesizedExpression(ctx); } - if (conditional_operator.GE()) { - return exp0 >= exp1; + if (ctx.constructor === MemberExpressionContext) { + return this.visitMemberExpression(ctx); } - if (conditional_operator.LT()) { - return exp0 < exp1; + if (ctx.constructor === FunctionCallExpressionContext) { + return this.visitFunctionCallExpression(ctx); } - if (conditional_operator.LE()) { - return exp0 <= exp1; + if (ctx.constructor === LiteralExpressionContext) { + return this.visitLiteralExpression(ctx); } - if (conditional_operator.EQ()) { - return exp0 === exp1; + if (ctx.constructor === IdentifierExpressionContext) { + return this.visitIdentifierExpression(ctx); } - if (conditional_operator.NE()) { - return exp0 !== exp1; + throw new RuntimeError( + 'Invalid expression: Constructor = ' + ctx.constructor.name + ); + } + + visitComparisonExpression = (ctx: ComparisonExpressionContext): boolean => { + const left = this.visitExpression(ctx.expression(0)); + if (ctx.AND() || ctx.ANDKEYWORD()) { + if (!left) { + return false; + } + const right = this.visitExpression(ctx.expression(1)); + return Boolean(left) && Boolean(right); } - if (conditional_operator.ISKEYWORD()) { - if (conditional_operator.NOTKEYWORD()) { - return exp0 !== exp1; + if (ctx.OR() || ctx.ORKEYWORD()) { + if (left) { + return true; } - return exp0 === exp1; + const right = this.visitExpression(ctx.expression(1)); + return Boolean(left) || Boolean(right); } + const right = this.visitExpression(ctx.expression(1)); - return this.visitChildren(ctx) as VarValue; + if (ctx.EQ() || (ctx.ISKEYWORD() && !ctx.NOTKEYWORD())) { + return left === right; + } + if (ctx.NE() || (ctx.ISKEYWORD() && ctx.NOTKEYWORD())) { + return left !== right; + } + if (ctx.LT()) { + return left < right; + } + if (ctx.GT()) { + return left > right; + } + if (ctx.LE()) { + return left <= right; + } + if (ctx.GE()) { + return left >= right; + } + throw new RuntimeError('Invalid comparison operator'); }; - visitExpression = (ctx: ExpressionContext): VarValue => { - if (ctx.STRING()) { - let result = ctx.STRING().getText(); - result = result.replace(/^['"](.*)['"]$/, '$1'); - return result; + visitUnaryExpression = (ctx: UnaryExpressionContext) => { + if (ctx.NOTKEYWORD() || ctx.NEG()) { + return !this.visitExpression(ctx.expression()); } - if (ctx.BOOLEAN()) { - if (ctx.BOOLEAN().getText() === 'true') { - return true; - } - return false; + if (ctx.ADD()) { + return this.visitExpression(ctx.expression()); } - - return this.visitAdditive_numeric_expression( - ctx.additive_numeric_expression() - ); + if (ctx.SUB()) { + return -this.visitExpression(ctx.expression()); + } + throw new RuntimeError('Invalid unary operator'); }; - 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'; - - 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; - } - } + visitMultiplicativeExpression = (ctx: MultiplicativeExpressionContext) => { + const left = this.visitExpression(ctx.expression(0)); + const right = this.visitExpression(ctx.expression(1)); + if (typeof left === 'string' || typeof right === 'string') { + throw new RuntimeError('Invalid operation with string'); + } + const leftValue = new BigNumber(left as number); + const rightValue = new BigNumber(right as number); - 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 (ctx.MUL()) { + return leftValue.multipliedBy(rightValue).toNumber(); + } + if (ctx.DIV()) { + if (rightValue.isZero()) { + throw new RuntimeError('Invalid division by zero'); } - if (hasString) { - throw new RuntimeError('Invalid subtraction with string'); + return leftValue.dividedBy(rightValue).toNumber(); + } + if (ctx.MOD()) { + if (rightValue.isZero()) { + throw new RuntimeError('Invalid division by zero'); } - return new BigNumber(additive_numeric_expression as number) - .minus(new BigNumber(multiplicative_numeric_expression as number)) - .toNumber(); + return leftValue.modulo(rightValue).toNumber(); } - - const multiplicative_numeric_expression = - this.visitMultiplicative_numeric_expression( - ctx.multiplicative_numeric_expression() - ); - - return multiplicative_numeric_expression; + throw new RuntimeError('Invalid multiplicative operator'); }; - 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`); + visitAdditiveExpression = (ctx: AdditiveExpressionContext) => { + let left = this.visitExpression(ctx.expression(0)); + let right = this.visitExpression(ctx.expression(1)); + if (typeof left === 'string' || typeof right === 'string') { + if (ctx.ADD()) { + return (left as string) + (right as string); } - return result; + throw new RuntimeError('Invalid operation with string'); } + left = Number(left); + right = Number(right); - const signed_unary_numeric_expression = - this.visitSigned_unary_numeric_expression( - ctx.signed_unary_numeric_expression() - ); + const leftValue = new BigNumber(left as number); + const rightValue = new BigNumber(right as number); - return signed_unary_numeric_expression; + if (ctx.ADD()) { + return leftValue.plus(rightValue).toNumber(); + } + if (ctx.SUB()) { + return leftValue.minus(rightValue).toNumber(); + } + throw new RuntimeError('Invalid additive operator'); }; - visitSigned_unary_numeric_expression = ( - ctx: Signed_unary_numeric_expressionContext - ): VarValue => { - const unary_numeric_expression = this.visitUnary_numeric_expression( - ctx.unary_numeric_expression() - ); + visitParenthesizedExpression = (ctx: ParenthesizedExpressionContext) => { + return this.visitExpression(ctx.expression()); + }; - const sign = ctx.sign(); - if (sign) { - if (sign.ADD()) { - return +unary_numeric_expression; - } - return -unary_numeric_expression; - } - return unary_numeric_expression; + visitMemberExpression = (ctx: MemberExpressionContext) => { + throw new RuntimeError('Member expressions are not supported'); }; - visitUnary_numeric_expression = ( - ctx: Unary_numeric_expressionContext - ): VarValue => { - if (ctx.FLOAT()) { - return parseFloat(ctx.FLOAT().getText()); + visitIdentifierExpression = (ctx: IdentifierExpressionContext) => { + const identifier = ctx.IDENTIFIER().getText(); + const variableObject = this.state.getVar(identifier); + if (!variableObject) { + throw new RuntimeError(`Variable ${identifier} not found`); } - if (ctx.INTEGER()) { - return parseInt(ctx.INTEGER().getText(), 10); + + if (identifier === 'w') { + console.log( + 'Getting variable', + identifier, + 'with value', + variableObject.getValue() + ); // --- IGNORE --- } - 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.getValue(); + }; + + visitLiteralExpression = (ctx: LiteralExpressionContext) => { + return this.visitLiteral(ctx.literal()); + }; + + visitFunctionCallExpression = (ctx: FunctionCallExpressionContext) => { + return this.visitFunction_call(ctx.function_call()); + }; + + 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.name; + } + 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 => { + const name = identifier.getText(); + const variableObject = this.state.getVar(name); + if (!variableObject) { + throw new RuntimeError(`Variable ${name} not found`); + } + return variableObject; + }); + }; + 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.visitExpression(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 0a72a3d..159f66b 100644 --- a/TypeScript/src/Generated/ArcscriptParserBase.ts +++ b/TypeScript/src/Generated/ArcscriptParserBase.ts @@ -1,8 +1,9 @@ import { Parser, TerminalNode, Token, TokenStream } from 'antlr4'; import ArcscriptParser, { Argument_listContext, + Function_callContext, + Identifier_listContext, Mention_attributesContext, - Variable_listContext, } from './ArcscriptParser.js'; import { VarObject } from '../types.js'; @@ -16,6 +17,7 @@ type ArcscriptFunctionInfo = { minArgs: number; maxArgs: number | null; returnType?: 'number' | 'void'; + argType?: 'number' | 'variable' | 'mention'; }; export default class ArcscriptParserBase extends Parser { @@ -30,8 +32,18 @@ export default class ArcscriptParserBase extends Parser { 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' }, - resetAll: { minArgs: 0, 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' }, }; @@ -57,23 +69,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.arcscriptFunctionsInfo[token.text].minArgs; - const max = ArcscriptParserBase.arcscriptFunctionsInfo[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}`, @@ -81,6 +94,43 @@ 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 + ); + } + } + } + } + + assertFunctionReturnValue( + fncCtx: Function_callContext, + hasReturnValue = true + ) { + const functionName = fncCtx.FNAME().getText(); + const token = fncCtx.FNAME().symbol; + const { returnType } = + ArcscriptParserBase.arcscriptFunctionsInfo[functionName]; + if (hasReturnValue) { + if (returnType === 'void') { + this.notifyErrorListeners( + `The function ${functionName} does not return a value`, + token, + undefined + ); + } + } else if (returnType !== 'void') { + this.notifyErrorListeners( + `The function ${functionName} returns a value that is not being used`, + token, + undefined + ); + } } assertVariable(variableToken: Token) { diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index b5e042b..11aad50 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -99,7 +99,7 @@ export default class Interpreter { const tokenTypeNames = lexer.getSymbolicNames(); const allTokens = lexer.getAllTokens(); const variableTokens = allTokens.filter( - token => tokenTypeNames[token.type] === 'VARIABLE' + token => tokenTypeNames[token.type] === 'IDENTIFIER' ); const tokenIdMap = new Map(); diff --git a/grammar/ArcscriptLexer.g4 b/grammar/ArcscriptLexer.g4 index 0f798fc..86eb754 100644 --- a/grammar/ArcscriptLexer.g4 +++ b/grammar/ArcscriptLexer.g4 @@ -62,6 +62,7 @@ OR: '||'; ASSIGN: '='; NEG: '!'; +DOT: '.'; COMMA: ','; LBRACE: '{'; RBRACE: '}'; @@ -77,11 +78,11 @@ FNAME: | 'round' | 'sqr' | 'sqrt' - | 'visits'; - -VFNAME: 'show' | 'resetVisits'; - -VFNAMEVARS: 'reset' | 'resetAll'; + | 'visits' + | 'show' + | 'resetVisits' + | 'reset' + | 'resetAll'; IFKEYWORD: 'if'; @@ -102,7 +103,10 @@ NOTKEYWORD: 'not'; STRING: '"' STRING_CONTENT* '"' | '\'' STRING_CONTENT* '\''; fragment STRING_CONTENT: ~[\\\r\n'"] | '\\' [abfnrtv'"\\]; -VARIABLE: [A-Za-z$_][0-9A-Za-z$_]*; +IDENTIFIER: IDENTIFIER_START IDENTIFIER_PART*; + +fragment IDENTIFIER_START: [A-Za-z$_]; +fragment IDENTIFIER_PART: [0-9A-Za-z$_]; WHITESPACE: [ \t\r\n]+ -> skip; diff --git a/grammar/ArcscriptParser.g4 b/grammar/ArcscriptParser.g4 index 6b8a569..c9da532 100644 --- a/grammar/ArcscriptParser.g4 +++ b/grammar/ArcscriptParser.g4 @@ -7,7 +7,7 @@ options { superClass = ArcscriptParserBase; } -input: script EOF | codestart compound_condition_or codeend EOF; +input: script EOF | codestart expression codeend EOF; script: script_section+; @@ -51,27 +51,35 @@ 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; -statement_function_call: void_function_call; +assignable: IDENTIFIER {this.assertVariable($IDENTIFIER);}; + +statement_function_call: + function_call {this.assertFunctionReturnValue($function_call.ctx, false);}; + +identifier_list: + IDENTIFIER {this.assertVariable($IDENTIFIER);} ( + ',' IDENTIFIER {this.assertVariable($IDENTIFIER);} + )*; 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 +87,30 @@ mention: mention_attributes: ATTR_NAME (TAG_EQUALS ATTR_VALUE)?; -additive_numeric_expression: - multiplicative_numeric_expression - | additive_numeric_expression (ADD | SUB) multiplicative_numeric_expression; - -multiplicative_numeric_expression: - signed_unary_numeric_expression - | multiplicative_numeric_expression (MUL | DIV | MOD) signed_unary_numeric_expression; - -signed_unary_numeric_expression: - sign unary_numeric_expression - | unary_numeric_expression; - -unary_numeric_expression: - FLOAT - | VARIABLE {this.assertVariable($VARIABLE);} - | INTEGER - | STRING - | BOOLEAN - | function_call - | LPAREN compound_condition_or RPAREN; +expression: + expression '.' IDENTIFIER # MemberExpression + | (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);} # IdentifierExpression + | '(' expression ')' # ParenthesizedExpression + | function_call {this.assertFunctionReturnValue($function_call.ctx);} # FunctionCallExpression + | literal # LiteralExpression; + +literal: BOOLEAN | STRING | numeric_literal; + +numeric_literal: FLOAT | INTEGER; 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; + FNAME LPAREN identifier_list? RPAREN {this.assertFunctionArguments($FNAME, $identifier_list.ctx); + } + | FNAME LPAREN argument_list? RPAREN {this.assertFunctionArguments($FNAME, $argument_list.ctx);} + ; -expression: STRING | BOOLEAN | additive_numeric_expression; \ No newline at end of file +// expression: STRING | BOOLEAN | additive_numeric_expression; \ No newline at end of file From c056ace96f033c0c1ce9564a19f7e0e8184e902c Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Fri, 20 Feb 2026 16:49:08 +0200 Subject: [PATCH 05/21] fix: restore setting changes on runScript --- TypeScript/src/antlr4.interpreter.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index 11aad50..c8f51a2 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -49,6 +49,10 @@ export default class Interpreter { this.emit ); + Object.entries(varValues).forEach(([id, value]) => { + visitor.state.variables[id].setValue(value); + }); + const result = tree.accept(visitor); let output = visitor.state.generateOutput(); From 95ae32172db5507402d70d0091843a8c57e6b523 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Fri, 20 Feb 2026 19:05:23 +0200 Subject: [PATCH 06/21] feat: initial scoped identifier implementation --- TypeScript/src/ArcscriptFunctions.ts | 4 +- TypeScript/src/ArcscriptState.ts | 108 ++++-- TypeScript/src/ArcscriptVisitor.ts | 58 +-- .../src/Generated/ArcscriptParserBase.ts | 32 +- .../src/__tests__/antlr4.interpreter.test.ts | 49 +-- TypeScript/src/__tests__/conditions.json | 84 ++--- TypeScript/src/__tests__/parseErrors.json | 84 ++--- .../src/__tests__/replaceVariables.json | 84 ++--- TypeScript/src/__tests__/runtimeErrors.json | 84 ++--- TypeScript/src/__tests__/stringConcat.json | 148 ++++---- TypeScript/src/__tests__/valid.json | 334 ++++++++++++------ TypeScript/src/antlr4.interpreter.ts | 49 ++- TypeScript/src/types.ts | 11 +- grammar/ArcscriptParser.g4 | 13 +- 14 files changed, 648 insertions(+), 494 deletions(-) diff --git a/TypeScript/src/ArcscriptFunctions.ts b/TypeScript/src/ArcscriptFunctions.ts index d1c8fd2..fe2fca9 100644 --- a/TypeScript/src/ArcscriptFunctions.ts +++ b/TypeScript/src/ArcscriptFunctions.ts @@ -133,7 +133,9 @@ export default class ArcscriptFunctions { } return v.id; }); - const all = Object.keys(this.state.variables); + const all = Object.keys(this.state.scopedVariables).flatMap(scope => + Object.keys(this.state.scopedVariables[scope]) + ); const resetIds = all.filter(id => !except.includes(id)); this.state.resetVarValues(resetIds); } diff --git a/TypeScript/src/ArcscriptState.ts b/TypeScript/src/ArcscriptState.ts index 88ec841..4da0c17 100644 --- a/TypeScript/src/ArcscriptState.ts +++ b/TypeScript/src/ArcscriptState.ts @@ -1,4 +1,4 @@ -import { VarObject, VarValue } from './types.js'; +import { ArcscriptStateDef, VarValue } from './types.js'; import ArcscriptVariable from './ArcscriptVariable.js'; type OutputObject = { @@ -10,7 +10,8 @@ type OutputObject = { }; export default class ArcscriptState { - variables: Record; + scopedVariables: Record>; + elementVisits: Record; currentElement: string; outputs: OutputObject[]; @@ -22,13 +23,12 @@ export default class ArcscriptState { insertBlockquote: boolean; constructor( - varValues: Record, - varObjects: Record, + arcscriptVariables: ArcscriptStateDef, elementVisits: Record, currentElement: string, emit: (event: string, data?: unknown) => void ) { - this.variables = this.initializeVariables(varObjects, varValues); + this.scopedVariables = this.initializeVariables(arcscriptVariables); this.elementVisits = elementVisits; this.currentElement = currentElement; @@ -46,59 +46,95 @@ export default class ArcscriptState { /** * - * @param {*} varObjects - * @param {*} varValues + * @param {*} arcscriptVariables * @returns {Record} An object with the variables of the script, where the keys are the variable IDs and the values are ArcscriptVariable instances */ initializeVariables( - varObjects: Record, - varValues: Record - ): Record { - const variables: Record = {}; - Object.entries(varObjects).forEach(([, varObject]) => { - if (varObject.children) return; - - const variable = new ArcscriptVariable({ - id: varObject.id, - name: varObject.name, - type: varObject.type, - value: varValues[varObject.id], - defaultValue: varValues[varObject.id], + arcscriptVariables: ArcscriptStateDef + ): Record> { + const variables: Record> = {}; + + Object.entries(arcscriptVariables).forEach(([scope, vars]) => { + variables[scope] = {}; + Object.entries(vars).forEach(([id, varDef]) => { + const variable = new ArcscriptVariable({ + id, + name: varDef.name, + type: varDef.type, + defaultValue: varDef.defaultValue, + }); + variables[scope][id] = variable; }); - variables[varObject.id] = variable; }); return variables; } - getVar(name: string) { - return Object.values(this.variables).find(v => v.name === name); + getVar(name: string): ArcscriptVariable { + const nameParts = name.split('.'); + if (nameParts.length === 1) { + const variable = Object.values(this.scopedVariables.global).find( + v => v.name === name + ); + if (!variable) { + throw new Error(`Variable ${name} not found`); + } + return variable; + } + if (nameParts.length > 2) { + throw new Error(`Invalid variable name: ${name}`); + } + const [scope, varName] = nameParts; + const variable = Object.values(this.scopedVariables[scope]).find( + v => v.name === varName + ); + if (!variable) { + throw new Error(`Variable ${name} not found`); + } + return variable; } - getVarValue(id: string) { - return this.variables[id].getValue(); + getVarValue(name: string) { + return this.getVar(name).getValue(); } setVarValues(ids: string[], values: VarValue[]) { ids.forEach((id, index) => { - this.variables[id].setValue(values[index]); + Object.entries(this.scopedVariables).find(([, variables]) => { + if (variables[id]) { + variables[id].setValue(values[index]); + return true; + } + return false; + }); }); } resetVarValues(ids: string[]): void { - Object.entries(this.variables).forEach(([id, variable]) => { - if (ids.includes(id)) { - variable.reset(); - } + Object.entries(this.scopedVariables).forEach(([, variables]) => { + ids.forEach(id => { + if (variables[id]) { + variables[id].reset(); + } + }); }); } - getChanges(): Record { - const changes: Record = {}; - Object.entries(this.variables).forEach(([id, variable]) => { - if (variable.changed) { - changes[id] = variable.getValue(); + getChanges() { + const changes: Record> = {}; + Object.entries(this.scopedVariables).forEach( + ([scope, variables]: [string, Record]) => { + Object.entries(variables).forEach( + ([id, variable]: [string, ArcscriptVariable]) => { + if (variable.changed) { + if (!changes[scope]) { + changes[scope] = {}; + } + changes[scope][id] = variable.getValue(); + } + } + ); } - }); + ); return changes; } diff --git a/TypeScript/src/ArcscriptVisitor.ts b/TypeScript/src/ArcscriptVisitor.ts index f441746..62ee614 100644 --- a/TypeScript/src/ArcscriptVisitor.ts +++ b/TypeScript/src/ArcscriptVisitor.ts @@ -5,7 +5,7 @@ import ArcscriptFunctions, { } 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 { AdditiveExpressionContext, Argument_listContext, @@ -23,13 +23,13 @@ import { Function_callContext, FunctionCallExpressionContext, Identifier_listContext, + IdentifierContext, IdentifierExpressionContext, If_clauseContext, If_sectionContext, InputContext, LiteralContext, LiteralExpressionContext, - MemberExpressionContext, Mention_attributesContext, MentionContext, MultiplicativeExpressionContext, @@ -48,22 +48,10 @@ 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); } @@ -197,7 +185,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { visitStatement_assignment = (ctx: Statement_assignmentContext) => { const assignable = this.visitAssignable(ctx.assignable()); - let variableValue = this.state.getVarValue(assignable.id); + let variableValue = this.state.getVarValue(assignable.name); let expressionValue = this.visitExpression(ctx.expression()); let result: VarValue = 0; @@ -258,7 +246,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { }; visitAssignable = (ctx: AssignableContext): ArcscriptVariable => { - const identifier = ctx.IDENTIFIER().getText(); + const identifier = ctx.identifier().getText(); const variableObject = this.state.getVar(identifier); if (!variableObject) { throw new RuntimeError(`Variable ${identifier} not found`); @@ -282,9 +270,6 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { if (ctx.constructor === ParenthesizedExpressionContext) { return this.visitParenthesizedExpression(ctx); } - if (ctx.constructor === MemberExpressionContext) { - return this.visitMemberExpression(ctx); - } if (ctx.constructor === FunctionCallExpressionContext) { return this.visitFunctionCallExpression(ctx); } @@ -406,26 +391,10 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { return this.visitExpression(ctx.expression()); }; - visitMemberExpression = (ctx: MemberExpressionContext) => { - throw new RuntimeError('Member expressions are not supported'); - }; - visitIdentifierExpression = (ctx: IdentifierExpressionContext) => { - const identifier = ctx.IDENTIFIER().getText(); - const variableObject = this.state.getVar(identifier); - if (!variableObject) { - throw new RuntimeError(`Variable ${identifier} not found`); - } + const identifier = this.visitIdentifier(ctx.identifier()); - if (identifier === 'w') { - console.log( - 'Getting variable', - identifier, - 'with value', - variableObject.getValue() - ); // --- IGNORE --- - } - return variableObject.getValue(); + return identifier.getValue(); }; visitLiteralExpression = (ctx: LiteralExpressionContext) => { @@ -436,6 +405,15 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { return this.visitFunction_call(ctx.function_call()); }; + visitIdentifier = (ctx: IdentifierContext): ArcscriptVariable => { + const name = ctx.getText(); + const variableObject = this.state.getVar(name); + if (!variableObject) { + throw new RuntimeError(`Variable ${name} not found`); + } + return variableObject; + }; + visitLiteral = (ctx: LiteralContext) => { if (ctx.BOOLEAN()) { if (ctx.BOOLEAN().getText() === 'true') { @@ -491,7 +469,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { }; visitIdentifier_list = (ctx: Identifier_listContext): ArcscriptVariable[] => { - return ctx.IDENTIFIER_list().map(identifier => { + return ctx.identifier_list().map(identifier => { const name = identifier.getText(); const variableObject = this.state.getVar(name); if (!variableObject) { diff --git a/TypeScript/src/Generated/ArcscriptParserBase.ts b/TypeScript/src/Generated/ArcscriptParserBase.ts index 159f66b..c562baa 100644 --- a/TypeScript/src/Generated/ArcscriptParserBase.ts +++ b/TypeScript/src/Generated/ArcscriptParserBase.ts @@ -3,12 +3,13 @@ import ArcscriptParser, { Argument_listContext, Function_callContext, Identifier_listContext, + IdentifierContext, Mention_attributesContext, } from './ArcscriptParser.js'; -import { VarObject } from '../types.js'; +import { ArcscriptStateDef } from '../types.js'; type ArcscriptParserOptions = { - varObjects: Record; + arcscriptVariables: ArcscriptStateDef; elementVisits: Record; currentElement: string; }; @@ -47,7 +48,7 @@ export default class ArcscriptParserBase extends Parser { resetVisits: { minArgs: 0, maxArgs: 0, returnType: 'void' }, }; - varObjects: Record = {}; + arcscriptVariableNames: string[] = []; elementVisits: Record = {}; currentElement: string = ''; currentLine: number = 0; @@ -58,7 +59,16 @@ export default class ArcscriptParserBase extends Parser { } setOptions(options: ArcscriptParserOptions) { - this.varObjects = options.varObjects; + this.arcscriptVariableNames = Object.entries( + options.arcscriptVariables + ).flatMap(([scope, vars]) => { + return Object.values(vars).map(variable => { + if (scope === 'global') { + return variable.name; + } + return `${scope}.${variable.name}`; + }); + }); this.elementVisits = options.elementVisits; this.currentElement = options.currentElement; } @@ -77,7 +87,7 @@ export default class ArcscriptParserBase extends Parser { if (argListCtx instanceof Argument_listContext) { argListLength = argListCtx.argument_list().length; } else if (argListCtx instanceof Identifier_listContext) { - argListLength = argListCtx.IDENTIFIER_list().length; + argListLength = argListCtx.identifier_list().length; } } @@ -133,16 +143,12 @@ export default class ArcscriptParserBase extends Parser { } } - 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..85b5833 100644 --- a/TypeScript/src/__tests__/antlr4.interpreter.test.ts +++ b/TypeScript/src/__tests__/antlr4.interpreter.test.ts @@ -6,7 +6,7 @@ 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 { ArcscriptStateDef, VarValue } from '../types.js'; type TestCase = { code: string; @@ -19,17 +19,7 @@ type TestCase = { variableChanges?: Record; }; -function setVarValues(vars: Record) { - return Object.fromEntries( - Object.entries(vars) - .filter(([, v]) => !v.children) - .map(([k, v]) => [k, v.value]) - ); -} - describe('Interprete valid scripts', () => { - const varObjects = validTests.initialVars as Record; - const varValues = setVarValues(varObjects); const cases = validTests.cases as TestCase[]; test.each(cases)( @@ -45,8 +35,7 @@ describe('Interprete valid scripts', () => { const eventHandler = vi.fn(); const interpreter = new Interpreter( - varValues, - varObjects, + validTests.initialVars as ArcscriptStateDef, visits, elementId, eventHandler @@ -67,9 +56,6 @@ describe('Interprete valid scripts', () => { }); describe('Interprete string test scripts', () => { - const varObjects = stringTests.initialVars as Record; - const varValues = setVarValues(varObjects); - test.each(stringTests.cases as unknown as TestCase[])( 'Tests script: $code', ({ @@ -80,8 +66,7 @@ describe('Interprete string test scripts', () => { elementId = '', }) => { const interpreter = new Interpreter( - varValues, - varObjects, + stringTests.initialVars as ArcscriptStateDef, visits, elementId ); @@ -93,15 +78,11 @@ describe('Interprete string test scripts', () => { }); 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, + parseErrorTests.initialVars as ArcscriptStateDef, visits, elementId ); @@ -113,15 +94,11 @@ 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, + runtimeErrorTests.initialVars as ArcscriptStateDef, visits, elementId ); @@ -133,15 +110,11 @@ 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, + conditionTests.initialVars as ArcscriptStateDef, visits, elementId ); @@ -153,17 +126,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[])( 'Tests replace: $code', ({ code, variableChanges = {}, result: expectedResult }) => { // Parse and check the condition - const interpreter = new Interpreter(varValues, varObjects); + const interpreter = new Interpreter( + replaceVariableTests.initialVars as ArcscriptStateDef + ); const result = interpreter.replaceVariables(code, variableChanges); // The given condition should match the expected evaluation diff --git a/TypeScript/src/__tests__/conditions.json b/TypeScript/src/__tests__/conditions.json index 88331f1..4c3e321 100644 --- a/TypeScript/src/__tests__/conditions.json +++ b/TypeScript/src/__tests__/conditions.json @@ -1,46 +1,48 @@ { "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 + "global": { + "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 + } } }, "cases": [ diff --git a/TypeScript/src/__tests__/parseErrors.json b/TypeScript/src/__tests__/parseErrors.json index 07d79d9..1113748 100644 --- a/TypeScript/src/__tests__/parseErrors.json +++ b/TypeScript/src/__tests__/parseErrors.json @@ -1,46 +1,48 @@ { "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 + "global": { + "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 + } } }, "cases": [ diff --git a/TypeScript/src/__tests__/replaceVariables.json b/TypeScript/src/__tests__/replaceVariables.json index 4fc89b0..0dd7570 100644 --- a/TypeScript/src/__tests__/replaceVariables.json +++ b/TypeScript/src/__tests__/replaceVariables.json @@ -1,46 +1,48 @@ { "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 + "global": { + "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 + } } }, "cases": [ diff --git a/TypeScript/src/__tests__/runtimeErrors.json b/TypeScript/src/__tests__/runtimeErrors.json index 9adb85e..85aeb61 100644 --- a/TypeScript/src/__tests__/runtimeErrors.json +++ b/TypeScript/src/__tests__/runtimeErrors.json @@ -1,46 +1,48 @@ { "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 + "global": { + "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 + } } }, "cases": [ diff --git a/TypeScript/src/__tests__/stringConcat.json b/TypeScript/src/__tests__/stringConcat.json index d9b3503..69bd426 100644 --- a/TypeScript/src/__tests__/stringConcat.json +++ b/TypeScript/src/__tests__/stringConcat.json @@ -1,131 +1,159 @@ { "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" + "global": { + "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" + "global": { + "var4": "testing" + } } }, { "code": "
w = \"test\" + \"ing\"
", "changes": { - "var4": "testing" + "global": { + "var4": "testing" + } } }, { "code": "
w = 0.42 + \"ing\"
", "changes": { - "var4": "0.42ing" + "global": { + "var4": "0.42ing" + } } }, { "code": "
w = 0.42 + \"\"
", "changes": { - "var4": "0.42" + "global": { + "var4": "0.42" + } } }, { "code": "
w = \"test\" + 44
", "changes": { - "var4": "test44" + "global": { + "var4": "test44" + } } }, { "code": "
w = \"test\" + false
", "changes": { - "var4": "testfalse" + "global": { + "var4": "testfalse" + } } }, { "code": "
w = \"test\" + true + true
", "changes": { - "var4": "testtruetrue" + "global": { + "var4": "testtruetrue" + } } }, { "code": "
w = true + true + \"test\"
", "changes": { - "var4": "2test" + "global": { + "var4": "2test" + } } }, { "code": "
w += \" test\"
", "changes": { - "var4": "Dummy text test" + "global": { + "var4": "Dummy text test" + } } }, { "code": "
x += \"test\"
", "changes": { - "var1": "14test" + "global": { + "var1": "14test" + } } }, { "code": "
w += 42
", "changes": { - "var4": "Dummy text42" + "global": { + "var4": "Dummy text42" + } } }, { "code": "
w += _a
", "changes": { - "var4": "Dummy textfalse" + "global": { + "var4": "Dummy textfalse" + } } }, { "code": "
w += man
", "changes": { - "var4": "Dummy textDifferent Text" + "global": { + "var4": "Dummy textDifferent Text" + } } } ] diff --git a/TypeScript/src/__tests__/valid.json b/TypeScript/src/__tests__/valid.json index d50ed3b..d2db91b 100644 --- a/TypeScript/src/__tests__/valid.json +++ b/TypeScript/src/__tests__/valid.json @@ -1,314 +1,402 @@ { "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" + "global": { + "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": "
x=5
", "changes": { - "var1": 5 + "global": { + "var1": 5 + } } }, { "code": "
x=-5
", "changes": { - "var1": -5 + "global": { + "var1": -5 + } } }, { "code": "
x=5.23
", "changes": { - "var1": 5.23 + "global": { + "var1": 5.23 + } } }, { "code": "
x=3
y=2
", "changes": { - "var1": 3, - "var2": 2 + "global": { + "var1": 3, + "var2": 2 + } } }, { "code": "
x=\"test\"
", "changes": { - "var1": "test" + "global": { + "var1": "test" + } } }, { "code": "
x=true
", "changes": { - "var1": true + "global": { + "var1": true + } } }, { "code": "
x = y
", "changes": { - "var1": 15 + "global": { + "var1": 15 + } } }, { "code": "
x = 5 + 3
", "changes": { - "var1": 8 + "global": { + "var1": 8 + } } }, { "code": "
x = 10 % 3
", "changes": { - "var1": 1 + "global": { + "var1": 1 + } } }, { "code": "
x = 10 % 3 * 2
", "changes": { - "var1": 2 + "global": { + "var1": 2 + } } }, { "code": "
x = 10 % (3 * 2)
", "changes": { - "var1": 4 + "global": { + "var1": 4 + } } }, { "code": "
x = 5 + 6 % 3
", "changes": { - "var1": 5 + "global": { + "var1": 5 + } } }, { "code": "
x %= 6
", "changes": { - "var1": 2 + "global": { + "var1": 2 + } } }, { "code": "
x = 10 % 7 % 2
", "changes": { - "var1": 1 + "global": { + "var1": 1 + } } }, { "code": "
x = 3 / 4 * 5
", "changes": { - "var1": 3.75 + "global": { + "var1": 3.75 + } } }, { "code": "
x = 3 + 4 * 5
", "changes": { - "var1": 23 + "global": { + "var1": 23 + } } }, { "code": "
x = 3 * 4 + 5
", "changes": { - "var1": 17 + "global": { + "var1": 17 + } } }, { "code": "
x = 3 / 4 / 5
", "changes": { - "var1": 0.15 + "global": { + "var1": 0.15 + } } }, { "code": "
x = sqr(2)
", "changes": { - "var1": 4 + "global": { + "var1": 4 + } } }, { "code": "
x = 3 > 2
", "changes": { - "var1": true + "global": { + "var1": true + } } }, { "code": "
x = 3 >= 2
", "changes": { - "var1": true + "global": { + "var1": true + } } }, { "code": "
x = 3 < 2
", "changes": { - "var1": false + "global": { + "var1": false + } } }, { "code": "
x = 3 <= 2
", "changes": { - "var1": false + "global": { + "var1": false + } } }, { "code": "
x = 3 == 2
", "changes": { - "var1": false + "global": { + "var1": false + } } }, { "code": "
x = 3 != 2
", "changes": { - "var1": true + "global": { + "var1": true + } } }, { "code": "
_a = !(3 == 2)
", "changes": { - "var6": true + "global": { + "var6": true + } } }, { "code": "
_a = not (3 == 2)
", "changes": { - "var6": true + "global": { + "var6": true + } } }, { "code": "
x = !0
", "changes": { - "var1": true + "global": { + "var1": true + } } }, { "code": "
x = true || false
", "changes": { - "var1": true + "global": { + "var1": true + } } }, { "code": "
x = true && false
", "changes": { - "var1": false + "global": { + "var1": false + } } }, { "code": "
x = -3
y = y + 3
z = sqr(x)
", "changes": { - "var1": -3, - "var2": 18, - "var3": 9 + "global": { + "var1": -3, + "var2": 18, + "var3": 9 + } } }, { "code": "
x = sqrt(y + 2)
", "changes": { - "var1": 4.123105625617661 + "global": { + "var1": 4.123105625617661 + } } }, { "code": "
x = z + 12 == 4 * 3
", "changes": { - "var1": true + "global": { + "var1": true + } } }, { "code": "
x = y == 15 && !(z == 1)
", "changes": { - "var1": true + "global": { + "var1": true + } } }, { "code": "
x = (x*x + 3)/y
", "changes": { - "var1": 13.266666666666667 + "global": { + "var1": 13.266666666666667 + } } }, { "code": "
x = x*(x + 1)/y
", "changes": { - "var1": 14 + "global": { + "var1": 14 + } } }, { "code": "
x += 2
", "changes": { - "var1": 16 + "global": { + "var1": 16 + } } }, { "code": "
x -= 2
", "changes": { - "var1": 12 + "global": { + "var1": 12 + } } }, { "code": "
x *= 2
", "changes": { - "var1": 28 + "global": { + "var1": 28 + } } }, { "code": "
x /= 2
", "changes": { - "var1": 7 + "global": { + "var1": 7 + } } }, { "code": "
x = abs(-6)
", "changes": { - "var1": 6 + "global": { + "var1": 6 + } } }, { "code": "
x = min(5, -y, 2.3)
", "changes": { - "var1": -15 + "global": { + "var1": -15 + } } }, { "code": "
x = max(5, -y, 2.3)
", "changes": { - "var1": 5 + "global": { + "var1": 5 + } } }, { @@ -330,7 +418,9 @@ { "code": "
x = round(2.65)
", "changes": { - "var1": 3 + "global": { + "var1": 3 + } } }, { @@ -341,26 +431,32 @@ { "code": "
y = 33
reset(y)
", "changes": { - "var2": 15 + "global": { + "var2": 15 + } } }, { "code": "
x = 16
y = 33
resetAll(y)
", "changes": { - "var1": 14, - "var2": 33, - "var3": 0, - "var4": "Dummy text", - "var5": 0, - "var6": false, - "var7": -1, - "var8": "Different Text" + "global": { + "var1": 14, + "var2": 33, + "var3": 0, + "var4": "Dummy text", + "var5": 0, + "var6": false, + "var7": -1, + "var8": "Different Text" + } } }, { "code": "
$c5 = x
", "changes": { - "var5": 14 + "global": { + "var5": 14 + } } }, { @@ -378,25 +474,33 @@ { "code": "
if (x==14) 
x=5
endif
", "changes": { - "var1": 5 + "global": { + "var1": 5 + } } }, { "code": "
if (x==11)
x=5
else
x=7
endif
", "changes": { - "var1": 7 + "global": { + "var1": 7 + } } }, { "code": "
if x==3
x=5
elseif x==14
x=2
else
x = 0
endif
", "changes": { - "var1": 2 + "global": { + "var1": 2 + } } }, { "code": "

Mein gott!

x = 1

What is that?

", "changes": { - "var1": 1 + "global": { + "var1": 1 + } }, "output": "

Mein gott! What is that?

" }, @@ -423,8 +527,10 @@ { "code": "
_a = true
if (((_a)))
x = 1
endif
", "changes": { - "var1": 1, - "var6": true + "global": { + "var1": 1, + "var6": true + } } }, { @@ -454,7 +560,9 @@ }, "elementId": "a", "changes": { - "var1": 0 + "global": { + "var1": 0 + } }, "events": [ { @@ -519,7 +627,9 @@ "code": "

Test

_a = true

this

", "output": "

Test this

", "changes": { - "var6": true + "global": { + "var6": true + } } }, { diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index c8f51a2..2ac3e84 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -4,11 +4,12 @@ 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, VarDef, VarValue } from './types.js'; +import ArcscriptState from './ArcscriptState.js'; export default class Interpreter { - varValues: Record; - varObjects: Record; + arcscriptVariables: ArcscriptStateDef; + state: ArcscriptState | null = null; elementVisits: Record; currentElement: string; variableOffsets: { start: number; end: number }[]; @@ -16,21 +17,18 @@ export default class Interpreter { /** * Interpreter constructor - * @param varValues - * @param varObjects + * @param arcscriptVariables * @param elementVisits The current element visits * @param currentElement The current element ID * @param eventHandler The event callback */ constructor( - varValues: Record, - varObjects: Record, + arcscriptVariables: ArcscriptStateDef, elementVisits: Record = {}, currentElement: string = '', eventHandler: (event: string, data?: unknown) => void = () => {} ) { - this.varValues = varValues; - this.varObjects = varObjects; + this.arcscriptVariables = arcscriptVariables; this.elementVisits = elementVisits; this.currentElement = currentElement; this.variableOffsets = []; @@ -39,19 +37,25 @@ export default class Interpreter { 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 ); + const { tree } = this.parse(code); + + const visitor = new ArcscriptVisitor(this.state); + + const varIds: string[] = []; + const varValuesList: VarValue[] = []; Object.entries(varValues).forEach(([id, value]) => { - visitor.state.variables[id].setValue(value); + varIds.push(id); + varValuesList.push(value); }); + this.state.setVarValues(varIds, varValuesList); const result = tree.accept(visitor); @@ -74,7 +78,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, }); @@ -108,10 +112,17 @@ export default class Interpreter { const tokenIdMap = new Map(); variableTokens.forEach(varToken => { - const targetVar = Object.values(this.varObjects).find( - variable => variable.name === varToken.text - ); - if (targetVar?.id) { + let targetVar: VarDef | null = null; + for (const scopeVars of Object.values(this.arcscriptVariables)) { + const v = Object.values(scopeVars).find( + variable => variable.name === varToken.text + ); + if (v) { + targetVar = v; + break; + } + } + if (targetVar !== null) { tokenIdMap.set(varToken, targetVar.id); } }); diff --git a/TypeScript/src/types.ts b/TypeScript/src/types.ts index 8c49687..ba43e5d 100644 --- a/TypeScript/src/types.ts +++ b/TypeScript/src/types.ts @@ -1,15 +1,20 @@ 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; }; export type MentionResult = { attrs: Record; label: string; }; + +export type VariableScope = 'global' | 'components' | 'elements'; + +export type ArcscriptStateDef = { + [key in VariableScope]?: Record; +}; diff --git a/grammar/ArcscriptParser.g4 b/grammar/ArcscriptParser.g4 index c9da532..22f5f89 100644 --- a/grammar/ArcscriptParser.g4 +++ b/grammar/ArcscriptParser.g4 @@ -67,14 +67,14 @@ statement_assignment: | ASSIGN ) expression; -assignable: IDENTIFIER {this.assertVariable($IDENTIFIER);}; +assignable: identifier {this.assertVariable($identifier.ctx);}; statement_function_call: function_call {this.assertFunctionReturnValue($function_call.ctx, false);}; identifier_list: - IDENTIFIER {this.assertVariable($IDENTIFIER);} ( - ',' IDENTIFIER {this.assertVariable($IDENTIFIER);} + identifier {this.assertVariable($identifier.ctx);} ( + ',' identifier {this.assertVariable($identifier.ctx);} )*; argument_list: argument (',' argument)*; @@ -88,8 +88,7 @@ mention: mention_attributes: ATTR_NAME (TAG_EQUALS ATTR_VALUE)?; expression: - expression '.' IDENTIFIER # MemberExpression - | (NEG | NOTKEYWORD) expression # UnaryExpression + (NEG | NOTKEYWORD) expression # UnaryExpression | ADD expression # UnaryExpression | SUB expression # UnaryExpression | expression (MUL | DIV | MOD) expression # MultiplicativeExpression @@ -98,7 +97,7 @@ expression: | expression (EQ | NE | (ISKEYWORD NOTKEYWORD) | ISKEYWORD) expression # ComparisonExpression | expression (AND | ANDKEYWORD) expression # ComparisonExpression | expression (OR | ORKEYWORD) expression # ComparisonExpression - | IDENTIFIER {this.assertVariable($IDENTIFIER);} # IdentifierExpression + | identifier {this.assertVariable($identifier.ctx);} # IdentifierExpression | '(' expression ')' # ParenthesizedExpression | function_call {this.assertFunctionReturnValue($function_call.ctx);} # FunctionCallExpression | literal # LiteralExpression; @@ -107,6 +106,8 @@ literal: BOOLEAN | STRING | numeric_literal; numeric_literal: FLOAT | INTEGER; +identifier: IDENTIFIER | IDENTIFIER '.' IDENTIFIER; + function_call: FNAME LPAREN identifier_list? RPAREN {this.assertFunctionArguments($FNAME, $identifier_list.ctx); } From 5f875684f541875f68c9b260710f03d73c191442 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Fri, 20 Feb 2026 19:06:20 +0200 Subject: [PATCH 07/21] chore: cleanup grammar --- grammar/ArcscriptParser.g4 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/grammar/ArcscriptParser.g4 b/grammar/ArcscriptParser.g4 index 22f5f89..745ee3d 100644 --- a/grammar/ArcscriptParser.g4 +++ b/grammar/ArcscriptParser.g4 @@ -112,6 +112,4 @@ function_call: FNAME LPAREN identifier_list? RPAREN {this.assertFunctionArguments($FNAME, $identifier_list.ctx); } | FNAME LPAREN argument_list? RPAREN {this.assertFunctionArguments($FNAME, $argument_list.ctx);} - ; - -// expression: STRING | BOOLEAN | additive_numeric_expression; \ No newline at end of file + ; \ No newline at end of file From ce6f98f34ac273be5fbf78e8e54a628d6e0f6e45 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Tue, 24 Feb 2026 14:06:21 +0200 Subject: [PATCH 08/21] feat: scoped variables resolving --- TypeScript/src/ArcscriptFunctions.ts | 32 ++--- TypeScript/src/ArcscriptState.ts | 107 ++++++++------ TypeScript/src/ArcscriptVariable.ts | 4 + TypeScript/src/ArcscriptVisitor.ts | 34 +++-- .../src/Generated/ArcscriptParserBase.ts | 30 ++-- .../src/__tests__/antlr4.interpreter.test.ts | 92 +++++++----- TypeScript/src/__tests__/member.json | 135 ++++++++++++++++++ TypeScript/src/__tests__/valid.json | 25 ++-- TypeScript/src/antlr4.interpreter.ts | 31 ++-- TypeScript/src/types.ts | 6 +- 10 files changed, 338 insertions(+), 158 deletions(-) create mode 100644 TypeScript/src/__tests__/member.json diff --git a/TypeScript/src/ArcscriptFunctions.ts b/TypeScript/src/ArcscriptFunctions.ts index fe2fca9..c95982f 100644 --- a/TypeScript/src/ArcscriptFunctions.ts +++ b/TypeScript/src/ArcscriptFunctions.ts @@ -105,39 +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 reset. 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.scopedVariables).flatMap(scope => - Object.keys(this.state.scopedVariables[scope]) + const variablesToReset = Object.values(this.state.globalVariables).filter( + v => !except.includes(v.id) ); - const resetIds = all.filter(id => !except.includes(id)); - this.state.resetVarValues(resetIds); + variablesToReset.forEach(variable => variable.reset()); } round(...args: ArgumentTypes): number { diff --git a/TypeScript/src/ArcscriptState.ts b/TypeScript/src/ArcscriptState.ts index 4da0c17..1758c24 100644 --- a/TypeScript/src/ArcscriptState.ts +++ b/TypeScript/src/ArcscriptState.ts @@ -1,4 +1,4 @@ -import { ArcscriptStateDef, VarValue } from './types.js'; +import { ArcscriptStateDef, ScopedVariableDef, VarValue } from './types.js'; import ArcscriptVariable from './ArcscriptVariable.js'; type OutputObject = { @@ -10,6 +10,7 @@ type OutputObject = { }; export default class ArcscriptState { + globalVariables: Record; scopedVariables: Record>; elementVisits: Record; @@ -28,7 +29,9 @@ export default class ArcscriptState { currentElement: string, emit: (event: string, data?: unknown) => void ) { - this.scopedVariables = this.initializeVariables(arcscriptVariables); + const { global, scoped } = this.initializeVariables(arcscriptVariables); + this.globalVariables = global; + this.scopedVariables = scoped; this.elementVisits = elementVisits; this.currentElement = currentElement; @@ -44,35 +47,53 @@ export default class ArcscriptState { this.insertBlockquote = false; } - /** - * - * @param {*} arcscriptVariables - * @returns {Record} An object with the variables of the script, where the keys are the variable IDs and the values are ArcscriptVariable instances - */ - initializeVariables( - arcscriptVariables: ArcscriptStateDef - ): Record> { - const variables: Record> = {}; - - Object.entries(arcscriptVariables).forEach(([scope, vars]) => { - variables[scope] = {}; - Object.entries(vars).forEach(([id, varDef]) => { - const variable = new ArcscriptVariable({ - id, - name: varDef.name, - type: varDef.type, - defaultValue: varDef.defaultValue, + initializeVariables(arcscriptVariables: ArcscriptStateDef) { + const globalScope: Record = {}; + const scopeVariables: Record< + string, + Record + > = {}; + Object.entries(arcscriptVariables).forEach(([scopeType, vars]) => { + if (scopeType === 'global') { + Object.entries(vars).forEach(([id, varDef]) => { + const variable = new ArcscriptVariable({ + id, + name: varDef.name, + type: varDef.type, + defaultValue: varDef.defaultValue, + scope: scopeType, + }); + globalScope[id] = variable; }); - variables[scope][id] = variable; - }); + } else { + Object.entries(vars).forEach( + ([parentId, scopedVars]: [string, ScopedVariableDef]) => { + if (!scopeVariables[parentId]) { + scopeVariables[parentId] = {}; + } + Object.entries(scopedVars).forEach(([id, varDef]) => { + const variable = new ArcscriptVariable({ + id, + name: varDef.name, + type: varDef.type, + defaultValue: varDef.defaultValue, + scope: scopeType, + }); + scopeVariables[parentId][id] = variable; + }); + } + ); + } }); - return variables; + return { + global: globalScope, + scoped: scopeVariables, + }; } - getVar(name: string): ArcscriptVariable { - const nameParts = name.split('.'); - if (nameParts.length === 1) { - const variable = Object.values(this.scopedVariables.global).find( + getVar(name: string, scope: string = 'global'): ArcscriptVariable { + if (scope === 'global') { + const variable = Object.values(this.globalVariables).find( v => v.name === name ); if (!variable) { @@ -80,12 +101,8 @@ export default class ArcscriptState { } return variable; } - if (nameParts.length > 2) { - throw new Error(`Invalid variable name: ${name}`); - } - const [scope, varName] = nameParts; const variable = Object.values(this.scopedVariables[scope]).find( - v => v.name === varName + v => v.name === name ); if (!variable) { throw new Error(`Variable ${name} not found`); @@ -93,10 +110,6 @@ export default class ArcscriptState { return variable; } - getVarValue(name: string) { - return this.getVar(name).getValue(); - } - setVarValues(ids: string[], values: VarValue[]) { ids.forEach((id, index) => { Object.entries(this.scopedVariables).find(([, variables]) => { @@ -109,18 +122,18 @@ export default class ArcscriptState { }); } - resetVarValues(ids: string[]): void { - Object.entries(this.scopedVariables).forEach(([, variables]) => { - ids.forEach(id => { - if (variables[id]) { - variables[id].reset(); - } - }); - }); - } - getChanges() { const changes: Record> = {}; + Object.entries(this.globalVariables).forEach( + ([id, variable]: [string, ArcscriptVariable]) => { + if (variable.changed) { + if (!changes['global']) { + changes['global'] = {}; + } + changes['global'][id] = variable.getValue(); + } + } + ); Object.entries(this.scopedVariables).forEach( ([scope, variables]: [string, Record]) => { Object.entries(variables).forEach( @@ -273,7 +286,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 index 2a16572..03c9b54 100644 --- a/TypeScript/src/ArcscriptVariable.ts +++ b/TypeScript/src/ArcscriptVariable.ts @@ -6,6 +6,7 @@ type ArcscriptVariableParams = { type: string; defaultValue: VarValue; value?: VarValue; + scope: string; }; export default class ArcscriptVariable { @@ -15,6 +16,7 @@ export default class ArcscriptVariable { value: VarValue; defaultValue: VarValue; changed = false; + scope: string; constructor({ id, @@ -22,11 +24,13 @@ export default class ArcscriptVariable { defaultValue, type, value, + scope, }: ArcscriptVariableParams) { this.id = id; this.name = name; this.type = type; this.defaultValue = defaultValue; + this.scope = scope; if (value !== undefined) { this.value = value; diff --git a/TypeScript/src/ArcscriptVisitor.ts b/TypeScript/src/ArcscriptVisitor.ts index 62ee614..b03b854 100644 --- a/TypeScript/src/ArcscriptVisitor.ts +++ b/TypeScript/src/ArcscriptVisitor.ts @@ -185,7 +185,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { visitStatement_assignment = (ctx: Statement_assignmentContext) => { const assignable = this.visitAssignable(ctx.assignable()); - let variableValue = this.state.getVarValue(assignable.name); + let variableValue = assignable.getValue(); let expressionValue = this.visitExpression(ctx.expression()); let result: VarValue = 0; @@ -201,7 +201,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { throw new RuntimeError('Invalid operation with string'); } - this.state.setVarValues([assignable.id], [result]); + assignable.setValue(result); return null; } if ( @@ -241,17 +241,12 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { } else if (ctx.ASSIGN()) { result = expressionValue; } - this.state.setVarValues([assignable.id], [result]); + assignable.setValue(result); return null; }; visitAssignable = (ctx: AssignableContext): ArcscriptVariable => { - const identifier = ctx.identifier().getText(); - const variableObject = this.state.getVar(identifier); - if (!variableObject) { - throw new RuntimeError(`Variable ${identifier} not found`); - } - return variableObject; + return this.visitIdentifier(ctx.identifier()); }; visitExpression(ctx: ExpressionContext): VarValue { @@ -406,8 +401,16 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { }; visitIdentifier = (ctx: IdentifierContext): ArcscriptVariable => { - const name = ctx.getText(); - const variableObject = this.state.getVar(name); + let name: string; + let scope: string; + if (ctx.IDENTIFIER_list().length === 1) { + scope = 'global'; + name = ctx.IDENTIFIER(0).getText(); + } else { + scope = ctx.IDENTIFIER(0).getText(); + name = ctx.IDENTIFIER(1).getText(); + } + const variableObject = this.state.getVar(name, scope); if (!variableObject) { throw new RuntimeError(`Variable ${name} not found`); } @@ -450,7 +453,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { ) { argument_list = argument_list.map(variable => { if (variable instanceof ArcscriptVariable) { - return variable.name; + return variable; } throw new RuntimeError('Expected a variable'); }); @@ -470,12 +473,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { visitIdentifier_list = (ctx: Identifier_listContext): ArcscriptVariable[] => { return ctx.identifier_list().map(identifier => { - const name = identifier.getText(); - const variableObject = this.state.getVar(name); - if (!variableObject) { - throw new RuntimeError(`Variable ${name} not found`); - } - return variableObject; + return this.visitIdentifier(identifier); }); }; diff --git a/TypeScript/src/Generated/ArcscriptParserBase.ts b/TypeScript/src/Generated/ArcscriptParserBase.ts index c562baa..7a1c786 100644 --- a/TypeScript/src/Generated/ArcscriptParserBase.ts +++ b/TypeScript/src/Generated/ArcscriptParserBase.ts @@ -6,7 +6,7 @@ import ArcscriptParser, { IdentifierContext, Mention_attributesContext, } from './ArcscriptParser.js'; -import { ArcscriptStateDef } from '../types.js'; +import { ArcscriptStateDef, ScopedVariableDef, VarDef } from '../types.js'; type ArcscriptParserOptions = { arcscriptVariables: ArcscriptStateDef; @@ -59,16 +59,26 @@ export default class ArcscriptParserBase extends Parser { } setOptions(options: ArcscriptParserOptions) { - this.arcscriptVariableNames = Object.entries( - options.arcscriptVariables - ).flatMap(([scope, vars]) => { - return Object.values(vars).map(variable => { - if (scope === 'global') { - return variable.name; - } - return `${scope}.${variable.name}`; - }); + const variableNames: string[] = []; + Object.entries(options.arcscriptVariables).forEach(([scope, vars]) => { + if (scope === 'global') { + const globalVarNames = Object.values( + vars as Record + ).map(variable => variable.name); + variableNames.push(...globalVarNames); + } else { + Object.entries(vars as Record).forEach( + ([parentId, scopedVars]) => { + variableNames.push( + ...Object.values(scopedVars).map( + variable => `${parentId}.${variable.name}` + ) + ); + } + ); + } }); + this.arcscriptVariableNames = variableNames; this.elementVisits = options.elementVisits; this.currentElement = options.currentElement; } diff --git a/TypeScript/src/__tests__/antlr4.interpreter.test.ts b/TypeScript/src/__tests__/antlr4.interpreter.test.ts index 85b5833..df4e5d8 100644 --- a/TypeScript/src/__tests__/antlr4.interpreter.test.ts +++ b/TypeScript/src/__tests__/antlr4.interpreter.test.ts @@ -6,11 +6,12 @@ import runtimeErrorTests from './runtimeErrors.json'; import conditionTests from './conditions.json'; import replaceVariableTests from './replaceVariables.json'; import stringTests from './stringConcat.json'; +import memberTests from './member.json'; import { ArcscriptStateDef, VarValue } from '../types.js'; type TestCase = { code: string; - changes?: Record; + changes?: Record | Record>; output?: string; events?: { name: string; args: unknown }[]; visits?: Record; @@ -26,7 +27,7 @@ describe('Interprete valid scripts', () => { 'Tests script: $code', ({ code, - changes: expectedChanges = {}, + changes: expectedChanges, output: expectedOutput = '', events = null, visits = {}, @@ -34,14 +35,16 @@ describe('Interprete valid scripts', () => { }) => { const eventHandler = vi.fn(); - const interpreter = new Interpreter( - validTests.initialVars as ArcscriptStateDef, - visits, - elementId, - eventHandler - ); + const interpreter = new Interpreter({ + state: validTests.initialVars as ArcscriptStateDef, + 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) { @@ -55,6 +58,29 @@ describe('Interprete valid scripts', () => { ); }); +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.initialVars as ArcscriptStateDef, + 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', @@ -65,13 +91,13 @@ describe('Interprete string test scripts', () => { visits, elementId = '', }) => { - const interpreter = new Interpreter( - stringTests.initialVars as ArcscriptStateDef, - visits, - elementId - ); + const interpreter = new Interpreter({ + state: stringTests.initialVars as ArcscriptStateDef, + elementVisits: visits, + currentElement: elementId, + }); const { changes, output } = interpreter.runScript(code); - expect(changes).toMatchObject(expectedChanges); + expect(changes).toEqual(expectedChanges); expect(output).toEqual(expectedOutput); } ); @@ -81,11 +107,11 @@ describe('Interprete script with parse errors', () => { test.each(parseErrorTests.cases as TestCase[])( 'Test error script: $code', ({ code, visits, elementId = '' }) => { - const interpreter = new Interpreter( - parseErrorTests.initialVars as ArcscriptStateDef, - visits, - elementId - ); + const interpreter = new Interpreter({ + state: parseErrorTests.initialVars as ArcscriptStateDef, + elementVisits: visits, + currentElement: elementId, + }); expect(() => { interpreter.parse(code); }).toThrow(ParseError); @@ -97,11 +123,11 @@ describe('Interprete script with runtime errors', () => { test.each(runtimeErrorTests.cases as TestCase[])( 'Test error script: $code', ({ code, visits, elementId = '' }) => { - const interpreter = new Interpreter( - runtimeErrorTests.initialVars as ArcscriptStateDef, - visits, - elementId - ); + const interpreter = new Interpreter({ + state: runtimeErrorTests.initialVars as ArcscriptStateDef, + elementVisits: visits, + currentElement: elementId, + }); expect(() => { interpreter.runScript(code); }).toThrow(RuntimeError); @@ -113,11 +139,11 @@ describe('Interprete condition', () => { test.each(conditionTests.cases as TestCase[])( 'Tests condition: $code', ({ code, visits, elementId = '', result: expectedResult }) => { - const interpreter = new Interpreter( - conditionTests.initialVars as ArcscriptStateDef, - visits, - elementId - ); + const interpreter = new Interpreter({ + state: conditionTests.initialVars as ArcscriptStateDef, + elementVisits: visits, + currentElement: elementId, + }); const { result } = interpreter.runScript(code); expect(result.condition).toStrictEqual(expectedResult); @@ -130,9 +156,9 @@ describe('Replace variables', () => { 'Tests replace: $code', ({ code, variableChanges = {}, result: expectedResult }) => { // Parse and check the condition - const interpreter = new Interpreter( - replaceVariableTests.initialVars as ArcscriptStateDef - ); + const interpreter = new Interpreter({ + state: replaceVariableTests.initialVars as ArcscriptStateDef, + }); const result = interpreter.replaceVariables(code, variableChanges); // The given condition should match the expected evaluation diff --git a/TypeScript/src/__tests__/member.json b/TypeScript/src/__tests__/member.json new file mode 100644 index 0000000..66b92b6 --- /dev/null +++ b/TypeScript/src/__tests__/member.json @@ -0,0 +1,135 @@ +{ + "initialVars": { + "global": { + "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" + } + }, + "components": { + "comp1": { + "var9": { + "id": "var9", + "name": "a", + "type": "integer", + "defaultValue": 12 + }, + "var10": { + "id": "var10", + "name": "b", + "type": "integer", + "defaultValue": 0 + } + }, + "comp2": { + "var11": { + "id": "var11", + "name": "a", + "type": "string", + "defaultValue": "Component Text" + } + } + } + }, + "cases": [ + { + "code": "
comp1.a = 42
", + "changes": { + "comp1": { + "var9": 42 + } + } + }, + { + "code": "
show(comp1.a)
", + "output": "

12

" + }, + { + "code": "
comp1.a = 99
reset(comp1.a, comp1.b)
", + "changes": { + "comp1": { + "var9": 12, + "var10": 0 + } + } + }, + { + "code": "
x = comp1.a
", + "changes": { + "global": { + "var1": 12 + } + } + }, + { + "code": "
comp1.a = 55
comp1.b = comp1.a
", + "changes": { + "comp1": { + "var9": 55, + "var10": 55 + } + } + }, + { + "code": "
comp1.a = 77
comp1.b = comp1.a
reset(comp1.a, comp1.b)
", + "changes": { + "comp1": { + "var9": 12, + "var10": 0 + } + } + }, + { + "code": "
comp2.a = \"Hello World\"
", + "changes": { + "comp2": { + "var11": "Hello World" + } + } + } + ] +} \ No newline at end of file diff --git a/TypeScript/src/__tests__/valid.json b/TypeScript/src/__tests__/valid.json index d2db91b..0d89689 100644 --- a/TypeScript/src/__tests__/valid.json +++ b/TypeScript/src/__tests__/valid.json @@ -400,20 +400,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)
", @@ -516,7 +512,11 @@ }, { "code": "

How are you?

x = 1

I am fine, thank you.

", - "changes": {}, + "changes": { + "global": { + "var1": 1 + } + }, "output": "

How are you?

I am fine, thank you.

" }, { @@ -646,7 +646,12 @@ }, { "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": { + "global": { + "var4": "String with \\\"quotes\\\" \\t tabs and \\n new lines" + } + } } ] } \ No newline at end of file diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index 2ac3e84..936c2c6 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -7,6 +7,13 @@ import ErrorListener from './ErrorListener.js'; import { ArcscriptStateDef, VarDef, 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 { arcscriptVariables: ArcscriptStateDef; state: ArcscriptState | null = null; @@ -15,24 +22,12 @@ export default class Interpreter { variableOffsets: { start: number; end: number }[]; emit: (event: string, data?: unknown) => void; - /** - * Interpreter constructor - * @param arcscriptVariables - * @param elementVisits The current element visits - * @param currentElement The current element ID - * @param eventHandler The event callback - */ - constructor( - arcscriptVariables: ArcscriptStateDef, - elementVisits: Record = {}, - currentElement: string = '', - eventHandler: (event: string, data?: unknown) => void = () => {} - ) { - this.arcscriptVariables = arcscriptVariables; - 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 = {}) { @@ -55,7 +50,7 @@ export default class Interpreter { varIds.push(id); varValuesList.push(value); }); - this.state.setVarValues(varIds, varValuesList); + // this.state.setVarValues(varIds, varValuesList); const result = tree.accept(visitor); diff --git a/TypeScript/src/types.ts b/TypeScript/src/types.ts index ba43e5d..36e5e5e 100644 --- a/TypeScript/src/types.ts +++ b/TypeScript/src/types.ts @@ -15,6 +15,10 @@ export type MentionResult = { export type VariableScope = 'global' | 'components' | 'elements'; +export type ScopedVariableDef = Record; + export type ArcscriptStateDef = { - [key in VariableScope]?: Record; + global?: Record; + components?: Record; + elements?: Record; }; From d4d11701619f8c635ab90b8af34bc916929df2a1 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Thu, 26 Feb 2026 10:25:29 +0200 Subject: [PATCH 09/21] fix: boards variable scope --- TypeScript/src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TypeScript/src/types.ts b/TypeScript/src/types.ts index 36e5e5e..6d5c26f 100644 --- a/TypeScript/src/types.ts +++ b/TypeScript/src/types.ts @@ -13,12 +13,12 @@ export type MentionResult = { label: string; }; -export type VariableScope = 'global' | 'components' | 'elements'; +export type VariableScope = 'global' | 'components' | 'boards'; export type ScopedVariableDef = Record; export type ArcscriptStateDef = { global?: Record; components?: Record; - elements?: Record; + boards?: Record; }; From a94f6145f537d4f759d1e71495cf8731f6bc2489 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Mon, 2 Mar 2026 10:56:20 +0200 Subject: [PATCH 10/21] fix: add value option in state def --- TypeScript/src/ArcscriptState.ts | 2 ++ .../src/__tests__/antlr4.interpreter.test.ts | 25 ++++++++++++++++--- TypeScript/src/__tests__/valid.json | 16 ++++++++++++ TypeScript/src/types.ts | 1 + 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/TypeScript/src/ArcscriptState.ts b/TypeScript/src/ArcscriptState.ts index 1758c24..403332f 100644 --- a/TypeScript/src/ArcscriptState.ts +++ b/TypeScript/src/ArcscriptState.ts @@ -61,6 +61,7 @@ export default class ArcscriptState { name: varDef.name, type: varDef.type, defaultValue: varDef.defaultValue, + value: varDef.value, scope: scopeType, }); globalScope[id] = variable; @@ -77,6 +78,7 @@ export default class ArcscriptState { name: varDef.name, type: varDef.type, defaultValue: varDef.defaultValue, + value: varDef.value, scope: scopeType, }); scopeVariables[parentId][id] = variable; diff --git a/TypeScript/src/__tests__/antlr4.interpreter.test.ts b/TypeScript/src/__tests__/antlr4.interpreter.test.ts index df4e5d8..79ef651 100644 --- a/TypeScript/src/__tests__/antlr4.interpreter.test.ts +++ b/TypeScript/src/__tests__/antlr4.interpreter.test.ts @@ -8,8 +8,10 @@ import replaceVariableTests from './replaceVariables.json'; import stringTests from './stringConcat.json'; import memberTests from './member.json'; import { ArcscriptStateDef, VarValue } from '../types.js'; +import cloneDeep from 'lodash.clonedeep'; type TestCase = { + values?: Record>; code: string; changes?: Record | Record>; output?: string; @@ -20,12 +22,18 @@ type TestCase = { variableChanges?: Record; }; +type TestSuite = { + initialVars: ArcscriptStateDef; + cases: TestCase[]; +}; + describe('Interprete valid scripts', () => { - const cases = validTests.cases as TestCase[]; + const cases: TestCase[] = (validTests as TestSuite).cases; test.each(cases)( 'Tests script: $code', ({ + values, code, changes: expectedChanges, output: expectedOutput = '', @@ -34,9 +42,20 @@ describe('Interprete valid scripts', () => { elementId = '', }) => { const eventHandler = vi.fn(); - + const initVars: ArcscriptStateDef = cloneDeep( + (validTests as TestSuite).initialVars + ); + if (values?.global && initVars.global) { + Object.entries(values.global as Record).forEach( + ([id, value]) => { + if (initVars.global![id]) { + initVars.global![id].value = value; + } + } + ); + } const interpreter = new Interpreter({ - state: validTests.initialVars as ArcscriptStateDef, + state: initVars, elementVisits: visits, currentElement: elementId, eventHandler, diff --git a/TypeScript/src/__tests__/valid.json b/TypeScript/src/__tests__/valid.json index 0d89689..44e3d64 100644 --- a/TypeScript/src/__tests__/valid.json +++ b/TypeScript/src/__tests__/valid.json @@ -652,6 +652,22 @@ "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": { + "global": { + "var1": 14, + "var2": 15 + } + } } ] } \ No newline at end of file diff --git a/TypeScript/src/types.ts b/TypeScript/src/types.ts index 6d5c26f..79e2667 100644 --- a/TypeScript/src/types.ts +++ b/TypeScript/src/types.ts @@ -6,6 +6,7 @@ export type VarDef = { name: string; type: VarType; defaultValue: VarValue; + value?: VarValue; }; export type MentionResult = { From b2470f928b45c3cc93119cf5f0afe578005b8e77 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Mon, 2 Mar 2026 11:08:47 +0200 Subject: [PATCH 11/21] feat: state def validation --- TypeScript/src/ArcscriptState.ts | 48 +++++++++++++++++++++++++++++++- TypeScript/tsconfig.json | 6 ++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/TypeScript/src/ArcscriptState.ts b/TypeScript/src/ArcscriptState.ts index 403332f..9e1436e 100644 --- a/TypeScript/src/ArcscriptState.ts +++ b/TypeScript/src/ArcscriptState.ts @@ -1,6 +1,51 @@ -import { ArcscriptStateDef, ScopedVariableDef, VarValue } from './types.js'; +import { + ArcscriptStateDef, + ScopedVariableDef, + 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(varDef: VarDef) { + if (!hasProperty(varDef, 'id')) { + throw new Error(`Variable ${varDef.name} is missing id property`); + } + 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`); + } +} + +function validateStateDef(stateDef: ArcscriptStateDef) { + if (stateDef.global) { + Object.values(stateDef.global).forEach(varDef => { + validateVarDef(varDef); + }); + } + ['components', 'boards'].forEach(scopeType => { + const key = scopeType as keyof ArcscriptStateDef; + if (stateDef[key]) { + Object.values(stateDef[key]).forEach((scopedVars: ScopedVariableDef) => { + Object.values(scopedVars).forEach(varDef => { + validateVarDef(varDef); + }); + }); + } + }); +} + type OutputObject = { output?: string; index?: number; @@ -48,6 +93,7 @@ export default class ArcscriptState { } initializeVariables(arcscriptVariables: ArcscriptStateDef) { + validateStateDef(arcscriptVariables); const globalScope: Record = {}; const scopeVariables: Record< string, 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", From 974e5cbb94afdff53cdf996383520b66816f831b Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Tue, 3 Mar 2026 11:57:45 +0200 Subject: [PATCH 12/21] feat: refactor interpreter to recieve flat variables def with scope property --- TypeScript/src/ArcscriptFunctions.ts | 2 +- TypeScript/src/ArcscriptState.ts | 149 +++----- TypeScript/src/ArcscriptVariable.ts | 6 +- TypeScript/src/ArcscriptVisitor.ts | 3 +- .../src/Generated/ArcscriptParserBase.ts | 27 +- .../src/__tests__/antlr4.interpreter.test.ts | 85 ++++- TypeScript/src/__tests__/conditions.json | 84 +++-- TypeScript/src/__tests__/member.json | 169 ++++----- TypeScript/src/__tests__/parseErrors.json | 84 +++-- .../src/__tests__/replaceVariables.json | 113 +++--- TypeScript/src/__tests__/runtimeErrors.json | 84 +++-- TypeScript/src/__tests__/stringConcat.json | 288 +++++++-------- TypeScript/src/__tests__/valid.json | 348 ++++++------------ TypeScript/src/antlr4.interpreter.ts | 48 ++- TypeScript/src/types.ts | 11 +- 15 files changed, 677 insertions(+), 824 deletions(-) diff --git a/TypeScript/src/ArcscriptFunctions.ts b/TypeScript/src/ArcscriptFunctions.ts index c95982f..cb6d1c5 100644 --- a/TypeScript/src/ArcscriptFunctions.ts +++ b/TypeScript/src/ArcscriptFunctions.ts @@ -124,7 +124,7 @@ export default class ArcscriptFunctions { } return variable.id; }); - const variablesToReset = Object.values(this.state.globalVariables).filter( + const variablesToReset = Object.values(this.state.variables).filter( v => !except.includes(v.id) ); variablesToReset.forEach(variable => variable.reset()); diff --git a/TypeScript/src/ArcscriptState.ts b/TypeScript/src/ArcscriptState.ts index 9e1436e..4422f85 100644 --- a/TypeScript/src/ArcscriptState.ts +++ b/TypeScript/src/ArcscriptState.ts @@ -1,9 +1,4 @@ -import { - ArcscriptStateDef, - ScopedVariableDef, - VarDef, - VarValue, -} from './types.js'; +import { ArcscriptStateDef, VarDef, VarValue } from './types.js'; import ArcscriptVariable from './ArcscriptVariable.js'; function hasProperty(obj: T, prop: keyof T): boolean { @@ -13,10 +8,15 @@ function hasProperty(obj: T, prop: keyof T): boolean { return Object.prototype.hasOwnProperty.call(obj, prop); } -function validateVarDef(varDef: VarDef) { +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`); } @@ -26,23 +26,19 @@ function validateVarDef(varDef: VarDef) { 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) { - if (stateDef.global) { - Object.values(stateDef.global).forEach(varDef => { - validateVarDef(varDef); - }); - } - ['components', 'boards'].forEach(scopeType => { - const key = scopeType as keyof ArcscriptStateDef; - if (stateDef[key]) { - Object.values(stateDef[key]).forEach((scopedVars: ScopedVariableDef) => { - Object.values(scopedVars).forEach(varDef => { - validateVarDef(varDef); - }); - }); - } + Object.entries(stateDef).forEach(([id, varDef]) => { + validateVarDef(id, varDef); }); } @@ -55,8 +51,7 @@ type OutputObject = { }; export default class ArcscriptState { - globalVariables: Record; - scopedVariables: Record>; + variables: Record; elementVisits: Record; currentElement: string; @@ -74,9 +69,7 @@ export default class ArcscriptState { currentElement: string, emit: (event: string, data?: unknown) => void ) { - const { global, scoped } = this.initializeVariables(arcscriptVariables); - this.globalVariables = global; - this.scopedVariables = scoped; + this.variables = this.initializeVariables(arcscriptVariables); this.elementVisits = elementVisits; this.currentElement = currentElement; @@ -94,64 +87,27 @@ export default class ArcscriptState { initializeVariables(arcscriptVariables: ArcscriptStateDef) { validateStateDef(arcscriptVariables); - const globalScope: Record = {}; - const scopeVariables: Record< - string, - Record - > = {}; - Object.entries(arcscriptVariables).forEach(([scopeType, vars]) => { - if (scopeType === 'global') { - Object.entries(vars).forEach(([id, varDef]) => { - const variable = new ArcscriptVariable({ - id, - name: varDef.name, - type: varDef.type, - defaultValue: varDef.defaultValue, - value: varDef.value, - scope: scopeType, - }); - globalScope[id] = variable; - }); - } else { - Object.entries(vars).forEach( - ([parentId, scopedVars]: [string, ScopedVariableDef]) => { - if (!scopeVariables[parentId]) { - scopeVariables[parentId] = {}; - } - Object.entries(scopedVars).forEach(([id, varDef]) => { - const variable = new ArcscriptVariable({ - id, - name: varDef.name, - type: varDef.type, - defaultValue: varDef.defaultValue, - value: varDef.value, - scope: scopeType, - }); - scopeVariables[parentId][id] = variable; - }); - } - ); - } + 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 { - global: globalScope, - scoped: scopeVariables, - }; + return variables; } - getVar(name: string, scope: string = 'global'): ArcscriptVariable { - if (scope === 'global') { - const variable = Object.values(this.globalVariables).find( - v => v.name === name - ); - if (!variable) { - throw new Error(`Variable ${name} not found`); + 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 variable; - } - const variable = Object.values(this.scopedVariables[scope]).find( - v => v.name === name - ); + return v.name === name; + }); if (!variable) { throw new Error(`Variable ${name} not found`); } @@ -160,42 +116,21 @@ export default class ArcscriptState { setVarValues(ids: string[], values: VarValue[]) { ids.forEach((id, index) => { - Object.entries(this.scopedVariables).find(([, variables]) => { - if (variables[id]) { - variables[id].setValue(values[index]); - return true; - } - return false; - }); + if (this.variables[id]) { + this.variables[id].setValue(values[index]); + } }); } getChanges() { - const changes: Record> = {}; - Object.entries(this.globalVariables).forEach( + const changes: Record = {}; + Object.entries(this.variables).forEach( ([id, variable]: [string, ArcscriptVariable]) => { if (variable.changed) { - if (!changes['global']) { - changes['global'] = {}; - } - changes['global'][id] = variable.getValue(); + changes[id] = variable.getValue(); } } ); - Object.entries(this.scopedVariables).forEach( - ([scope, variables]: [string, Record]) => { - Object.entries(variables).forEach( - ([id, variable]: [string, ArcscriptVariable]) => { - if (variable.changed) { - if (!changes[scope]) { - changes[scope] = {}; - } - changes[scope][id] = variable.getValue(); - } - } - ); - } - ); return changes; } diff --git a/TypeScript/src/ArcscriptVariable.ts b/TypeScript/src/ArcscriptVariable.ts index 03c9b54..11e2f9b 100644 --- a/TypeScript/src/ArcscriptVariable.ts +++ b/TypeScript/src/ArcscriptVariable.ts @@ -6,7 +6,7 @@ type ArcscriptVariableParams = { type: string; defaultValue: VarValue; value?: VarValue; - scope: string; + scope?: string | null; }; export default class ArcscriptVariable { @@ -16,7 +16,7 @@ export default class ArcscriptVariable { value: VarValue; defaultValue: VarValue; changed = false; - scope: string; + scope: string | null; constructor({ id, @@ -30,7 +30,7 @@ export default class ArcscriptVariable { this.name = name; this.type = type; this.defaultValue = defaultValue; - this.scope = scope; + this.scope = scope || null; if (value !== undefined) { this.value = value; diff --git a/TypeScript/src/ArcscriptVisitor.ts b/TypeScript/src/ArcscriptVisitor.ts index b03b854..cd35f57 100644 --- a/TypeScript/src/ArcscriptVisitor.ts +++ b/TypeScript/src/ArcscriptVisitor.ts @@ -402,9 +402,8 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { visitIdentifier = (ctx: IdentifierContext): ArcscriptVariable => { let name: string; - let scope: string; + let scope: string | null = null; if (ctx.IDENTIFIER_list().length === 1) { - scope = 'global'; name = ctx.IDENTIFIER(0).getText(); } else { scope = ctx.IDENTIFIER(0).getText(); diff --git a/TypeScript/src/Generated/ArcscriptParserBase.ts b/TypeScript/src/Generated/ArcscriptParserBase.ts index 7a1c786..6c916a3 100644 --- a/TypeScript/src/Generated/ArcscriptParserBase.ts +++ b/TypeScript/src/Generated/ArcscriptParserBase.ts @@ -6,7 +6,7 @@ import ArcscriptParser, { IdentifierContext, Mention_attributesContext, } from './ArcscriptParser.js'; -import { ArcscriptStateDef, ScopedVariableDef, VarDef } from '../types.js'; +import { ArcscriptStateDef } from '../types.js'; type ArcscriptParserOptions = { arcscriptVariables: ArcscriptStateDef; @@ -59,25 +59,14 @@ export default class ArcscriptParserBase extends Parser { } setOptions(options: ArcscriptParserOptions) { - const variableNames: string[] = []; - Object.entries(options.arcscriptVariables).forEach(([scope, vars]) => { - if (scope === 'global') { - const globalVarNames = Object.values( - vars as Record - ).map(variable => variable.name); - variableNames.push(...globalVarNames); - } else { - Object.entries(vars as Record).forEach( - ([parentId, scopedVars]) => { - variableNames.push( - ...Object.values(scopedVars).map( - variable => `${parentId}.${variable.name}` - ) - ); - } - ); + 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; diff --git a/TypeScript/src/__tests__/antlr4.interpreter.test.ts b/TypeScript/src/__tests__/antlr4.interpreter.test.ts index 79ef651..d6e514f 100644 --- a/TypeScript/src/__tests__/antlr4.interpreter.test.ts +++ b/TypeScript/src/__tests__/antlr4.interpreter.test.ts @@ -13,7 +13,7 @@ import cloneDeep from 'lodash.clonedeep'; type TestCase = { values?: Record>; code: string; - changes?: Record | Record>; + changes?: Record; output?: string; events?: { name: string; args: unknown }[]; visits?: Record; @@ -28,7 +28,7 @@ type TestSuite = { }; describe('Interprete valid scripts', () => { - const cases: TestCase[] = (validTests as TestSuite).cases; + const cases: TestCase[] = (validTests as unknown as TestSuite).cases; test.each(cases)( 'Tests script: $code', @@ -43,13 +43,18 @@ describe('Interprete valid scripts', () => { }) => { const eventHandler = vi.fn(); const initVars: ArcscriptStateDef = cloneDeep( - (validTests as TestSuite).initialVars + (validTests as unknown as TestSuite).initialVars ); - if (values?.global && initVars.global) { + if (values?.global) { Object.entries(values.global as Record).forEach( ([id, value]) => { - if (initVars.global![id]) { - initVars.global![id].value = value; + if ( + initVars[id] && + (initVars[id].scope === 'global' || + initVars[id].scope === undefined || + initVars[id].scope === null) + ) { + initVars[id].value = value; } } ); @@ -88,7 +93,7 @@ describe('Object members variables', () => { elementId = '', }) => { const interpreter = new Interpreter({ - state: memberTests.initialVars as ArcscriptStateDef, + state: (memberTests as unknown as TestSuite).initialVars, elementVisits: visits, currentElement: elementId, }); @@ -111,7 +116,7 @@ describe('Interprete string test scripts', () => { elementId = '', }) => { const interpreter = new Interpreter({ - state: stringTests.initialVars as ArcscriptStateDef, + state: (stringTests as unknown as TestSuite).initialVars, elementVisits: visits, currentElement: elementId, }); @@ -127,7 +132,7 @@ describe('Interprete script with parse errors', () => { 'Test error script: $code', ({ code, visits, elementId = '' }) => { const interpreter = new Interpreter({ - state: parseErrorTests.initialVars as ArcscriptStateDef, + state: (parseErrorTests as unknown as TestSuite).initialVars, elementVisits: visits, currentElement: elementId, }); @@ -143,7 +148,7 @@ describe('Interprete script with runtime errors', () => { 'Test error script: $code', ({ code, visits, elementId = '' }) => { const interpreter = new Interpreter({ - state: runtimeErrorTests.initialVars as ArcscriptStateDef, + state: (runtimeErrorTests as unknown as TestSuite).initialVars, elementVisits: visits, currentElement: elementId, }); @@ -159,7 +164,7 @@ describe('Interprete condition', () => { 'Tests condition: $code', ({ code, visits, elementId = '', result: expectedResult }) => { const interpreter = new Interpreter({ - state: conditionTests.initialVars as ArcscriptStateDef, + state: (conditionTests as unknown as TestSuite).initialVars, elementVisits: visits, currentElement: elementId, }); @@ -176,7 +181,7 @@ describe('Replace variables', () => { ({ code, variableChanges = {}, result: expectedResult }) => { // Parse and check the condition const interpreter = new Interpreter({ - state: replaceVariableTests.initialVars as ArcscriptStateDef, + state: (replaceVariableTests as unknown as TestSuite).initialVars, }); const result = interpreter.replaceVariables(code, variableChanges); @@ -185,3 +190,59 @@ describe('Replace variables', () => { } ); }); + +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
'); + }); +}); diff --git a/TypeScript/src/__tests__/conditions.json b/TypeScript/src/__tests__/conditions.json index 4c3e321..d6f0284 100644 --- a/TypeScript/src/__tests__/conditions.json +++ b/TypeScript/src/__tests__/conditions.json @@ -1,48 +1,46 @@ { "initialVars": { - "global": { - "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 - } + "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 } }, "cases": [ diff --git a/TypeScript/src/__tests__/member.json b/TypeScript/src/__tests__/member.json index 66b92b6..05c170c 100644 --- a/TypeScript/src/__tests__/member.json +++ b/TypeScript/src/__tests__/member.json @@ -1,87 +1,80 @@ { "initialVars": { - "global": { - "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" - } + "var1": { + "id": "var1", + "name": "x", + "type": "integer", + "defaultValue": 14 }, - "components": { - "comp1": { - "var9": { - "id": "var9", - "name": "a", - "type": "integer", - "defaultValue": 12 - }, - "var10": { - "id": "var10", - "name": "b", - "type": "integer", - "defaultValue": 0 - } - }, - "comp2": { - "var11": { - "id": "var11", - "name": "a", - "type": "string", - "defaultValue": "Component Text" - } - } + "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": { - "comp1": { - "var9": 42 - } + "var9": 42 } }, { @@ -91,44 +84,34 @@ { "code": "
comp1.a = 99
reset(comp1.a, comp1.b)
", "changes": { - "comp1": { - "var9": 12, - "var10": 0 - } + "var9": 12, + "var10": 0 } }, { "code": "
x = comp1.a
", "changes": { - "global": { - "var1": 12 - } + "var1": 12 } }, { "code": "
comp1.a = 55
comp1.b = comp1.a
", "changes": { - "comp1": { - "var9": 55, - "var10": 55 - } + "var9": 55, + "var10": 55 } }, { "code": "
comp1.a = 77
comp1.b = comp1.a
reset(comp1.a, comp1.b)
", "changes": { - "comp1": { - "var9": 12, - "var10": 0 - } + "var9": 12, + "var10": 0 } }, { "code": "
comp2.a = \"Hello World\"
", "changes": { - "comp2": { - "var11": "Hello World" - } + "var11": "Hello World" } } ] diff --git a/TypeScript/src/__tests__/parseErrors.json b/TypeScript/src/__tests__/parseErrors.json index 1113748..96902ac 100644 --- a/TypeScript/src/__tests__/parseErrors.json +++ b/TypeScript/src/__tests__/parseErrors.json @@ -1,48 +1,46 @@ { "initialVars": { - "global": { - "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 - } + "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 } }, "cases": [ diff --git a/TypeScript/src/__tests__/replaceVariables.json b/TypeScript/src/__tests__/replaceVariables.json index 0dd7570..542a0b9 100644 --- a/TypeScript/src/__tests__/replaceVariables.json +++ b/TypeScript/src/__tests__/replaceVariables.json @@ -1,48 +1,53 @@ { "initialVars": { - "global": { - "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 - } + "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": "x", + "type": "string", + "defaultValue": "Different Text", + "scope": "comp1" } }, "cases": [ @@ -66,6 +71,28 @@ "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": "
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 85aeb61..4f6b72e 100644 --- a/TypeScript/src/__tests__/runtimeErrors.json +++ b/TypeScript/src/__tests__/runtimeErrors.json @@ -1,48 +1,46 @@ { "initialVars": { - "global": { - "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 - } + "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 } }, "cases": [ diff --git a/TypeScript/src/__tests__/stringConcat.json b/TypeScript/src/__tests__/stringConcat.json index 69bd426..f6a8bc3 100644 --- a/TypeScript/src/__tests__/stringConcat.json +++ b/TypeScript/src/__tests__/stringConcat.json @@ -1,160 +1,132 @@ { - "initialVars": { - "global": { - "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": { - "global": { - "var4": "testing" - } - } - }, - { - "code": "
w = \"test\" + \"ing\"
", - "changes": { - "global": { - "var4": "testing" - } - } - }, - { - "code": "
w = 0.42 + \"ing\"
", - "changes": { - "global": { - "var4": "0.42ing" - } - } - }, - { - "code": "
w = 0.42 + \"\"
", - "changes": { - "global": { - "var4": "0.42" - } - } - }, - { - "code": "
w = \"test\" + 44
", - "changes": { - "global": { - "var4": "test44" - } - } - }, - { - "code": "
w = \"test\" + false
", - "changes": { - "global": { - "var4": "testfalse" - } - } - }, - { - "code": "
w = \"test\" + true + true
", - "changes": { - "global": { - "var4": "testtruetrue" - } - } - }, - { - "code": "
w = true + true + \"test\"
", - "changes": { - "global": { - "var4": "2test" - } - } - }, - { - "code": "
w += \" test\"
", - "changes": { - "global": { - "var4": "Dummy text test" - } - } - }, - { - "code": "
x += \"test\"
", - "changes": { - "global": { - "var1": "14test" - } - } - }, - { - "code": "
w += 42
", - "changes": { - "global": { - "var4": "Dummy text42" - } - } - }, - { - "code": "
w += _a
", - "changes": { - "global": { - "var4": "Dummy textfalse" - } - } - }, - { - "code": "
w += man
", - "changes": { - "global": { - "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 44e3d64..6929780 100644 --- a/TypeScript/src/__tests__/valid.json +++ b/TypeScript/src/__tests__/valid.json @@ -1,402 +1,314 @@ { "initialVars": { - "global": { - "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" - } + "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": "
x=5
", "changes": { - "global": { - "var1": 5 - } + "var1": 5 } }, { "code": "
x=-5
", "changes": { - "global": { - "var1": -5 - } + "var1": -5 } }, { "code": "
x=5.23
", "changes": { - "global": { - "var1": 5.23 - } + "var1": 5.23 } }, { "code": "
x=3
y=2
", "changes": { - "global": { - "var1": 3, - "var2": 2 - } + "var1": 3, + "var2": 2 } }, { "code": "
x=\"test\"
", "changes": { - "global": { - "var1": "test" - } + "var1": "test" } }, { "code": "
x=true
", "changes": { - "global": { - "var1": true - } + "var1": true } }, { "code": "
x = y
", "changes": { - "global": { - "var1": 15 - } + "var1": 15 } }, { "code": "
x = 5 + 3
", "changes": { - "global": { - "var1": 8 - } + "var1": 8 } }, { "code": "
x = 10 % 3
", "changes": { - "global": { - "var1": 1 - } + "var1": 1 } }, { "code": "
x = 10 % 3 * 2
", "changes": { - "global": { - "var1": 2 - } + "var1": 2 } }, { "code": "
x = 10 % (3 * 2)
", "changes": { - "global": { - "var1": 4 - } + "var1": 4 } }, { "code": "
x = 5 + 6 % 3
", "changes": { - "global": { - "var1": 5 - } + "var1": 5 } }, { "code": "
x %= 6
", "changes": { - "global": { - "var1": 2 - } + "var1": 2 } }, { "code": "
x = 10 % 7 % 2
", "changes": { - "global": { - "var1": 1 - } + "var1": 1 } }, { "code": "
x = 3 / 4 * 5
", "changes": { - "global": { - "var1": 3.75 - } + "var1": 3.75 } }, { "code": "
x = 3 + 4 * 5
", "changes": { - "global": { - "var1": 23 - } + "var1": 23 } }, { "code": "
x = 3 * 4 + 5
", "changes": { - "global": { - "var1": 17 - } + "var1": 17 } }, { "code": "
x = 3 / 4 / 5
", "changes": { - "global": { - "var1": 0.15 - } + "var1": 0.15 } }, { "code": "
x = sqr(2)
", "changes": { - "global": { - "var1": 4 - } + "var1": 4 } }, { "code": "
x = 3 > 2
", "changes": { - "global": { - "var1": true - } + "var1": true } }, { "code": "
x = 3 >= 2
", "changes": { - "global": { - "var1": true - } + "var1": true } }, { "code": "
x = 3 < 2
", "changes": { - "global": { - "var1": false - } + "var1": false } }, { "code": "
x = 3 <= 2
", "changes": { - "global": { - "var1": false - } + "var1": false } }, { "code": "
x = 3 == 2
", "changes": { - "global": { - "var1": false - } + "var1": false } }, { "code": "
x = 3 != 2
", "changes": { - "global": { - "var1": true - } + "var1": true } }, { "code": "
_a = !(3 == 2)
", "changes": { - "global": { - "var6": true - } + "var6": true } }, { "code": "
_a = not (3 == 2)
", "changes": { - "global": { - "var6": true - } + "var6": true } }, { "code": "
x = !0
", "changes": { - "global": { - "var1": true - } + "var1": true } }, { "code": "
x = true || false
", "changes": { - "global": { - "var1": true - } + "var1": true } }, { "code": "
x = true && false
", "changes": { - "global": { - "var1": false - } + "var1": false } }, { "code": "
x = -3
y = y + 3
z = sqr(x)
", "changes": { - "global": { - "var1": -3, - "var2": 18, - "var3": 9 - } + "var1": -3, + "var2": 18, + "var3": 9 } }, { "code": "
x = sqrt(y + 2)
", "changes": { - "global": { - "var1": 4.123105625617661 - } + "var1": 4.123105625617661 } }, { "code": "
x = z + 12 == 4 * 3
", "changes": { - "global": { - "var1": true - } + "var1": true } }, { "code": "
x = y == 15 && !(z == 1)
", "changes": { - "global": { - "var1": true - } + "var1": true } }, { "code": "
x = (x*x + 3)/y
", "changes": { - "global": { - "var1": 13.266666666666667 - } + "var1": 13.266666666666667 } }, { "code": "
x = x*(x + 1)/y
", "changes": { - "global": { - "var1": 14 - } + "var1": 14 } }, { "code": "
x += 2
", "changes": { - "global": { - "var1": 16 - } + "var1": 16 } }, { "code": "
x -= 2
", "changes": { - "global": { - "var1": 12 - } + "var1": 12 } }, { "code": "
x *= 2
", "changes": { - "global": { - "var1": 28 - } + "var1": 28 } }, { "code": "
x /= 2
", "changes": { - "global": { - "var1": 7 - } + "var1": 7 } }, { "code": "
x = abs(-6)
", "changes": { - "global": { - "var1": 6 - } + "var1": 6 } }, { "code": "
x = min(5, -y, 2.3)
", "changes": { - "global": { - "var1": -15 - } + "var1": -15 } }, { "code": "
x = max(5, -y, 2.3)
", "changes": { - "global": { - "var1": 5 - } + "var1": 5 } }, { @@ -414,9 +326,7 @@ { "code": "
x = round(2.65)
", "changes": { - "global": { - "var1": 3 - } + "var1": 3 } }, { @@ -427,32 +337,26 @@ { "code": "
y = 33
reset(y)
", "changes": { - "global": { - "var2": 15 - } + "var2": 15 } }, { "code": "
x = 16
y = 33
resetAll(y)
", "changes": { - "global": { - "var1": 14, - "var2": 33, - "var3": 0, - "var4": "Dummy text", - "var5": 0, - "var6": false, - "var7": -1, - "var8": "Different Text" - } + "var1": 14, + "var2": 33, + "var3": 0, + "var4": "Dummy text", + "var5": 0, + "var6": false, + "var7": -1, + "var8": "Different Text" } }, { "code": "
$c5 = x
", "changes": { - "global": { - "var5": 14 - } + "var5": 14 } }, { @@ -470,33 +374,25 @@ { "code": "
if (x==14) 
x=5
endif
", "changes": { - "global": { - "var1": 5 - } + "var1": 5 } }, { "code": "
if (x==11)
x=5
else
x=7
endif
", "changes": { - "global": { - "var1": 7 - } + "var1": 7 } }, { "code": "
if x==3
x=5
elseif x==14
x=2
else
x = 0
endif
", "changes": { - "global": { - "var1": 2 - } + "var1": 2 } }, { "code": "

Mein gott!

x = 1

What is that?

", "changes": { - "global": { - "var1": 1 - } + "var1": 1 }, "output": "

Mein gott! What is that?

" }, @@ -513,9 +409,7 @@ { "code": "

How are you?

x = 1

I am fine, thank you.

", "changes": { - "global": { - "var1": 1 - } + "var1": 1 }, "output": "

How are you?

I am fine, thank you.

" }, @@ -527,10 +421,8 @@ { "code": "
_a = true
if (((_a)))
x = 1
endif
", "changes": { - "global": { - "var1": 1, - "var6": true - } + "var1": 1, + "var6": true } }, { @@ -560,9 +452,7 @@ }, "elementId": "a", "changes": { - "global": { - "var1": 0 - } + "var1": 0 }, "events": [ { @@ -627,9 +517,7 @@ "code": "

Test

_a = true

this

", "output": "

Test this

", "changes": { - "global": { - "var6": true - } + "var6": true } }, { @@ -648,9 +536,7 @@ "code": "
w = \"String with \\\"quotes\\\" \\t tabs and \\n new lines\"
show(w)
", "output": "

String with \"quotes\" \t tabs and \n new lines

", "changes": { - "global": { - "var4": "String with \\\"quotes\\\" \\t tabs and \\n new lines" - } + "var4": "String with \\\"quotes\\\" \\t tabs and \\n new lines" } }, { @@ -663,10 +549,8 @@ "code": "
show(x,\" \", y)
reset(x, y)
", "output": "

12 10

", "changes": { - "global": { - "var1": 14, - "var2": 15 - } + "var1": 14, + "var2": 15 } } ] diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index 936c2c6..86cf798 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -4,7 +4,7 @@ import ArcscriptLexer from './Generated/ArcscriptLexer.js'; import ArcscriptParser from './Generated/ArcscriptParser.js'; import ArcscriptVisitor from './ArcscriptVisitor.js'; import ErrorListener from './ErrorListener.js'; -import { ArcscriptStateDef, VarDef, VarValue } from './types.js'; +import { ArcscriptStateDef, VarValue } from './types.js'; import ArcscriptState from './ArcscriptState.js'; type ArcscriptInterpreterOptions = { @@ -101,27 +101,43 @@ export default class Interpreter { const tokenTypeNames = lexer.getSymbolicNames(); const allTokens = lexer.getAllTokens(); - const variableTokens = allTokens.filter( - token => tokenTypeNames[token.type] === 'IDENTIFIER' - ); - const tokenIdMap = new Map(); - variableTokens.forEach(varToken => { - let targetVar: VarDef | null = null; - for (const scopeVars of Object.values(this.arcscriptVariables)) { - const v = Object.values(scopeVars).find( - variable => variable.name === varToken.text - ); - if (v) { - targetVar = v; - break; + const stateVars = Object.values(this.arcscriptVariables); + + 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) ?? null; } - if (targetVar !== null) { - tokenIdMap.set(varToken, targetVar.id); + + if (targetVar) { + tokenIdMap.set(token, targetVar.id); } }); + const variableTokens = allTokens.filter(token => tokenIdMap.has(token)); const f = variableTokens .filter(varToken => tokenIdMap.has(varToken) diff --git a/TypeScript/src/types.ts b/TypeScript/src/types.ts index 79e2667..f559a3d 100644 --- a/TypeScript/src/types.ts +++ b/TypeScript/src/types.ts @@ -7,6 +7,7 @@ export type VarDef = { type: VarType; defaultValue: VarValue; value?: VarValue; + scope?: string | null; }; export type MentionResult = { @@ -14,12 +15,4 @@ export type MentionResult = { label: string; }; -export type VariableScope = 'global' | 'components' | 'boards'; - -export type ScopedVariableDef = Record; - -export type ArcscriptStateDef = { - global?: Record; - components?: Record; - boards?: Record; -}; +export type ArcscriptStateDef = Record; From aa0bf356e518da731decaa1de44eed812d441dbc Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Tue, 3 Mar 2026 16:56:39 +0200 Subject: [PATCH 13/21] fix: add variable overrides on runScript --- TypeScript/src/ArcscriptState.ts | 6 ++-- .../src/__tests__/antlr4.interpreter.test.ts | 35 +++++++++++++++++++ TypeScript/src/antlr4.interpreter.ts | 8 +---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/TypeScript/src/ArcscriptState.ts b/TypeScript/src/ArcscriptState.ts index 4422f85..0169f43 100644 --- a/TypeScript/src/ArcscriptState.ts +++ b/TypeScript/src/ArcscriptState.ts @@ -114,10 +114,10 @@ export default class ArcscriptState { return variable; } - setVarValues(ids: string[], values: VarValue[]) { - ids.forEach((id, index) => { + setVarValues(values: Record) { + Object.entries(values).forEach(([id, value]) => { if (this.variables[id]) { - this.variables[id].setValue(values[index]); + this.variables[id].setValue(value); } }); } diff --git a/TypeScript/src/__tests__/antlr4.interpreter.test.ts b/TypeScript/src/__tests__/antlr4.interpreter.test.ts index d6e514f..af9b383 100644 --- a/TypeScript/src/__tests__/antlr4.interpreter.test.ts +++ b/TypeScript/src/__tests__/antlr4.interpreter.test.ts @@ -246,3 +246,38 @@ describe('Scope inference', () => { 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/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index 86cf798..2a8c8d0 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -44,13 +44,7 @@ export default class Interpreter { const visitor = new ArcscriptVisitor(this.state); - const varIds: string[] = []; - const varValuesList: VarValue[] = []; - Object.entries(varValues).forEach(([id, value]) => { - varIds.push(id); - varValuesList.push(value); - }); - // this.state.setVarValues(varIds, varValuesList); + this.state.setVarValues(varValues); const result = tree.accept(visitor); From 734346e650b1734369614d8c36fc2ac9bb984304 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Thu, 5 Mar 2026 12:04:18 +0200 Subject: [PATCH 14/21] fix: condition function calls --- TypeScript/src/ArcscriptVisitor.ts | 15 +++++++---- .../src/Generated/ArcscriptParserBase.ts | 26 ------------------- TypeScript/src/__tests__/conditions.json | 7 +++++ grammar/ArcscriptParser.g4 | 12 ++++----- 4 files changed, 22 insertions(+), 38 deletions(-) diff --git a/TypeScript/src/ArcscriptVisitor.ts b/TypeScript/src/ArcscriptVisitor.ts index cd35f57..3372eb6 100644 --- a/TypeScript/src/ArcscriptVisitor.ts +++ b/TypeScript/src/ArcscriptVisitor.ts @@ -15,6 +15,7 @@ import { BlockquoteContext, ComparisonExpressionContext, Conditional_sectionContext, + ConditionContext, Else_if_clauseContext, Else_if_sectionContext, Else_sectionContext, @@ -61,10 +62,12 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { return { script: this.visitChildren(ctx.script()) }; } + return this.visitCondition(ctx.condition()); + }; + + visitCondition = (ctx: ConditionContext) => { return { - condition: this.visitComparisonExpression( - ctx.expression() as ComparisonExpressionContext - ), + condition: !!this.visitExpression(ctx.expression()), }; }; @@ -116,7 +119,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) => { @@ -187,7 +190,9 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { let variableValue = assignable.getValue(); let expressionValue = this.visitExpression(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' || diff --git a/TypeScript/src/Generated/ArcscriptParserBase.ts b/TypeScript/src/Generated/ArcscriptParserBase.ts index 6c916a3..df5e327 100644 --- a/TypeScript/src/Generated/ArcscriptParserBase.ts +++ b/TypeScript/src/Generated/ArcscriptParserBase.ts @@ -1,7 +1,6 @@ import { Parser, TerminalNode, Token, TokenStream } from 'antlr4'; import ArcscriptParser, { Argument_listContext, - Function_callContext, Identifier_listContext, IdentifierContext, Mention_attributesContext, @@ -117,31 +116,6 @@ export default class ArcscriptParserBase extends Parser { } } - assertFunctionReturnValue( - fncCtx: Function_callContext, - hasReturnValue = true - ) { - const functionName = fncCtx.FNAME().getText(); - const token = fncCtx.FNAME().symbol; - const { returnType } = - ArcscriptParserBase.arcscriptFunctionsInfo[functionName]; - if (hasReturnValue) { - if (returnType === 'void') { - this.notifyErrorListeners( - `The function ${functionName} does not return a value`, - token, - undefined - ); - } - } else if (returnType !== 'void') { - this.notifyErrorListeners( - `The function ${functionName} returns a value that is not being used`, - token, - undefined - ); - } - } - assertVariable(identifierCtx: IdentifierContext) { const variableName = identifierCtx.getText(); if (!this.arcscriptVariableNames.includes(variableName)) { diff --git a/TypeScript/src/__tests__/conditions.json b/TypeScript/src/__tests__/conditions.json index d6f0284..2720737 100644 --- a/TypeScript/src/__tests__/conditions.json +++ b/TypeScript/src/__tests__/conditions.json @@ -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/grammar/ArcscriptParser.g4 b/grammar/ArcscriptParser.g4 index 745ee3d..b7559f2 100644 --- a/grammar/ArcscriptParser.g4 +++ b/grammar/ArcscriptParser.g4 @@ -7,7 +7,9 @@ options { superClass = ArcscriptParserBase; } -input: script EOF | codestart expression 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; @@ -69,9 +70,6 @@ statement_assignment: assignable: identifier {this.assertVariable($identifier.ctx);}; -statement_function_call: - function_call {this.assertFunctionReturnValue($function_call.ctx, false);}; - identifier_list: identifier {this.assertVariable($identifier.ctx);} ( ',' identifier {this.assertVariable($identifier.ctx);} @@ -99,7 +97,7 @@ expression: | expression (OR | ORKEYWORD) expression # ComparisonExpression | identifier {this.assertVariable($identifier.ctx);} # IdentifierExpression | '(' expression ')' # ParenthesizedExpression - | function_call {this.assertFunctionReturnValue($function_call.ctx);} # FunctionCallExpression + | function_call # FunctionCallExpression | literal # LiteralExpression; literal: BOOLEAN | STRING | numeric_literal; From f2636466dee34a30146f38d46af0f68c0aaa6f48 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Fri, 6 Mar 2026 19:29:39 +0200 Subject: [PATCH 15/21] fix: replace scoped variables --- .../src/__tests__/antlr4.interpreter.test.ts | 2 +- .../src/__tests__/replaceVariables.json | 34 +++++++++++++++++++ TypeScript/src/antlr4.interpreter.ts | 8 ++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/TypeScript/src/__tests__/antlr4.interpreter.test.ts b/TypeScript/src/__tests__/antlr4.interpreter.test.ts index af9b383..1c4d322 100644 --- a/TypeScript/src/__tests__/antlr4.interpreter.test.ts +++ b/TypeScript/src/__tests__/antlr4.interpreter.test.ts @@ -176,7 +176,7 @@ describe('Interprete condition', () => { }); describe('Replace variables', () => { - 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 diff --git a/TypeScript/src/__tests__/replaceVariables.json b/TypeScript/src/__tests__/replaceVariables.json index 542a0b9..de4cda6 100644 --- a/TypeScript/src/__tests__/replaceVariables.json +++ b/TypeScript/src/__tests__/replaceVariables.json @@ -48,6 +48,19 @@ "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": [ @@ -86,6 +99,27 @@ }, "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": { diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index 2a8c8d0..82f8ff6 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -97,6 +97,9 @@ export default class Interpreter { const allTokens = lexer.getAllTokens(); const tokenIdMap = new Map(); 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') { @@ -123,7 +126,10 @@ export default class Interpreter { } } else { targetVar = - stateVars.find(variable => variable.name === token.text) ?? null; + stateVars.find( + variable => + variable.name === token.text && isGlobalScope(variable.scope) + ) ?? null; } if (targetVar) { From 5dbeff2eedb32c7d2f38701621e65ab7f586b8b1 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Fri, 6 Mar 2026 19:38:14 +0200 Subject: [PATCH 16/21] feat: replace scope method --- .../src/__tests__/antlr4.interpreter.test.ts | 34 +++++++++++ TypeScript/src/antlr4.interpreter.ts | 60 ++++++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/TypeScript/src/__tests__/antlr4.interpreter.test.ts b/TypeScript/src/__tests__/antlr4.interpreter.test.ts index 1c4d322..fa27e82 100644 --- a/TypeScript/src/__tests__/antlr4.interpreter.test.ts +++ b/TypeScript/src/__tests__/antlr4.interpreter.test.ts @@ -191,6 +191,40 @@ 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: { diff --git a/TypeScript/src/antlr4.interpreter.ts b/TypeScript/src/antlr4.interpreter.ts index 82f8ff6..2aaf52e 100644 --- a/TypeScript/src/antlr4.interpreter.ts +++ b/TypeScript/src/antlr4.interpreter.ts @@ -86,15 +86,21 @@ 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(); + return { + tokenTypeNames: lexer.getSymbolicNames(), + allTokens: lexer.getAllTokens(), + }; + } + + replaceVariables(code: string, variables: Record) { + const { tokenTypeNames, allTokens } = this.parseTokens(code); const tokenIdMap = new Map(); const stateVars = Object.values(this.arcscriptVariables); const isGlobalScope = (scope: string | null | undefined) => { @@ -154,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, + }); + } } From 198723bebd55ba651828d685cb92ddab0d2a7b8c Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Thu, 19 Mar 2026 09:54:35 +0200 Subject: [PATCH 17/21] feat: scoped variables in C# --- CSharp/Interpreter/ArcscriptExpression.cs | 8 +- CSharp/Interpreter/ArcscriptFunctions.cs | 46 ++- CSharp/Interpreter/ArcscriptParserBase.cs | 106 +++-- CSharp/Interpreter/ArcscriptState.cs | 51 ++- CSharp/Interpreter/ArcscriptVisitor.cs | 435 ++++++++++++--------- CSharp/Interpreter/INodes/IBoard.cs | 2 +- CSharp/Interpreter/INodes/IHasVariables.cs | 20 + CSharp/Interpreter/INodes/IProject.cs | 2 +- CSharp/Interpreter/INodes/IVariable.cs | 5 +- CSharp/Project/Board.cs | 26 +- CSharp/Project/Project.cs | 13 +- CSharp/Project/Variable.cs | 46 ++- CSharp/TestObjects.cs | 5 +- CSharp/UnitTests.cs | 254 ++++++------ CSharp/__tests__/conditions.json | 21 +- CSharp/__tests__/member.json | 121 ++++++ CSharp/__tests__/parseErrors.json | 14 +- CSharp/__tests__/replaceVariables.json | 78 +++- CSharp/__tests__/runtimeErrors.json | 14 +- CSharp/__tests__/stringConcat.json | 16 +- CSharp/__tests__/valid.json | 51 ++- 21 files changed, 868 insertions(+), 466 deletions(-) create mode 100644 CSharp/Interpreter/INodes/IHasVariables.cs create mode 100644 CSharp/__tests__/member.json 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 args) { public object Reset(IList args) { foreach (IVariable argument in args ) { - state.SetVarValue(argument.Name, argument.DefaultValue); + state.SetVarValue(argument, argument.DefaultValue); } return null; } @@ -164,7 +173,7 @@ public object ResetAll(IList args) { } foreach ( IVariable variable in this._project.Variables ) { if ( !variableNames.Contains(variable.Name) ) { - state.SetVarValue(variable.Name, variable.DefaultValue); + state.SetVarValue(variable, variable.DefaultValue); } } return null; @@ -179,12 +188,13 @@ public object Round(IList args) { public object Min(IList args) { IList arguments = args.Cast().ToList(); - return arguments.Min(); + + return arguments.Min().Value; } public object Max(IList args) { IList arguments = args.Cast().ToList(); - return arguments.Max(); + return arguments.Max().Value; } public object Visits(IList args) { diff --git a/CSharp/Interpreter/ArcscriptParserBase.cs b/CSharp/Interpreter/ArcscriptParserBase.cs index 6c33b8a..5f06ba1 100644 --- a/CSharp/Interpreter/ArcscriptParserBase.cs +++ b/CSharp/Interpreter/ArcscriptParserBase.cs @@ -3,66 +3,41 @@ using System.IO; using System.Linq; using Arcweave.Interpreter.INodes; +using Arcweave.Project; namespace Arcweave.Interpreter { public class ArcscriptParserBase : Parser { - private class FunctionArgs - { - public int? MinArgs { get; set; } - public int? MaxArgs { get; set; } - } - - private Dictionary ArcscriptFunctions; private protected IProject Project { get; private set; } - + private protected List VariableNames = new List(); internal int currentLine; internal int openTagEndPos; - public ArcscriptParserBase(ITokenStream input) : base(input) { - var functions = new Dictionary() - { - { "abs", new FunctionArgs { MinArgs=1, MaxArgs=1 } }, - { "max", new FunctionArgs { MinArgs=2 } }, - { "min", new FunctionArgs { MinArgs=2 } }, - { "random", new FunctionArgs { MinArgs=0, MaxArgs=0 } }, - { "roll", new FunctionArgs { MinArgs=1, MaxArgs=2 } }, - { "round", new FunctionArgs { MinArgs=1, MaxArgs=1 } }, - { "sqr", new FunctionArgs { MinArgs=1, MaxArgs=1 } }, - { "sqrt", new FunctionArgs { MinArgs=1, MaxArgs=1 } }, - { "visits", new FunctionArgs { MinArgs=0, MaxArgs=1 } }, - { "show", new FunctionArgs { MinArgs=1 } }, - { "reset", new FunctionArgs { MinArgs=1 } }, - { "resetAll", new FunctionArgs { MinArgs=0 } }, - { "resetVisits", new FunctionArgs { MinArgs=0, MaxArgs = 0} }, - }; - this.ArcscriptFunctions = functions; + + + public ArcscriptParserBase(ITokenStream input) : base(input) + { } - public ArcscriptParserBase(ITokenStream input, TextWriter output, TextWriter errorOutput) : base(input, output, errorOutput) { - var functions = new Dictionary() + public ArcscriptParserBase(ITokenStream input, TextWriter output, TextWriter errorOutput) : base(input, output, errorOutput) { - { "abs", new FunctionArgs { MinArgs=1, MaxArgs=1 } }, - { "max", new FunctionArgs { MinArgs=2 } }, - { "min", new FunctionArgs { MinArgs=2 } }, - { "random", new FunctionArgs { MinArgs=0, MaxArgs=0 } }, - { "roll", new FunctionArgs { MinArgs=1, MaxArgs=2 } }, - { "round", new FunctionArgs { MinArgs=1, MaxArgs=1 } }, - { "sqr", new FunctionArgs { MinArgs=1, MaxArgs=1 } }, - { "sqrt", new FunctionArgs { MinArgs=1, MaxArgs=1 } }, - { "visits", new FunctionArgs { MinArgs=0, MaxArgs=1 } }, - { "show", new FunctionArgs { MinArgs=1 } }, - { "reset", new FunctionArgs { MinArgs=1 } }, - { "resetAll", new FunctionArgs { MinArgs=0 } }, - { "resetVisits", new FunctionArgs { MinArgs=0, MaxArgs = 0} }, - }; - - this.ArcscriptFunctions = functions; } public void SetProject(IProject project) { this.Project = project; + foreach (var projectVariable in project.Variables) + { + VariableNames.Add(projectVariable.Name); + } + foreach (var projectBoard in project.Boards) + { + if (projectBoard.Variables == null) continue; + foreach (var projectBoardVariable in projectBoard.Variables) + { + VariableNames.Add(projectBoard.CustomId + "." + projectBoardVariable.Name); + } + } } public override string[] RuleNames => throw new System.NotImplementedException(); @@ -71,9 +46,10 @@ public void SetProject(IProject project) { public override string GrammarFileName => throw new System.NotImplementedException(); - public bool assertVariable(IToken variable) { - var variableName = variable.Text; - var found = this.Project.Variables.First(x => x.Name == variableName); + public bool assertVariable(ArcscriptParser.IdentifierContext identifierContext) { + var variableName = identifierContext.GetText(); + + var found = VariableNames.First(name => name == variableName); if ( found != null ) { return false; } @@ -105,26 +81,42 @@ public bool assertMention(IList attrC return true; } - public bool assertFunctionArguments(IToken fname, ArcscriptParser.Argument_listContext argumentList) { + public bool assertFunctionArguments(IToken fname, ArcscriptParser.Argument_listContext argumentListContext) { int argListLength = 0; - if ( argumentList != null && argumentList.argument() != null ) { - argListLength = argumentList.argument().Length; + if ( argumentListContext != null && argumentListContext.argument() != null ) { + argListLength = argumentListContext.argument().Length; } - var min = this.ArcscriptFunctions[fname.Text].MinArgs; - var max = this.ArcscriptFunctions[fname.Text].MaxArgs; + var min = Functions.FunctionDefinitions[fname.Text].MinArgs; + var max = Functions.FunctionDefinitions[fname.Text].MaxArgs; + var argType = Functions.FunctionDefinitions[fname.Text].ArgumentsType; if ( ( min != null && argListLength < min ) || ( max != null && argListLength > max ) ) { throw new RecognitionException("Incorrect number of arguments for function " + fname.Text, this, this.InputStream, this.Context); } + + if (argType != null && argumentListContext != null && argumentListContext.argument() != null) + { + if (argType == typeof(Variable)) + { + throw new RecognitionException("Function " + fname.Text + " accepts variables as arguments", this, this.InputStream, this.Context); + } + } + return true; } - public bool assertFunctionArguments(IToken fname, ArcscriptParser.Variable_listContext variable_List) { - int varListLength = variable_List.VARIABLE().Length; - var min = this.ArcscriptFunctions[fname.Text].MinArgs; - var max = this.ArcscriptFunctions[fname.Text].MaxArgs; - if ( ( min != null && varListLength < min ) || ( max != null && varListLength > max ) ) { + public bool assertFunctionArguments(IToken fname, ArcscriptParser.Identifier_listContext identifierListContext) { + int identifierListLength = 0; + if (identifierListContext != null && identifierListContext.identifier() != null) + { + identifierListLength = identifierListContext.identifier().Length; + } + + var min = Functions.FunctionDefinitions[fname.Text].MinArgs; + var max = Functions.FunctionDefinitions[fname.Text].MaxArgs; + if ( ( min != null && identifierListLength < min ) || ( max != null && identifierListLength > max ) ) { throw new RecognitionException("Incorrect number of arguments for function " + fname.Text, this, this.InputStream, this.Context); } + return true; } diff --git a/CSharp/Interpreter/ArcscriptState.cs b/CSharp/Interpreter/ArcscriptState.cs index 5b321e8..edba11c 100644 --- a/CSharp/Interpreter/ArcscriptState.cs +++ b/CSharp/Interpreter/ArcscriptState.cs @@ -13,6 +13,8 @@ public class ArcscriptState public ArcscriptOutputs Outputs; public string currentElement { get; set; } public IProject project { get; set; } + + public Dictionary Variables { get; } = new Dictionary(); private System.Action _emit; public ArcscriptState(string elementId, IProject project, System.Action? emit = null) @@ -20,6 +22,17 @@ public ArcscriptState(string elementId, IProject project, System.Action? Outputs = new ArcscriptOutputs(); this.currentElement = elementId; this.project = project; + + this.Variables = project.Variables.ToDictionary(variable => variable.Id, variable => variable); + foreach (var projectBoard in project.Boards) + { + if (projectBoard.Variables == null) continue; + foreach (var projectBoardVariable in projectBoard.Variables) + { + Variables.Add(projectBoardVariable.Id, projectBoardVariable); + } + } + if (emit != null) { _emit = emit; @@ -30,10 +43,18 @@ public ArcscriptState(string elementId, IProject project, System.Action? } } - public IVariable? GetVariable(string name) { + public IVariable? GetVariable(string name, string? scope = null) { try { - return this.project.Variables.First(variable => variable.Name == name); + return Variables.Values.FirstOrDefault(variable => + { + if (scope != null) + { + return variable.Name == name && scope == variable.Parent.CustomId; + } + + return variable.Name == name && variable.Parent == null; + }); } catch (System.InvalidOperationException) { @@ -41,14 +62,30 @@ public ArcscriptState(string elementId, IProject project, System.Action? } } - public object GetVarValue(string name) { - if ( this.VariableChanges.ContainsKey(name) ) { - return VariableChanges[name]; + public object GetVarValue(string name, string? scope = null) + { + var v = GetVariable(name, scope); + if (v == null) + { + throw new System.InvalidOperationException($"Variable {name} not found"); } - return this.project.GetVariable(name).ObjectValue; + + return this.VariableChanges.ContainsKey(v.Id) ? VariableChanges[v.Id] : v.ObjectValue; } - public void SetVarValue(string name, object value) { VariableChanges[name] = value; } + public void SetVarValue(IVariable v, object value) + { + VariableChanges[v.Id] = value; + } + + public void SetVarValue(ArcscriptVisitor.IdentifierDef identifierDef, object value) { + var v = GetVariable(identifierDef.Name, identifierDef.Scope); + if (v == null) + { + throw new System.InvalidOperationException($"Variable {identifierDef.Name} not found"); + } + VariableChanges[v.Id] = value; + } public void SetVarValues(string[] names, string[] values) { for ( int i = 0; i < names.Length; i++ ) { diff --git a/CSharp/Interpreter/ArcscriptVisitor.cs b/CSharp/Interpreter/ArcscriptVisitor.cs index 6e5d06d..0e56cc4 100644 --- a/CSharp/Interpreter/ArcscriptVisitor.cs +++ b/CSharp/Interpreter/ArcscriptVisitor.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using Antlr4.Runtime.Misc; using System.Globalization; +using System.Linq; using Antlr4.Runtime.Tree; using Arcweave.Interpreter.INodes; +using Arcweave.Project; namespace Arcweave.Interpreter { - public class ArcscriptVisitor : ArcscriptParserBaseVisitor + public class ArcscriptVisitor : ArcscriptParserBaseVisitor { public IProject project; public readonly ArcscriptState state; @@ -35,8 +37,13 @@ public override object VisitInput([NotNull] ArcscriptParser.InputContext context return this.VisitScript(context.script()); } - Expression comp_cond = (Expression)this.VisitCompound_condition_or(context.compound_condition_or()); - return Expression.GetBoolValue(comp_cond.Value); + Expression condition = (Expression)this.VisitCondition(context.condition()); + return Expression.GetBoolValue(condition.Value); + } + + public override object VisitCondition(ArcscriptParser.ConditionContext context) + { + return this.VisitExpression(context.expression()); } public override object VisitScript_section([NotNull] ArcscriptParser.Script_sectionContext context) { @@ -67,17 +74,12 @@ public override object VisitScript_section([NotNull] ArcscriptParser.Script_sect } return result; } - // if ( context.normal_text() != null && context.normal_text().Length > 0 ) { - // this.state.outputs.Add(context.GetText()); - // return context.GetText(); - // } return this.VisitChildren(context); } public override object VisitParagraph(ArcscriptParser.ParagraphContext context) { - // this.state.outputs.Add(context.GetText()); var paragraphEnd = context.PARAGRAPHEND().GetText(); var paragraphContent = paragraphEnd.Substring(0, paragraphEnd.Length - "

".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,282 +154,330 @@ 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 this.VisitExpression(context.expression()); } public override object VisitElse_if_clause([NotNull] ArcscriptParser.Else_if_clauseContext context) { - return this.VisitCompound_condition_or(context.compound_condition_or()); + return this.VisitExpression(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)this.VisitExpression(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 VisitIdentifier(ArcscriptParser.IdentifierContext context) + { + string name = ""; + string? scope = null; + if (context.IDENTIFIER().Length == 1) + { + name = context.IDENTIFIER(0).GetText(); } - - 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; + else + { + scope = context.IDENTIFIER(0).GetText(); + name = context.IDENTIFIER(1).GetText(); } - - return unary_condition; - } - - public override object VisitUnary_condition([NotNull] ArcscriptParser.Unary_conditionContext context) { - return this.VisitCondition(context.condition()); + return new IdentifierDef(name, scope); } - public override object VisitCondition([NotNull] ArcscriptParser.ConditionContext context) { - if ( context.expression().Length == 1 ) { - return this.VisitExpression(context.expression()[0]); + public object VisitExpression([NotNull] ArcscriptParser.ExpressionContext context) + { + var contextType = context.GetType(); + if (contextType == typeof(ArcscriptParser.ComparisonExpressionContext)) + { + return (Expression)VisitComparisonExpression((ArcscriptParser.ComparisonExpressionContext)context); } - 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 (contextType == typeof(ArcscriptParser.UnaryExpressionContext)) + { + return (Expression)VisitUnaryExpression((ArcscriptParser.UnaryExpressionContext)context); + } + if (contextType == typeof(ArcscriptParser.MultiplicativeExpressionContext)) + { + return (Expression)VisitMultiplicativeExpression((ArcscriptParser.MultiplicativeExpressionContext)context); } - if ( conditional_operator_context.GE() != null ) { - result = exp0 >= exp1; + + if (contextType == typeof(ArcscriptParser.AdditiveExpressionContext)) + { + return (Expression)VisitAdditiveExpression((ArcscriptParser.AdditiveExpressionContext)context); } - if ( conditional_operator_context.LT() != null ) { - result = exp0 < exp1; + + if (contextType == typeof(ArcscriptParser.ParenthesizedExpressionContext)) + { + return (Expression)VisitParenthesizedExpression((ArcscriptParser.ParenthesizedExpressionContext)context); } - if ( conditional_operator_context.LE() != null ) { - result = exp0 <= exp1; + + if (contextType == typeof(ArcscriptParser.FunctionCallExpressionContext)) + { + return (Expression)VisitFunctionCallExpression((ArcscriptParser.FunctionCallExpressionContext)context); } - if ( conditional_operator_context.EQ() != null ) { - result = exp0 == exp1; + + if (contextType == typeof(ArcscriptParser.LiteralExpressionContext)) + { + return (Expression)VisitLiteralExpression((ArcscriptParser.LiteralExpressionContext)context); } - if ( conditional_operator_context.NE() != null ) { - result = exp0 != exp1; + + if (contextType == typeof(ArcscriptParser.IdentifierExpressionContext)) + { + return (Expression)VisitIdentifierExpression((ArcscriptParser.IdentifierExpressionContext)context); } - if ( conditional_operator_context.ISKEYWORD() != null ) { - if ( conditional_operator_context.NOTKEYWORD() != null ) { - result = exp0 != exp1; - } else + + throw new Exception("Unknown expression type: " + contextType.Name); + } + + public override object VisitComparisonExpression(ArcscriptParser.ComparisonExpressionContext context) + { + var left =(Expression)VisitExpression(context.expression(0)); + if (context.AND() != null || context.ANDKEYWORD() != null) + { + if (!Expression.GetBoolValue(left.Value)) { - result = exp0 == exp1; + return new Expression(false); } + var rightComp = (Expression)VisitExpression(context.expression(1)); + return new Expression(Expression.GetBoolValue(rightComp.Value)); } - return new Expression(result); - } - - 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.OR() != null || context.ORKEYWORD() != null) + { + if (Expression.GetBoolValue(left.Value)) + { + return left; + } + var rightComp = (Expression)VisitExpression(context.expression(1)); + return rightComp; } - if ( context.BOOLEAN() != null ) { - return new Expression(context.BOOLEAN().GetText() == "true"); + + var right = (Expression)this.VisitExpression(context.expression(1)); + + if (context.EQ() != null || (context.ISKEYWORD() != null && context.NOTKEYWORD() == null)) + { + return new Expression(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.NE() != null || (context.ISKEYWORD() != null && context.NOTKEYWORD() != null)) + { + return new Expression(left != right); } - return (Expression)this.VisitMultiplicative_numeric_expression(context.multiplicative_numeric_expression()); - } + if (context.LT() != null) + { + return new Expression(left < right); + } - 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; - } + if (context.GT() != null) + { + return new Expression(left > right); + } - if (context.DIV() != null) - { - return result / signed_unary_num_expr; - } - // Else MOD - return result % signed_unary_num_expr; + if (context.LE() != null) + { + return new Expression(left <= right); } - return (Expression)this.VisitSigned_unary_numeric_expression(context.signed_unary_numeric_expression()); + if (context.GE() != null) + { + return new Expression(left >= right); + } + + throw new Exception("Unknown comparison operator"); } - 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 VisitUnaryExpression(ArcscriptParser.UnaryExpressionContext context) + { + if (context.NOTKEYWORD() != null || context.NEG() != null) + { + return !(Expression)this.VisitExpression(context.expression()); + } - if ( sign != null ) { - if ( sign.ADD() != null ) { - return unary_num_expr; - } - // Else MINUS - return -unary_num_expr; + if (context.ADD() != null) + { + return this.VisitExpression(context.expression()); } - return unary_num_expr; - } - 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.SUB() != null) + { + return -(Expression)this.VisitExpression(context.expression()); } - if ( context.INTEGER() != null ) { - return new Expression(int.Parse(context.INTEGER().GetText())); + throw new Exception("Unknown unary operator"); + } + + public override object VisitMultiplicativeExpression(ArcscriptParser.MultiplicativeExpressionContext context) + { + var left = (Expression)this.VisitExpression(context.expression(0)); + var right = (Expression)this.VisitExpression(context.expression(1)); + if (context.MUL() != null) + { + return left * right; } - if (context.STRING() != null) + if (context.DIV() != null) { - string result = context.STRING().GetText(); - result = result.Substring(1, result.Length - 2); - return new Expression(result); + return left / right; } - if (context.BOOLEAN() != null) + if (context.MOD() != null) { - return new Expression(context.BOOLEAN().GetText() == "true"); + return left % right; } - if ( context.VARIABLE() != null ) { - string variableName = context.VARIABLE().GetText(); - return new Expression(this.state.GetVarValue(variableName)); + throw new Exception("Unknown multiplicative operator"); + } + + public override object VisitAdditiveExpression(ArcscriptParser.AdditiveExpressionContext context) + { + var left = (Expression)this.VisitExpression(context.expression(0)); + var right = (Expression)this.VisitExpression(context.expression(1)); + if (context.ADD() != null) + { + return left + right; } - if ( context.function_call() != null ) + if (context.SUB() != null) { - object functionResult = this.VisitFunction_call(context.function_call()); - if (functionResult.GetType() == typeof(Expression)) - { - return functionResult; - } - return new Expression(functionResult); + return left - right; } - return this.VisitCompound_condition_or(context.compound_condition_or()); + throw new Exception("Unknown additive operator"); } - public override object VisitVoid_function_call([NotNull] ArcscriptParser.Void_function_callContext context) + + public override object VisitParenthesizedExpression(ArcscriptParser.ParenthesizedExpressionContext context) { - string fname = ""; - IList? argument_list_result = null; - if (context.VFNAME() != null) + return VisitExpression(context.expression()); + } + + public override object VisitIdentifierExpression(ArcscriptParser.IdentifierExpressionContext context) + { + var identifier = (IdentifierDef)VisitIdentifier(context.identifier()); + return new Expression(this.state.GetVarValue(identifier.Name, identifier.Scope)); + } + + public override object VisitLiteralExpression(ArcscriptParser.LiteralExpressionContext context) + { + return VisitLiteral(context.literal()); + } + + 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) { - fname = context.VFNAME().GetText(); - if (context.argument_list() != null) + if (context.BOOLEAN().GetText() == "true") { - argument_list_result = (IList)this.VisitArgument_list(context.argument_list()); + return new Expression(true); } + return new Expression(false); } - if (context.VFNAMEVARS() != null) + + if (context.STRING() != null) { - fname = context.VFNAMEVARS().GetText(); - if (context.variable_list() != null) - { - argument_list_result = (IList)this.VisitVariable_list(context.variable_list()); - } + string result = context.STRING().GetText(); + result = result.Substring(1, result.Length - 2); + return new Expression(result); } + return VisitNumeric_literal(context.numeric_literal()); + } - object returnValue = this._functions.functions[fname](argument_list_result); - - return returnValue; + public override object VisitNumeric_literal(ArcscriptParser.Numeric_literalContext context) + { + if (context.FLOAT() != null) + { + return new Expression(double.Parse(context.FLOAT().GetText(), CultureInfo.InvariantCulture)); + } + 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 VisitExpression(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 VisitMention([NotNull] ArcscriptParser.MentionContext context) { @@ -487,5 +537,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 From d9fc46fe9f7a4355c50801dc068a3f92df0b0ced Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Thu, 19 Mar 2026 11:05:28 +0200 Subject: [PATCH 18/21] chore: replace unnecessary VisitExpression method --- CSharp/Interpreter/ArcscriptVisitor.cs | 80 ++++++-------------------- 1 file changed, 17 insertions(+), 63 deletions(-) diff --git a/CSharp/Interpreter/ArcscriptVisitor.cs b/CSharp/Interpreter/ArcscriptVisitor.cs index 0e56cc4..760856a 100644 --- a/CSharp/Interpreter/ArcscriptVisitor.cs +++ b/CSharp/Interpreter/ArcscriptVisitor.cs @@ -43,7 +43,7 @@ public override object VisitInput([NotNull] ArcscriptParser.InputContext context public override object VisitCondition(ArcscriptParser.ConditionContext context) { - return this.VisitExpression(context.expression()); + return Visit(context.expression())!; } public override object VisitScript_section([NotNull] ArcscriptParser.Script_sectionContext context) { @@ -154,11 +154,11 @@ public override object VisitElse_section([NotNull] ArcscriptParser.Else_sectionC } public override object VisitIf_clause([NotNull] ArcscriptParser.If_clauseContext context) { - return this.VisitExpression(context.expression()); + return Visit(context.expression())!; } public override object VisitElse_if_clause([NotNull] ArcscriptParser.Else_if_clauseContext context) { - return this.VisitExpression(context.expression()); + return Visit(context.expression())!; } public override object VisitStatement_assignment([NotNull] ArcscriptParser.Statement_assignmentContext context) @@ -167,7 +167,7 @@ public override object VisitStatement_assignment([NotNull] ArcscriptParser.State var identifierValue = new Expression(this.state.GetVarValue(identifier.Name, identifier.Scope)); - var re = (Expression)this.VisitExpression(context.expression()); + var re = (Expression)Visit(context.expression())!; if ( context.ASSIGN() != null ) { this.state.SetVarValue(identifier, re.Value); @@ -211,62 +211,16 @@ public override object VisitIdentifier(ArcscriptParser.IdentifierContext context return new IdentifierDef(name, scope); } - public object VisitExpression([NotNull] ArcscriptParser.ExpressionContext context) - { - var contextType = context.GetType(); - if (contextType == typeof(ArcscriptParser.ComparisonExpressionContext)) - { - return (Expression)VisitComparisonExpression((ArcscriptParser.ComparisonExpressionContext)context); - } - - if (contextType == typeof(ArcscriptParser.UnaryExpressionContext)) - { - return (Expression)VisitUnaryExpression((ArcscriptParser.UnaryExpressionContext)context); - } - - if (contextType == typeof(ArcscriptParser.MultiplicativeExpressionContext)) - { - return (Expression)VisitMultiplicativeExpression((ArcscriptParser.MultiplicativeExpressionContext)context); - } - - if (contextType == typeof(ArcscriptParser.AdditiveExpressionContext)) - { - return (Expression)VisitAdditiveExpression((ArcscriptParser.AdditiveExpressionContext)context); - } - - if (contextType == typeof(ArcscriptParser.ParenthesizedExpressionContext)) - { - return (Expression)VisitParenthesizedExpression((ArcscriptParser.ParenthesizedExpressionContext)context); - } - - if (contextType == typeof(ArcscriptParser.FunctionCallExpressionContext)) - { - return (Expression)VisitFunctionCallExpression((ArcscriptParser.FunctionCallExpressionContext)context); - } - - if (contextType == typeof(ArcscriptParser.LiteralExpressionContext)) - { - return (Expression)VisitLiteralExpression((ArcscriptParser.LiteralExpressionContext)context); - } - - if (contextType == typeof(ArcscriptParser.IdentifierExpressionContext)) - { - return (Expression)VisitIdentifierExpression((ArcscriptParser.IdentifierExpressionContext)context); - } - - throw new Exception("Unknown expression type: " + contextType.Name); - } - public override object VisitComparisonExpression(ArcscriptParser.ComparisonExpressionContext context) { - var left =(Expression)VisitExpression(context.expression(0)); + 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)VisitExpression(context.expression(1)); + var rightComp = (Expression)Visit(context.expression(1))!; return new Expression(Expression.GetBoolValue(rightComp.Value)); } @@ -276,11 +230,11 @@ public override object VisitComparisonExpression(ArcscriptParser.ComparisonExpre { return left; } - var rightComp = (Expression)VisitExpression(context.expression(1)); + var rightComp = (Expression)Visit(context.expression(1))!; return rightComp; } - var right = (Expression)this.VisitExpression(context.expression(1)); + var right = (Expression)Visit(context.expression(1))!; if (context.EQ() != null || (context.ISKEYWORD() != null && context.NOTKEYWORD() == null)) { @@ -319,25 +273,25 @@ public override object VisitUnaryExpression(ArcscriptParser.UnaryExpressionConte { if (context.NOTKEYWORD() != null || context.NEG() != null) { - return !(Expression)this.VisitExpression(context.expression()); + return !(Expression)Visit(context.expression())!; } if (context.ADD() != null) { - return this.VisitExpression(context.expression()); + return Visit(context.expression())!; } if (context.SUB() != null) { - return -(Expression)this.VisitExpression(context.expression()); + return -(Expression)Visit(context.expression())!; } throw new Exception("Unknown unary operator"); } public override object VisitMultiplicativeExpression(ArcscriptParser.MultiplicativeExpressionContext context) { - var left = (Expression)this.VisitExpression(context.expression(0)); - var right = (Expression)this.VisitExpression(context.expression(1)); + var left = (Expression)Visit(context.expression(0))!; + var right = (Expression)Visit(context.expression(1))!; if (context.MUL() != null) { return left * right; @@ -357,8 +311,8 @@ public override object VisitMultiplicativeExpression(ArcscriptParser.Multiplicat public override object VisitAdditiveExpression(ArcscriptParser.AdditiveExpressionContext context) { - var left = (Expression)this.VisitExpression(context.expression(0)); - var right = (Expression)this.VisitExpression(context.expression(1)); + var left = (Expression)Visit(context.expression(0))!; + var right = (Expression)Visit(context.expression(1))!; if (context.ADD() != null) { return left + right; @@ -373,7 +327,7 @@ public override object VisitAdditiveExpression(ArcscriptParser.AdditiveExpressio public override object VisitParenthesizedExpression(ArcscriptParser.ParenthesizedExpressionContext context) { - return VisitExpression(context.expression()); + return Visit(context.expression())!; } public override object VisitIdentifierExpression(ArcscriptParser.IdentifierExpressionContext context) @@ -466,7 +420,7 @@ public override object VisitArgument_list([NotNull] ArcscriptParser.Argument_lis public override object VisitArgument([NotNull] ArcscriptParser.ArgumentContext context) { if (context.expression() != null) { - return VisitExpression(context.expression()); + return Visit(context.expression())!; } if ( context.mention() != null ) { Mention mention_result = (Mention)this.VisitMention(context.mention()); From e41ea4c7dd5d8f176954462225091ab0ecf72f31 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Thu, 19 Mar 2026 11:07:59 +0200 Subject: [PATCH 19/21] chore (js): remove unnecessary visitExpression method --- TypeScript/src/ArcscriptVisitor.ts | 65 ++++++++---------------------- 1 file changed, 17 insertions(+), 48 deletions(-) diff --git a/TypeScript/src/ArcscriptVisitor.ts b/TypeScript/src/ArcscriptVisitor.ts index 3372eb6..86987f8 100644 --- a/TypeScript/src/ArcscriptVisitor.ts +++ b/TypeScript/src/ArcscriptVisitor.ts @@ -19,7 +19,6 @@ import { Else_if_clauseContext, Else_if_sectionContext, Else_sectionContext, - ExpressionContext, Function_call_segmentContext, Function_callContext, FunctionCallExpressionContext, @@ -67,7 +66,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { visitCondition = (ctx: ConditionContext) => { return { - condition: !!this.visitExpression(ctx.expression()), + condition: !!this.visit(ctx.expression()), }; }; @@ -178,18 +177,18 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { }; visitIf_clause = (ctx: If_clauseContext): boolean => { - return this.visitExpression(ctx.expression()) as boolean; + return this.visit(ctx.expression()) as boolean; }; visitElse_if_clause = (ctx: Else_if_clauseContext): boolean => { - return this.visitExpression(ctx.expression()) as boolean; + return this.visit(ctx.expression()) as boolean; }; visitStatement_assignment = (ctx: Statement_assignmentContext) => { const assignable = this.visitAssignable(ctx.assignable()); let variableValue = assignable.getValue(); - let expressionValue = this.visitExpression(ctx.expression()); + let expressionValue = this.visit(ctx.expression()); if (expressionValue === undefined) { throw new RuntimeError('The right part of the assigment has no value'); } @@ -254,53 +253,23 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { return this.visitIdentifier(ctx.identifier()); }; - visitExpression(ctx: ExpressionContext): VarValue { - if (ctx.constructor === ComparisonExpressionContext) { - return this.visitComparisonExpression(ctx); - } - if (ctx.constructor === UnaryExpressionContext) { - return this.visitUnaryExpression(ctx); - } - if (ctx.constructor === MultiplicativeExpressionContext) { - return this.visitMultiplicativeExpression(ctx); - } - if (ctx.constructor === AdditiveExpressionContext) { - return this.visitAdditiveExpression(ctx); - } - if (ctx.constructor === ParenthesizedExpressionContext) { - return this.visitParenthesizedExpression(ctx); - } - if (ctx.constructor === FunctionCallExpressionContext) { - return this.visitFunctionCallExpression(ctx); - } - if (ctx.constructor === LiteralExpressionContext) { - return this.visitLiteralExpression(ctx); - } - if (ctx.constructor === IdentifierExpressionContext) { - return this.visitIdentifierExpression(ctx); - } - throw new RuntimeError( - 'Invalid expression: Constructor = ' + ctx.constructor.name - ); - } - visitComparisonExpression = (ctx: ComparisonExpressionContext): boolean => { - const left = this.visitExpression(ctx.expression(0)); + const left = this.visit(ctx.expression(0)); if (ctx.AND() || ctx.ANDKEYWORD()) { if (!left) { return false; } - const right = this.visitExpression(ctx.expression(1)); + const right = this.visit(ctx.expression(1)); return Boolean(left) && Boolean(right); } if (ctx.OR() || ctx.ORKEYWORD()) { if (left) { return true; } - const right = this.visitExpression(ctx.expression(1)); + const right = this.visit(ctx.expression(1)); return Boolean(left) || Boolean(right); } - const right = this.visitExpression(ctx.expression(1)); + const right = this.visit(ctx.expression(1)); if (ctx.EQ() || (ctx.ISKEYWORD() && !ctx.NOTKEYWORD())) { return left === right; @@ -325,20 +294,20 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { visitUnaryExpression = (ctx: UnaryExpressionContext) => { if (ctx.NOTKEYWORD() || ctx.NEG()) { - return !this.visitExpression(ctx.expression()); + return !this.visit(ctx.expression()); } if (ctx.ADD()) { - return this.visitExpression(ctx.expression()); + return this.visit(ctx.expression()); } if (ctx.SUB()) { - return -this.visitExpression(ctx.expression()); + return -this.visit(ctx.expression()); } throw new RuntimeError('Invalid unary operator'); }; visitMultiplicativeExpression = (ctx: MultiplicativeExpressionContext) => { - const left = this.visitExpression(ctx.expression(0)); - const right = this.visitExpression(ctx.expression(1)); + 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'); } @@ -364,8 +333,8 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { }; visitAdditiveExpression = (ctx: AdditiveExpressionContext) => { - let left = this.visitExpression(ctx.expression(0)); - let right = this.visitExpression(ctx.expression(1)); + 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); @@ -388,7 +357,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { }; visitParenthesizedExpression = (ctx: ParenthesizedExpressionContext) => { - return this.visitExpression(ctx.expression()); + return this.visit(ctx.expression()); }; visitIdentifierExpression = (ctx: IdentifierExpressionContext) => { @@ -487,7 +456,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { visitArgument = (ctx: ArgumentContext): VarValue | MentionResult => { if (ctx.expression()) { - return this.visitExpression(ctx.expression()); + return this.visit(ctx.expression()); } if (ctx.mention()) { return this.visitMention(ctx.mention()); From 796bd4f75214fa594627a75feee0d7235701c6b5 Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Thu, 19 Mar 2026 13:32:40 +0200 Subject: [PATCH 20/21] feat (cpp): scoped variables in Cpp --- Cpp/CMakeLists.txt | 6 +- Cpp/demo/ArcscriptTest.cpp | 4 + Cpp/demo/tests/member.json | 118 +++++++++ Cpp/src/ArcscriptExpression.cpp | 18 +- Cpp/src/ArcscriptExpression.h | 5 + Cpp/src/ArcscriptFunctions.cpp | 11 +- Cpp/src/ArcscriptFunctions.h | 2 + Cpp/src/ArcscriptHelpers.h | 50 ++-- Cpp/src/ArcscriptParserBase.cpp | 25 +- Cpp/src/ArcscriptParserBase.h | 4 +- Cpp/src/ArcscriptTranspiler.cpp | 10 +- Cpp/src/ArcscriptTranspiler.h | 2 + Cpp/src/ArcscriptVisitor.cpp | 438 ++++++++++++++------------------ Cpp/src/ArcscriptVisitor.h | 29 ++- 14 files changed, 420 insertions(+), 302 deletions(-) create mode 100644 Cpp/demo/tests/member.json 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; }; From 514576af70a36aaa0281cf41bf598b4d75e1ec8e Mon Sep 17 00:00:00 2001 From: Manolis Dermitzakis Date: Thu, 19 Mar 2026 13:33:18 +0200 Subject: [PATCH 21/21] chore: move method --- CSharp/Interpreter/ArcscriptVisitor.cs | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/CSharp/Interpreter/ArcscriptVisitor.cs b/CSharp/Interpreter/ArcscriptVisitor.cs index 760856a..4c750bf 100644 --- a/CSharp/Interpreter/ArcscriptVisitor.cs +++ b/CSharp/Interpreter/ArcscriptVisitor.cs @@ -195,22 +195,6 @@ public override object VisitAssignable(ArcscriptParser.AssignableContext context return this.VisitIdentifier(context.identifier()); } - 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 VisitComparisonExpression(ArcscriptParser.ComparisonExpressionContext context) { var left = (Expression)Visit(context.expression(0))!; @@ -433,6 +417,22 @@ public override object VisitIdentifier_list(ArcscriptParser.Identifier_listConte { 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) { Dictionary attrs = new Dictionary();