diff --git a/.github/workflows/CSharp.yml b/.github/workflows/CSharp.yml
new file mode 100644
index 0000000..3436067
--- /dev/null
+++ b/.github/workflows/CSharp.yml
@@ -0,0 +1,42 @@
+name: CI CSharp
+
+on:
+ push:
+ branches: ["main"]
+ paths:
+ - "CSharp/**"
+ - "grammar/*.g4"
+ pull_request:
+ branches: ["main"]
+ paths:
+ - "CSharp/**"
+ - "grammar/*.g4"
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./CSharp
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: "6.0.x"
+
+ - name: Generate ANTLR4 files
+ run: sh generate.sh
+ working-directory: .
+
+ - name: Restore
+ run: dotnet restore CSharp.sln
+
+ - name: Build
+ run: dotnet build CSharp.sln --configuration Release --no-restore
+
+ - name: Test
+ run: dotnet test CSharp.sln --configuration Release --no-build --verbosity normal
diff --git a/CSharp/.gitignore b/CSharp/.gitignore
index 21bc544..9dd07fb 100644
--- a/CSharp/.gitignore
+++ b/CSharp/.gitignore
@@ -1 +1,425 @@
-src/Generated
\ No newline at end of file
+Interpreter/Generated
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- Backup*.rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+
+
+## VS CODE
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+.idea
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
\ No newline at end of file
diff --git a/CSharp/CSharp.csproj b/CSharp/CSharp.csproj
new file mode 100644
index 0000000..d74dd02
--- /dev/null
+++ b/CSharp/CSharp.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+ true
+ Arcweave
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CSharp/CSharp.sln b/CSharp/CSharp.sln
new file mode 100644
index 0000000..c54d833
--- /dev/null
+++ b/CSharp/CSharp.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharp", "CSharp.csproj", "{46E8C9BE-D0EC-440D-9FBB-034D1FD4E434}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {46E8C9BE-D0EC-440D-9FBB-034D1FD4E434}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {46E8C9BE-D0EC-440D-9FBB-034D1FD4E434}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {46E8C9BE-D0EC-440D-9FBB-034D1FD4E434}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {46E8C9BE-D0EC-440D-9FBB-034D1FD4E434}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/CSharp/Interpreter/ArcscriptExceptions.cs b/CSharp/Interpreter/ArcscriptExceptions.cs
new file mode 100644
index 0000000..b3bb46d
--- /dev/null
+++ b/CSharp/Interpreter/ArcscriptExceptions.cs
@@ -0,0 +1,23 @@
+namespace Arcweave.Interpreter;
+
+public class RuntimeException : Exception
+{
+ public RuntimeException(string message) : base(message)
+ {
+ }
+
+ public RuntimeException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
+
+public class ParseException : Exception
+{
+ public ParseException(string message) : base(message)
+ {
+ }
+
+ public ParseException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Interpreter/ArcscriptExpression.cs b/CSharp/Interpreter/ArcscriptExpression.cs
new file mode 100644
index 0000000..bb9b67c
--- /dev/null
+++ b/CSharp/Interpreter/ArcscriptExpression.cs
@@ -0,0 +1,375 @@
+
+using System;
+using System.Collections;
+
+namespace Arcweave.Interpreter
+{
+ public class Expression : ArcscriptExpressionBase, IComparable
+ {
+ public object Value { get; set; }
+ public Expression() { Value = null; }
+ public Expression(object value) { Value = value; }
+ public Expression(object value, Type type) { Value = Convert.ChangeType(value, type); }
+ public Expression(Expression expression) { Value = expression.Value; }
+ public void SetValue(object value) { Value = value; }
+ public Type Type() { return Value.GetType(); }
+ public static Expression operator +(Expression e) { return e; }
+ public static Expression operator -(Expression e)
+ {
+ if (e.Value is int i)
+ {
+ return new Expression(-i);
+ }
+ else if (e.Value is double value)
+ {
+ return new Expression(-value);
+ }
+ return e;
+ }
+ public static Expression operator +(Expression first, Expression second)
+ {
+ if (first.Type() == typeof(string) || second.Type() == typeof(string))
+ {
+ return new Expression(first.ToString() + second.ToString());
+ }
+ var doubleValues = GetDoubleValues(first.Value, second.Value);
+ if (!doubleValues.HasDouble)
+ {
+ var intValue = (int)(doubleValues.Value1 + doubleValues.Value2);
+ return new Expression(intValue);
+ }
+ else
+ {
+ return new Expression(doubleValues.Value1 + doubleValues.Value2);
+ }
+ }
+
+ public static Expression operator -(Expression first, Expression second)
+ {
+ if (first.Type() == typeof(string) || second.Type() == typeof(string))
+ {
+ throw new InvalidOperationException("Subtraction is not supported for string types.");
+ }
+ var doubleValues = GetDoubleValues(first.Value, second.Value);
+ if (!doubleValues.HasDouble)
+ {
+ var intValue = (int) (doubleValues.Value1 - doubleValues.Value2);
+ return new Expression(intValue);
+ } else
+ {
+ return new Expression(doubleValues.Value1 - doubleValues.Value2);
+ }
+ }
+
+ public static Expression operator *(Expression first, Expression second)
+ {
+ if (first.Type() == typeof(string) || second.Type() == typeof(string))
+ {
+ throw new InvalidOperationException("Multiplication is not supported for string types.");
+ }
+ var doubleValues = GetDoubleValues(first.Value, second.Value);
+ if (!doubleValues.HasDouble)
+ {
+ var intValue = (int)(doubleValues.Value1 * doubleValues.Value2);
+ return new Expression(intValue);
+ }
+ else
+ {
+ return new Expression(doubleValues.Value1 * doubleValues.Value2);
+ }
+ }
+
+ public static Expression operator /(Expression first, Expression second)
+ {
+ if (first.Type() == typeof(string) || second.Type() == typeof(string))
+ {
+ throw new InvalidOperationException("Division is not supported for string types.");
+ }
+ var doubleValues = GetDoubleValues(first.Value, second.Value);
+ var result = doubleValues.Value1 / doubleValues.Value2;
+ if (double.IsInfinity(result))
+ {
+ throw new DivideByZeroException("Division by zero.");
+ }
+ return new Expression(result);
+ }
+
+ public static bool operator ==(Expression first, Expression second)
+ {
+ System.Type type1 = first.Type();
+ if (type1 == typeof(int) || type1 == typeof(double))
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second.Value);
+ return doubleValues.Value1 == doubleValues.Value2;
+ }
+ if (type1 == typeof(bool))
+ {
+ return (bool)first.Value == (bool)second.Value;
+ }
+ if (type1 == typeof(string))
+ {
+ return (string)first.Value == (string)second.Value;
+ }
+ return false;
+ }
+
+ public int CompareTo(object other)
+ {
+ Expression o = (Expression)other;
+ DoubleValues fValues = GetDoubleValues(this.Value, o.Value);
+ double result = fValues.Value1 - fValues.Value2;
+ if (result < 0)
+ {
+ return -1;
+ } else if (result > 0)
+ {
+ return 1;
+ }
+ return 0;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null || !(obj is Expression))
+ {
+ return false;
+ }
+ Expression e = (Expression)obj;
+
+ System.Type type1 = Type();
+ if (type1 == typeof(int) || type1 == typeof(double))
+ {
+ DoubleValues doubleValues = GetDoubleValues(Value, e.Value);
+ return doubleValues.Value1 == doubleValues.Value2;
+ }
+ if (type1 == typeof(bool))
+ {
+ return (bool)Value == (bool)e.Value;
+ }
+ if (type1 == typeof(string))
+ {
+ return (string)Value == (string)e.Value;
+ }
+ return false;
+ }
+
+ public static bool operator !=(Expression first, Expression second)
+ {
+ System.Type type1 = first.Type();
+ if (type1 == typeof(int) || type1 == typeof(double))
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second.Value);
+ return doubleValues.Value1 != doubleValues.Value2;
+ }
+ if (type1 == typeof(bool))
+ {
+ return (bool)first.Value != (bool)second.Value;
+ }
+ if (type1 == typeof(string))
+ {
+ return (string)first.Value != (string)second.Value;
+ }
+ return true;
+ }
+ public static bool operator ==(Expression first, bool second)
+ {
+ return GetBoolValue(first.Value) == second;
+ }
+ public static bool operator !=(Expression first, bool second)
+ {
+ return GetBoolValue(first.Value) != second;
+ }
+ public static bool operator ==(Expression first, int second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 == doubleValues.Value2;
+ }
+ public static bool operator !=(Expression first, int second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 != doubleValues.Value2;
+ }
+ public static bool operator ==(Expression first, double second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 == doubleValues.Value2;
+ }
+ public static bool operator !=(Expression first, double second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 != doubleValues.Value2;
+ }
+ public static bool operator ==(Expression first, string second)
+ {
+ return (string)first.Value == second;
+ }
+
+ public override int GetHashCode()
+ {
+ return Value.GetHashCode();
+ }
+ public static bool operator !=(Expression first, string second)
+ {
+ return (string)first.Value == second;
+ }
+
+ public static bool operator >(Expression first, Expression second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second.Value);
+ return doubleValues.Value1 > doubleValues.Value2;
+ }
+ public static bool operator <(Expression first, Expression second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second.Value);
+ return doubleValues.Value1 < doubleValues.Value2;
+ }
+ public static bool operator >=(Expression first, Expression second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second.Value);
+ return doubleValues.Value1 >= doubleValues.Value2;
+ }
+ public static bool operator <=(Expression first, Expression second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second.Value);
+ return doubleValues.Value1 <= doubleValues.Value2;
+ }
+ public static bool operator >(Expression first, int second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 > doubleValues.Value2;
+ }
+ public static bool operator <(Expression first, int second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 < doubleValues.Value2;
+ }
+ public static bool operator >=(Expression first, int second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 >= doubleValues.Value2;
+ }
+ public static bool operator <=(Expression first, int second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 <= doubleValues.Value2;
+ }
+ public static bool operator >(Expression first, double second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 > doubleValues.Value2;
+ }
+ public static bool operator <(Expression first, double second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 < doubleValues.Value2;
+ }
+ public static bool operator >=(Expression first, double second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 >= doubleValues.Value2;
+ }
+ public static bool operator <=(Expression first, double second)
+ {
+ DoubleValues doubleValues = GetDoubleValues(first.Value, second);
+ return doubleValues.Value1 <= doubleValues.Value2;
+ }
+ public static Expression operator !(Expression e)
+ {
+ return new Expression(!GetBoolValue(e.Value));
+ }
+ public static bool operator true(Expression e)
+ {
+ return GetBoolValue(e.Value);
+ }
+ public static bool operator false(Expression e)
+ {
+ return !GetBoolValue(e.Value);
+ }
+ public static Expression operator &(Expression first, Expression second)
+ {
+ if (GetBoolValue(first.Value) && GetBoolValue(second.Value))
+ {
+ return new Expression(true);
+ }
+ return new Expression(false);
+ }
+
+ public static Expression operator |(Expression first, Expression second)
+ {
+ if (GetBoolValue(first.Value) || GetBoolValue(second.Value))
+ {
+ return new Expression(true);
+ }
+ return new Expression(false);
+ }
+
+ public override string ToString()
+ {
+ if (Value.GetType() == typeof(bool))
+ {
+ if ((bool)Value)
+ {
+ return "true";
+ }
+ return "false";
+ }
+
+ if (Value.GetType() == typeof(double))
+ {
+ return ((double)Value).ToString(NumberFormat);
+ }
+ return Value.ToString();
+ }
+
+ private struct DoubleValues
+ {
+ public double Value1;
+ public double Value2;
+ public bool HasDouble;
+
+ public DoubleValues(double val1, double val2, bool hasDouble) { Value1 = val1; Value2 = val2; HasDouble = hasDouble; }
+ }
+
+ private static DoubleValues GetDoubleValues(object first, object second) {
+ bool hasDouble = false;
+ int value1, value2;
+ double flValue1 = 0, flValue2 = 0;
+ if (first.GetType() == typeof(int))
+ {
+ value1 = (int)first;
+ flValue1 = value1;
+ } else if (first.GetType() == typeof(double))
+ {
+ hasDouble = true;
+ flValue1 = (double)first;
+ } else if (first.GetType() == typeof(bool))
+ {
+ flValue1 = (bool)first ? 1 : 0;
+ }
+
+ if (second.GetType() == typeof(int))
+ {
+ value2 = (int)second;
+ flValue2 = value2;
+ } else if (second.GetType() == typeof(double))
+ {
+ hasDouble = true;
+ flValue2 = (double)second;
+ } else if (second.GetType() == typeof(bool))
+ {
+ flValue2 = (bool)second ? 1 : 0;
+ }
+
+ return new DoubleValues(flValue1, flValue2, hasDouble);
+ }
+
+ 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; }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Interpreter/ArcscriptExpressionBase.cs b/CSharp/Interpreter/ArcscriptExpressionBase.cs
new file mode 100644
index 0000000..8dc0958
--- /dev/null
+++ b/CSharp/Interpreter/ArcscriptExpressionBase.cs
@@ -0,0 +1,16 @@
+using System.Globalization;
+
+namespace Arcweave.Interpreter;
+
+public class ArcscriptExpressionBase
+{
+ public NumberFormatInfo NumberFormat { get; private set; }
+ public ArcscriptExpressionBase()
+ {
+ NumberFormat = new NumberFormatInfo
+ {
+ NumberDecimalSeparator = ".",
+ NumberGroupSeparator = ","
+ };
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Interpreter/ArcscriptFunctions.cs b/CSharp/Interpreter/ArcscriptFunctions.cs
new file mode 100644
index 0000000..20605e0
--- /dev/null
+++ b/CSharp/Interpreter/ArcscriptFunctions.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Arcweave.Interpreter.INodes;
+
+namespace Arcweave.Interpreter
+{
+ public class Functions
+ {
+ private static readonly Random _getrandom = new Random();
+ private IProject _project;
+ private string elementId;
+ private ArcscriptState state;
+ public Dictionary, object>> functions { get; private set; } = new Dictionary, object>>();
+
+ public Dictionary returnTypes = new Dictionary()
+ {
+ { "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) }
+ };
+
+ public Functions(string elementId, IProject project, ArcscriptState state) {
+ this._project = project;
+ this.elementId = elementId;
+ this.state = state;
+
+ this.functions["sqrt"] = this.Sqrt;
+ this.functions["sqr"] = this.Sqr;
+ this.functions["abs"] = this.Abs;
+ this.functions["random"] = this.Random;
+ this.functions["roll"] = this.Roll;
+ this.functions["show"] = this.Show;
+ this.functions["reset"] = this.Reset;
+ this.functions["resetAll"] = this.ResetAll;
+ this.functions["round"] = this.Round;
+ this.functions["min"] = this.Min;
+ this.functions["max"] = this.Max;
+ this.functions["visits"] = this.Visits;
+ }
+
+ public object Sqrt(IList
".Length);
+ this.state.Outputs.AddParagraph(paragraphContent);
+ return context.GetText();
+ }
+
+ public override object VisitBlockquote(ArcscriptParser.BlockquoteContext context)
+ {
+ this.state.Outputs.AddBlockquote();
+ this.VisitChildren(context);
+ this.state.Outputs.ExitBlockquote();
+ return context.GetText();
+ }
+
+ public override object VisitAssignment_segment([NotNull] ArcscriptParser.Assignment_segmentContext context) {
+ this.state.Outputs.AddScriptOutput(null);
+ return this.VisitStatement_assignment(context.statement_assignment());
+ }
+
+ 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());
+ }
+
+ public override object VisitConditional_section([NotNull] ArcscriptParser.Conditional_sectionContext context) {
+ this.state.Outputs.AddScriptOutput(null);
+ ConditionalSection if_section = (ConditionalSection)this.VisitIf_section(context.if_section());
+ if ( if_section.Clause ) {
+ this.state.Outputs.AddScriptOutput(null);
+ return if_section.Script;
+ }
+ foreach(ArcscriptParser.Else_if_sectionContext else_if_context in context.else_if_section())
+ {
+ ConditionalSection elif_section = (ConditionalSection)this.VisitElse_if_section(else_if_context);
+ if (elif_section.Clause )
+ {
+ return elif_section.Script;
+ }
+ }
+
+ if ( context.else_section() != null ) {
+ ConditionalSection elseSection = (ConditionalSection)this.VisitElse_section(context.else_section());
+ this.state.Outputs.AddScriptOutput(null);
+ return elseSection.Script;
+ }
+ this.state.Outputs.AddScriptOutput(null);
+ return null;
+ }
+
+ public override object VisitIf_section([NotNull] ArcscriptParser.If_sectionContext context) {
+ Expression result = (Expression)this.VisitIf_clause(context.if_clause());
+ ConditionalSection ifSection = new ConditionalSection(false, null);
+ if ( result ) {
+ ifSection.Clause = true;
+ ifSection.Script = this.VisitScript(context.script());
+ }
+ return ifSection;
+ }
+
+ public override object VisitElse_if_section([NotNull] ArcscriptParser.Else_if_sectionContext context) {
+ Expression result = (Expression)this.VisitElse_if_clause(context.else_if_clause());
+ ConditionalSection elseIfSection = new ConditionalSection(false, null);
+ if ( result ) {
+ elseIfSection.Clause = true;
+ elseIfSection.Script = this.VisitScript(context.script());
+ }
+ return elseIfSection;
+ }
+
+ public override object VisitElse_section([NotNull] ArcscriptParser.Else_sectionContext context) {
+ return new ConditionalSection(true, this.VisitScript(context.script()));
+ }
+
+ public override object VisitIf_clause([NotNull] ArcscriptParser.If_clauseContext context) {
+ return this.VisitCompound_condition_or(context.compound_condition_or());
+ }
+
+ public override object VisitElse_if_clause([NotNull] ArcscriptParser.Else_if_clauseContext context) {
+ return this.VisitCompound_condition_or(context.compound_condition_or());
+ }
+
+ public override object VisitStatement_assignment([NotNull] ArcscriptParser.Statement_assignmentContext context) {
+ string variableName = context.VARIABLE().GetText();
+ IVariable variable = this.state.GetVariable(variableName);
+
+ Expression compound_condition_or = (Expression)this.VisitCompound_condition_or(context.compound_condition_or());
+ if ( context.ASSIGN() != null ) {
+ this.state.SetVarValue(variableName, compound_condition_or.Value);
+ return null;
+ }
+
+ Expression variableValue = new Expression(this.state.GetVarValue(variableName));
+
+ if ( context.ASSIGNADD() != null ) {
+ variableValue += compound_condition_or;
+ } else if ( context.ASSIGNSUB() != null ) {
+ variableValue -= compound_condition_or;
+ } else if ( context.ASSIGNMUL() != null ) {
+ variableValue *= compound_condition_or;
+ } else if ( context.ASSIGNDIV() != null ) {
+ variableValue /= compound_condition_or;
+ }
+
+ this.state.SetVarValue(variableName, variableValue.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 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;
+ }
+
+ 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;
+ }
+
+ return unary_condition;
+ }
+
+ public override object VisitUnary_condition([NotNull] ArcscriptParser.Unary_conditionContext context) {
+ return this.VisitCondition(context.condition());
+ }
+
+ public override object VisitCondition([NotNull] ArcscriptParser.ConditionContext context) {
+ if ( context.expression().Length == 1 ) {
+ return this.VisitExpression(context.expression()[0]);
+ }
+ 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 ( conditional_operator_context.GE() != null ) {
+ result = exp0 >= exp1;
+ }
+ if ( conditional_operator_context.LT() != null ) {
+ result = exp0 < exp1;
+ }
+ if ( conditional_operator_context.LE() != null ) {
+ result = exp0 <= exp1;
+ }
+ if ( conditional_operator_context.EQ() != null ) {
+ result = exp0 == exp1;
+ }
+ if ( conditional_operator_context.NE() != null ) {
+ result = exp0 != exp1;
+ }
+ if ( conditional_operator_context.ISKEYWORD() != null ) {
+ if ( conditional_operator_context.NOTKEYWORD() != null ) {
+ result = exp0 != exp1;
+ } else
+ {
+ result = exp0 == exp1;
+ }
+ }
+
+ 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.BOOLEAN() != null ) {
+ return new Expression(context.BOOLEAN().GetText() == "true");
+ }
+ 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;
+ }
+
+ return (Expression)this.VisitMultiplicative_numeric_expression(context.multiplicative_numeric_expression());
+ }
+
+ 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;
+ }
+ // Else DIV
+ return result / signed_unary_num_expr;
+ }
+
+ return (Expression)this.VisitSigned_unary_numeric_expression(context.signed_unary_numeric_expression());
+ }
+
+ public override object VisitSigned_unary_numeric_expression([NotNull] ArcscriptParser.Signed_unary_numeric_expressionContext context) {
+ Expression unary_num_expr = (Expression)this.VisitUnary_numeric_expression(context.unary_numeric_expression());
+ ArcscriptParser.SignContext sign = context.sign();
+
+ if ( sign != null ) {
+ if ( sign.ADD() != null ) {
+ return unary_num_expr;
+ }
+ // Else MINUS
+ return -unary_num_expr;
+ }
+ 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.INTEGER() != null ) {
+ return new Expression(int.Parse(context.INTEGER().GetText()));
+ }
+
+ if (context.STRING() != null)
+ {
+ string result = context.STRING().GetText();
+ result = result.Substring(1, result.Length - 2);
+ return new Expression(result);
+ }
+
+ if (context.BOOLEAN() != null)
+ {
+ return new Expression(context.BOOLEAN().GetText() == "true");
+ }
+ if ( context.VARIABLE() != null ) {
+ string variableName = context.VARIABLE().GetText();
+ return new Expression(this.state.GetVarValue(variableName));
+ }
+
+ if ( context.function_call() != null )
+ {
+ object functionResult = this.VisitFunction_call(context.function_call());
+ if (functionResult.GetType() == typeof(Expression))
+ {
+ return functionResult;
+ }
+ return new Expression(functionResult);
+ }
+ return this.VisitCompound_condition_or(context.compound_condition_or());
+ }
+ public override object VisitVoid_function_call([NotNull] ArcscriptParser.Void_function_callContext context)
+ {
+ string fname = "";
+ IList argument_list_result = null;
+ if (context.VFNAME() != null)
+ {
+ fname = context.VFNAME().GetText();
+ if (context.argument_list() != null)
+ {
+ argument_list_result = (IList)this.VisitArgument_list(context.argument_list());
+ }
+ }
+ if (context.VFNAMEVARS() != null)
+ {
+ fname = context.VFNAMEVARS().GetText();
+ if (context.variable_list() != null)
+ {
+ argument_list_result = (IList)this.VisitVariable_list(context.variable_list());
+ }
+ }
+
+ object returnValue = this._functions.functions[fname](argument_list_result);
+
+ return returnValue;
+ }
+
+ public override object VisitFunction_call([NotNull] ArcscriptParser.Function_callContext context) {
+ IList argument_list_result = null;
+ 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())
+ {
+ IVariable varObject = this.state.GetVariable(variable.GetText());
+ variables.Add(varObject);
+ }
+ return variables;
+ }
+
+ 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([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.mention() != null ) {
+ Mention mention_result = (Mention)this.VisitMention(context.mention());
+ return mention_result;
+ }
+ return this.VisitAdditive_numeric_expression(context.additive_numeric_expression());
+ }
+
+ public override object VisitMention([NotNull] ArcscriptParser.MentionContext context) {
+ Dictionary attrs = new Dictionary();
+
+ foreach ( ArcscriptParser.Mention_attributesContext attr in context.mention_attributes() ) {
+ Dictionary res = (Dictionary)this.VisitMention_attributes(attr);
+ attrs.Add((string)res["name"], (string)res["value"]);
+ }
+ string label = "";
+ if ( context.MENTION_LABEL() != null ) {
+ label = context.MENTION_LABEL().GetText();
+ }
+ return new Mention(label, attrs);
+ }
+
+ public override object VisitMention_attributes([NotNull] ArcscriptParser.Mention_attributesContext context) {
+ string name = context.ATTR_NAME().GetText();
+ ITerminalNode ctxvalue = context.ATTR_VALUE();
+ object value = true;
+ if ( ctxvalue != null ) {
+ string strvalue = ctxvalue.GetText();
+ if ( ( strvalue.StartsWith('"') && strvalue.EndsWith('"') ) ||
+ ( strvalue.StartsWith("'") && strvalue.EndsWith("'") ) ) {
+ strvalue = strvalue.Substring(1, strvalue.Length - 2);
+ }
+ value = strvalue;
+ }
+ return new Dictionary { { "name", name }, { "value", value } };
+ }
+
+ public class Argument
+ {
+ public Type type { get; private set; }
+ public object value { get; private set; }
+ public Argument(Type type, object value) {
+ this.type = type;
+ this.value = value;
+ }
+ }
+
+ public class Mention
+ {
+ public string label { get; private set; }
+ public Dictionary attrs { get; private set; }
+
+ public Mention(string label, Dictionary attrs) {
+ this.label = label;
+ this.attrs = attrs;
+ }
+ }
+
+ public struct ConditionalSection
+ {
+ public bool Clause;
+ public object Script;
+
+ public ConditionalSection(bool clause, object script) { Clause = clause; Script = script; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Interpreter/ErrorListener.cs b/CSharp/Interpreter/ErrorListener.cs
new file mode 100644
index 0000000..397d2c1
--- /dev/null
+++ b/CSharp/Interpreter/ErrorListener.cs
@@ -0,0 +1,17 @@
+using Antlr4.Runtime;
+
+namespace Arcweave.Interpreter;
+
+public class ErrorListener: ConsoleErrorListener
+{
+ public bool HasErrors = false;
+ public List Errors = new List();
+
+ public override void SyntaxError(TextWriter output, IRecognizer recognizer, S offendingSymbol, int line, int charPositionInLine,
+ string msg, RecognitionException e)
+ {
+ HasErrors = true;
+ Errors.Add($"line {line}:{charPositionInLine} {msg}");
+ base.SyntaxError(output, recognizer, offendingSymbol, line, charPositionInLine, msg, e);
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Interpreter/INodes/IAttribute.cs b/CSharp/Interpreter/INodes/IAttribute.cs
new file mode 100644
index 0000000..2023d33
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IAttribute.cs
@@ -0,0 +1,29 @@
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IAttribute
+ {
+ public enum DataType
+ {
+ Undefined,
+ StringPlainText,
+ StringRichText,
+ ComponentList,
+ }
+
+ public enum ContainerType
+ {
+ Undefined,
+ Component,
+ Element,
+ }
+
+ public string Name { get; }
+
+ public DataType Type { get; }
+
+ public ContainerType containerType { get; }
+
+ public string containerId { get; }
+
+ }
+}
diff --git a/CSharp/Interpreter/INodes/IBoard.cs b/CSharp/Interpreter/INodes/IBoard.cs
new file mode 100644
index 0000000..94faf57
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IBoard.cs
@@ -0,0 +1,6 @@
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IBoard
+ {
+ }
+}
diff --git a/CSharp/Interpreter/INodes/IComponent.cs b/CSharp/Interpreter/INodes/IComponent.cs
new file mode 100644
index 0000000..8e4efe8
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IComponent.cs
@@ -0,0 +1,6 @@
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IComponent : IHasAttributes
+ {
+ }
+}
diff --git a/CSharp/Interpreter/INodes/IConnection.cs b/CSharp/Interpreter/INodes/IConnection.cs
new file mode 100644
index 0000000..68d7517
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IConnection.cs
@@ -0,0 +1,20 @@
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IConnection
+ {
+ public string Id { get; }
+ public string RawLabel { get; }
+ public string RuntimeLabel { get; }
+
+ public INode Source { get; }
+ public INode Target { get; }
+
+ public Arcweave.Project.Project Project { get; }
+
+ public void Set(string label, INode source, INode target);
+
+ public void RunLabelScript();
+
+ public Arcweave.Project.Path ResolvePath(Arcweave.Project.Path p);
+ }
+}
diff --git a/CSharp/Interpreter/INodes/IElement.cs b/CSharp/Interpreter/INodes/IElement.cs
new file mode 100644
index 0000000..2e45d8a
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IElement.cs
@@ -0,0 +1,24 @@
+#if GODOT
+using Godot.Collections;
+#else
+using System.Collections.Generic;
+#endif
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IElement : INode, IHasAttributes
+ {
+ public int Visits { get; set; }
+
+ public string Title { get; }
+ public string RawContent { get; }
+
+#if GODOT
+ public Array Outputs { get; }
+#else
+ public List Outputs { get; }
+#endif
+ public void RunContentScript();
+
+ public Arcweave.Project.Options GetOptions();
+ }
+}
diff --git a/CSharp/Interpreter/INodes/IHasAttributes.cs b/CSharp/Interpreter/INodes/IHasAttributes.cs
new file mode 100644
index 0000000..2a06a67
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IHasAttributes.cs
@@ -0,0 +1,19 @@
+#if GODOT
+using Godot.Collections;
+#else
+using System.Collections.Generic;
+#endif
+
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IHasAttributes
+ {
+#if GODOT
+ Array Attributes { get; }
+#else
+ List Attributes { get; }
+#endif
+
+ public void AddAttribute(Arcweave.Project.Attribute attribute);
+ }
+}
diff --git a/CSharp/Interpreter/INodes/IHelperClasses.cs b/CSharp/Interpreter/INodes/IHelperClasses.cs
new file mode 100644
index 0000000..94ea149
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IHelperClasses.cs
@@ -0,0 +1,37 @@
+#if GODOT
+using Godot.Collections;
+#else
+using System.Collections.Generic;
+#endif
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IOptions
+ {
+ public Arcweave.Project.Element Element { get; set; }
+#if GODOT
+ public Array Paths { get; set; }
+#else
+ public List Paths { get; set; }
+#endif
+ public bool HasPaths => Paths != null;
+ public bool HasOptions => HasPaths && ( Paths.Count > 1 || !string.IsNullOrEmpty(Paths[0].label) );
+ }
+
+ public interface IPath
+ {
+ public string label { get; set; }
+ public Arcweave.Project.Element TargetElement { get; set; }
+#if GODOT
+ public Array _connections { get; set; }
+#else
+ public List _connections { get; set; }
+#endif
+ internal bool IsValid => TargetElement != null;
+
+ internal static IPath Invalid => default(IPath);
+
+ public void AppendConnection(Arcweave.Project.Connection connection);
+
+ public void ExecuteAppendedConnectionLabels();
+ }
+}
diff --git a/CSharp/Interpreter/INodes/INode.cs b/CSharp/Interpreter/INodes/INode.cs
new file mode 100644
index 0000000..d877dbd
--- /dev/null
+++ b/CSharp/Interpreter/INodes/INode.cs
@@ -0,0 +1,10 @@
+namespace Arcweave.Interpreter.INodes
+{
+ public partial interface INode
+ {
+ string Id { get; }
+ Arcweave.Project.Project Project { get; }
+
+ Arcweave.Project.Path ResolvePath(Arcweave.Project.Path path);
+ }
+}
diff --git a/CSharp/Interpreter/INodes/IProject.cs b/CSharp/Interpreter/INodes/IProject.cs
new file mode 100644
index 0000000..b732008
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IProject.cs
@@ -0,0 +1,20 @@
+#if GODOT
+using Godot.Collections;
+#else
+using System.Collections.Generic;
+#endif
+
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IProject
+ {
+#if GODOT
+ public Array Variables { get; }
+#else
+ public List Variables { get; }
+#endif
+ public Arcweave.Project.Element ElementWithId(string id);
+
+ public Arcweave.Project.Variable GetVariable(string name);
+ }
+}
diff --git a/CSharp/Interpreter/INodes/IVariable.cs b/CSharp/Interpreter/INodes/IVariable.cs
new file mode 100644
index 0000000..b6ec4d6
--- /dev/null
+++ b/CSharp/Interpreter/INodes/IVariable.cs
@@ -0,0 +1,23 @@
+#if GODOT
+using Godot;
+#endif
+
+namespace Arcweave.Interpreter.INodes
+{
+ public interface IVariable
+ {
+ public string Name { get; set; }
+#if GODOT
+ Variant Value { get; }
+ Variant DefaultValue { get; }
+#else
+ object Value { get; }
+ object DefaultValue { get; }
+#endif
+ public object ObjectValue { get; }
+
+ System.Type Type { get; }
+
+ public void ResetToDefaultValue();
+ }
+}
diff --git a/CSharp/Interpreter/Nodes.cs b/CSharp/Interpreter/Nodes.cs
new file mode 100644
index 0000000..a7a99a0
--- /dev/null
+++ b/CSharp/Interpreter/Nodes.cs
@@ -0,0 +1,31 @@
+#if GODOT
+using Godot;
+#endif
+
+using Arcweave.Interpreter.INodes;
+
+namespace Arcweave.Project
+{
+#if GODOT
+ public partial class Attribute : GodotObject, IAttribute { }
+ public partial class Board : GodotObject, IBoard { }
+ public partial class Component : GodotObject, IComponent { }
+ public partial class Connection : GodotObject, IConnection { }
+ public partial class Element : GodotObject, IElement { }
+ public partial class Project : GodotObject, IProject { }
+ public partial class Variable : GodotObject, IVariable { }
+ public partial class Options : GodotObject, IOptions { }
+ public partial class Path : GodotObject, IPath { }
+#else
+ public partial class Attribute : IAttribute { }
+ public partial class Board : IBoard { }
+ public partial class Component : IComponent { }
+ public partial class Connection : IConnection { }
+ public partial class Element : IElement { }
+ public partial class Project : IProject { }
+ public partial class Variable : IVariable { }
+ public partial class Options : IOptions { }
+ public partial class Path : IPath { }
+#endif
+}
+
diff --git a/CSharp/Project/Attribute.cs b/CSharp/Project/Attribute.cs
new file mode 100644
index 0000000..0857d12
--- /dev/null
+++ b/CSharp/Project/Attribute.cs
@@ -0,0 +1,11 @@
+using Arcweave.Interpreter.INodes;
+
+namespace Arcweave.Project;
+
+public partial class Attribute
+{
+ public string Name { get; }
+ public IAttribute.DataType Type { get; }
+ public IAttribute.ContainerType containerType { get; }
+ public string containerId { get; }
+}
\ No newline at end of file
diff --git a/CSharp/Project/Component.cs b/CSharp/Project/Component.cs
new file mode 100644
index 0000000..35d4a43
--- /dev/null
+++ b/CSharp/Project/Component.cs
@@ -0,0 +1,10 @@
+namespace Arcweave.Project;
+
+public partial class Component
+{
+ public List Attributes { get; }
+ public void AddAttribute(Attribute attribute)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Project/Connection.cs b/CSharp/Project/Connection.cs
new file mode 100644
index 0000000..24d9127
--- /dev/null
+++ b/CSharp/Project/Connection.cs
@@ -0,0 +1,27 @@
+using Arcweave.Interpreter.INodes;
+
+namespace Arcweave.Project;
+
+public partial class Connection
+{
+ public string Id { get; }
+ public string RawLabel { get; }
+ public string RuntimeLabel { get; }
+ public INode Source { get; }
+ public INode Target { get; }
+ public Project Project { get; }
+ public void Set(string label, INode source, INode target)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void RunLabelScript()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Path ResolvePath(Path p)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Project/Element.cs b/CSharp/Project/Element.cs
new file mode 100644
index 0000000..e25204b
--- /dev/null
+++ b/CSharp/Project/Element.cs
@@ -0,0 +1,32 @@
+namespace Arcweave.Project;
+
+public partial class Element
+{
+ public string Id { get; }
+ public Project Project { get; }
+
+ public Path ResolvePath(Path path)
+ {
+ throw new NotImplementedException();
+ }
+
+ public List Attributes { get; }
+ public void AddAttribute(Attribute attribute)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int Visits { get; set; }
+ public string Title { get; }
+ public string RawContent { get; }
+ public List Outputs { get; }
+ public void RunContentScript()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Options GetOptions()
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Project/HelperClasses.cs b/CSharp/Project/HelperClasses.cs
new file mode 100644
index 0000000..8356bc8
--- /dev/null
+++ b/CSharp/Project/HelperClasses.cs
@@ -0,0 +1,23 @@
+namespace Arcweave.Project;
+
+public partial class Options
+{
+ public Element Element { get; set; }
+ public List Paths { get; set; }
+}
+
+public partial class Path
+{
+ public string label { get; set; }
+ public Element TargetElement { get; set; }
+ public List _connections { get; set; }
+ public void AppendConnection(Connection connection)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ExecuteAppendedConnectionLabels()
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Project/Project.cs b/CSharp/Project/Project.cs
new file mode 100644
index 0000000..6a61f85
--- /dev/null
+++ b/CSharp/Project/Project.cs
@@ -0,0 +1,26 @@
+namespace Arcweave.Project;
+
+public partial class Project
+{
+ public List Variables { get; }
+ public Dictionary Elements;
+ public Project(List variables, Dictionary elements)
+ {
+ Variables = variables;
+ Elements = elements;
+ }
+ public Element ElementWithId(string id)
+ {
+ if (Elements.ContainsKey(id))
+ {
+ return Elements[id];
+ }
+
+ return null;
+ }
+
+ public Variable GetVariable(string name)
+ {
+ return Variables.FirstOrDefault(variable => variable.Name == name);
+ }
+}
\ No newline at end of file
diff --git a/CSharp/Project/Variable.cs b/CSharp/Project/Variable.cs
new file mode 100644
index 0000000..40455b4
--- /dev/null
+++ b/CSharp/Project/Variable.cs
@@ -0,0 +1,22 @@
+namespace Arcweave.Project;
+
+public partial class Variable
+{
+ public string Name { get; set; }
+ public object Value { get; set; }
+ public object ObjectValue => Value;
+ public Type Type { get; }
+ private object _defaultValue;
+ public object DefaultValue => _defaultValue;
+ public Variable(string name, object value)
+ {
+ Name = name;
+ Value = value;
+ _defaultValue = value;
+ Type = value.GetType();
+ }
+ public void ResetToDefaultValue()
+ {
+ Value = _defaultValue;
+ }
+}
\ No newline at end of file
diff --git a/CSharp/TestObjects.cs b/CSharp/TestObjects.cs
new file mode 100644
index 0000000..17736c3
--- /dev/null
+++ b/CSharp/TestObjects.cs
@@ -0,0 +1,26 @@
+namespace Arcweave;
+
+public struct TestVariable
+{
+ public string id;
+ public string name;
+ public string type;
+ public object value;
+}
+
+public struct TestCase
+{
+ public string code;
+ public Dictionary changes;
+ public string? output;
+ public string elementId;
+ public object result;
+ public string? error;
+ public Dictionary? visits;
+}
+
+public struct TestFile
+{
+ public Dictionary initialVars;
+ public List cases;
+}
\ No newline at end of file
diff --git a/CSharp/UnitTests.cs b/CSharp/UnitTests.cs
new file mode 100644
index 0000000..83dbb53
--- /dev/null
+++ b/CSharp/UnitTests.cs
@@ -0,0 +1,244 @@
+using NUnit.Framework;
+using Newtonsoft.Json;
+using Arcweave.Interpreter;
+using Arcweave.Project;
+
+namespace Arcweave;
+
+public class Tests
+{
+ struct TestData
+ {
+ public readonly Dictionary Variables;
+ public readonly List Cases;
+
+ public TestData(Dictionary variables, List cases)
+ {
+ Variables = variables;
+ Cases = cases;
+ }
+ }
+
+ private static IEnumerable GetValidTestData()
+ {
+ var tests = LoadJson("../../../__tests__/valid.json");
+
+ foreach (var testCase in tests.Cases)
+ {
+ yield return new TestCaseData(tests.Variables, testCase);
+ }
+ }
+
+ private static IEnumerable GetConditionsTestData()
+ {
+ var tests = LoadJson("../../../__tests__/conditions.json");
+
+ foreach (var validTestsCase in tests.Cases)
+ {
+ yield return new TestCaseData(tests.Variables, validTestsCase);
+ }
+ }
+
+ private static IEnumerable GetStringConcatTestData()
+ {
+ var tests = LoadJson("../../../__tests__/stringConcat.json");
+
+ foreach (var stringConcatTestCase in tests.Cases)
+ {
+ yield return new TestCaseData(tests.Variables, stringConcatTestCase);
+ }
+ }
+
+ private static IEnumerable GetRuntimeErrorTestData()
+ {
+ var tests = LoadJson("../../../__tests__/runtimeErrors.json");
+
+ foreach (var runtimeErrorTestCase in tests.Cases)
+ {
+ yield return new TestCaseData(tests.Variables, runtimeErrorTestCase);
+ }
+ }
+
+ private static IEnumerable GetParseErrorTestData()
+ {
+ var tests = LoadJson("../../../__tests__/parseErrors.json");
+
+ foreach (var parseErrorTestCase in tests.Cases)
+ {
+ yield return new TestCaseData(tests.Variables, parseErrorTestCase);
+ }
+ }
+
+ private static TestData LoadJson(string filePath)
+ {
+ using var r = new StreamReader(filePath);
+ var json = r.ReadToEnd();
+ var testFile = JsonConvert.DeserializeObject(json);
+ var variables = new Dictionary();
+
+ foreach (var variable in testFile.initialVars.Values)
+ {
+ 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);
+ }
+ }
+
+ return new TestData(variables, testFile.cases);
+ }
+
+ private static Dictionary? ChangesByName(Dictionary variables,
+ Dictionary? changes)
+ {
+ if (changes == null || changes.Count == 0)
+ {
+ return null;
+ }
+
+ var changesByName = new Dictionary();
+ foreach (var varChange in changes)
+ {
+ changesByName[variables[varChange.Key].Name] = varChange.Value;
+ }
+
+ return changesByName;
+ }
+
+ [Test]
+ [TestCaseSource(nameof(GetValidTestData))]
+ public void ValidTests(Dictionary variables, TestCase testCase)
+ {
+ var elements = new Dictionary();
+ if (testCase.visits != null)
+ {
+ foreach (var kvp in testCase.visits)
+ {
+ elements[kvp.Key] = new Element
+ {
+ Visits = kvp.Value
+ };
+ }
+ }
+
+ Console.WriteLine("Testing Case: " + testCase.code);
+ var project = new Project.Project(variables.Values.ToList(), elements);
+ 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));
+ }
+
+ var changesByName = ChangesByName(variables, testCase.changes);
+ if (changesByName != null)
+ {
+ Assert.That(output.Changes, Is.EqualTo(changesByName));
+ }
+ }
+
+ [Test]
+ [TestCaseSource(nameof(GetConditionsTestData))]
+ public void ConditionTests(Dictionary variables, TestCase testCase)
+ {
+ var elements = new Dictionary();
+ if (testCase.visits != null)
+ {
+ foreach (var kvp in testCase.visits)
+ {
+ elements[kvp.Key] = new Element
+ {
+ Visits = kvp.Value
+ };
+ }
+ }
+
+ Console.WriteLine("Testing Case: " + testCase.code);
+ var project = new Project.Project(variables.Values.ToList(), elements);
+ var i = new AwInterpreter(project, testCase.elementId);
+ var output = i.RunScript(testCase.code);
+
+ Assert.That(output.Result, Is.EqualTo(testCase.result));
+ }
+
+ [Test]
+ [TestCaseSource(nameof(GetStringConcatTestData))]
+ public void StringConcatTests(Dictionary variables, TestCase testCase)
+ {
+ var elements = new Dictionary();
+ if (testCase.visits != null)
+ {
+ foreach (var kvp in testCase.visits)
+ {
+ elements[kvp.Key] = new Element
+ {
+ Visits = kvp.Value
+ };
+ }
+ }
+
+ Console.WriteLine("Testing Case: " + testCase.code);
+ var project = new Project.Project(variables.Values.ToList(), elements);
+ 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));
+ }
+
+ var changesByName = ChangesByName(variables, testCase.changes);
+ if (changesByName != null)
+ {
+ Assert.That(output.Changes, Is.EqualTo(changesByName));
+ }
+ }
+
+ [Test]
+ [TestCaseSource(nameof(GetRuntimeErrorTestData))]
+ public void RuntimeErrorTests(Dictionary variables, TestCase testCase)
+ {
+ var Elements = new Dictionary();
+ if (testCase.visits != null)
+ {
+ foreach (var kvp in testCase.visits)
+ {
+ Elements[kvp.Key] = new Element
+ {
+ Visits = kvp.Value
+ };
+ }
+ }
+
+ Console.WriteLine("Testing Case: " + testCase.code);
+ var project = new Project.Project(variables.Values.ToList(), Elements);
+ var i = new AwInterpreter(project, testCase.elementId);
+ var ex = Assert.Throws(() => i.RunScript(testCase.code));
+ }
+
+ [Test]
+ [TestCaseSource(nameof(GetParseErrorTestData))]
+ public void ParseErrorTests(Dictionary variables, TestCase testCase)
+ {
+ var Elements = new Dictionary();
+ if (testCase.visits != null)
+ {
+ foreach (var kvp in testCase.visits)
+ {
+ Elements[kvp.Key] = new Element
+ {
+ Visits = kvp.Value
+ };
+ }
+ }
+
+ Console.WriteLine("Testing Case: " + testCase.code);
+ var project = new Project.Project(variables.Values.ToList(), Elements);
+ var i = new AwInterpreter(project, testCase.elementId);
+ var ex = Assert.Throws(() => i.RunScript(testCase.code));
+ }
+}
\ No newline at end of file
diff --git a/CSharp/__tests__/conditions.json b/CSharp/__tests__/conditions.json
new file mode 100644
index 0000000..88331f1
--- /dev/null
+++ b/CSharp/__tests__/conditions.json
@@ -0,0 +1,100 @@
+{
+ "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
+ }
+ },
+ "cases": [
+ {
+ "code": "x >= 14
",
+ "result": true
+ },
+ {
+ "code": "x == 14
",
+ "result": true
+ },
+ {
+ "code": "x is 14
",
+ "result": true
+ },
+ {
+ "code": "x == 45
",
+ "result": false
+ },
+ {
+ "code": "x is 45
",
+ "result": false
+ },
+ {
+ "code": "x == 14 && y == 15
",
+ "result": true
+ },
+ {
+ "code": "x == 14 and y == 15
",
+ "result": true
+ },
+ {
+ "code": "x == 3 || y != 0
",
+ "result": true
+ },
+ {
+ "code": "x == 3 or y != 0
",
+ "result": true
+ },
+ {
+ "code": "w == \"Dummy text\"
",
+ "result": true
+ },
+ {
+ "code": "w != \"Dummy text\"
",
+ "result": false
+ },
+ {
+ "code": "w is not \"Dummy text\"
",
+ "result": false
+ },
+ {
+ "code": "x is not \"Dummy text\"
",
+ "result": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CSharp/__tests__/parseErrors.json b/CSharp/__tests__/parseErrors.json
new file mode 100644
index 0000000..07d79d9
--- /dev/null
+++ b/CSharp/__tests__/parseErrors.json
@@ -0,0 +1,96 @@
+{
+ "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
+ }
+ },
+ "cases": [
+ {
+ "code": "p = 1
",
+ "error": "parse"
+ },
+ {
+ "code": "x = p
",
+ "error": "parse"
+ },
+ {
+ "code": "show(x, y, z, abc)
",
+ "error": "parse"
+ },
+ {
+ "code": "testFunction(x, y)
",
+ "error": "parse"
+ },
+ {
+ "code": "x = 1;
",
+ "error": "parse"
+ },
+ {
+ "code": "We want to be friends
",
+ "error": "parse"
+ },
+ {
+ "code": "x = sqr()
",
+ "error": "parse"
+ },
+ {
+ "code": "x = random(2)
",
+ "error": "parse"
+ },
+ {
+ "code": "y = 33
reset(13)
",
+ "error": "parse"
+ },
+ {
+ "code": "ελληνικά
",
+ "error": "parse"
+ },
+ {
+ "code": "visits(Untitled Comp) == 9
",
+ "error": "parse"
+ },
+ {
+ "code": "Test
if true
inside
endif
this
",
+ "error": "parse"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CSharp/__tests__/replaceVariables.json b/CSharp/__tests__/replaceVariables.json
new file mode 100644
index 0000000..4fc89b0
--- /dev/null
+++ b/CSharp/__tests__/replaceVariables.json
@@ -0,0 +1,69 @@
+{
+ "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
+ }
+ },
+ "cases": [
+ {
+ "code": "x = x + 2 * sqrt(x)
y = 5 + x
",
+ "variableChanges": {
+ "var1": "zed"
+ },
+ "result": "zed = zed + 2 * sqrt(zed)
y = 5 + zed
"
+ },
+ {
+ "code": "x = \"x marks the spot\"
xy = 5 + x
",
+ "variableChanges": {
+ "var1": "zed"
+ },
+ "result": "zed = \"x marks the spot\"
xy = 5 + zed
"
+ },
+ {
+ "code": "b = \"x marks the spot\"
xy = 5 + x
",
+ "variableChanges": {
+ "var1": "zed"
+ },
+ "result": "b = \"x marks the spot\"
xy = 5 + zed
"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CSharp/__tests__/runtimeErrors.json b/CSharp/__tests__/runtimeErrors.json
new file mode 100644
index 0000000..2f4924a
--- /dev/null
+++ b/CSharp/__tests__/runtimeErrors.json
@@ -0,0 +1,72 @@
+{
+ "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
+ }
+ },
+ "cases": [
+ {
+ "code": "x = sqrt(-3)
",
+ "error": "runtime"
+ },
+ {
+ "code": "x = 3 / 0
",
+ "error": "runtime"
+ },
+ {
+ "code": "x = roll(\"2\")
",
+ "error": "runtime"
+ },
+ {
+ "code": "w = \"2\" - \"test\"
",
+ "error": "runtime"
+ },
+ {
+ "code": "w = w - \"test\"
",
+ "error": "runtime"
+ },
+ {
+ "code": "w -= _a
",
+ "error": "runtime"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CSharp/__tests__/stringConcat.json b/CSharp/__tests__/stringConcat.json
new file mode 100644
index 0000000..d9b3503
--- /dev/null
+++ b/CSharp/__tests__/stringConcat.json
@@ -0,0 +1,132 @@
+{
+ "initialVars": {
+ "var1": {
+ "id": "var1",
+ "name": "x",
+ "type": "integer",
+ "value": 14
+ },
+ "var2": {
+ "id": "var2",
+ "name": "y",
+ "type": "integer",
+ "value": 15
+ },
+ "var3": {
+ "id": "var3",
+ "name": "z",
+ "type": "integer",
+ "value": 0
+ },
+ "var4": {
+ "id": "var4",
+ "name": "w",
+ "type": "string",
+ "value": "Dummy text"
+ },
+ "var5": {
+ "id": "var5",
+ "name": "$c5",
+ "type": "integer",
+ "value": 0
+ },
+ "var6": {
+ "id": "var6",
+ "name": "_a",
+ "type": "boolean",
+ "value": false
+ },
+ "var7": {
+ "id": "var7",
+ "name": "xy",
+ "type": "integer",
+ "value": -1
+ },
+ "var8": {
+ "id": "var8",
+ "name": "man",
+ "type": "string",
+ "value": "Different Text"
+ }
+ },
+ "cases": [
+ {
+ "code": "w = \"test\"
w = w + \"ing\"
",
+ "changes": {
+ "var4": "testing"
+ }
+ },
+ {
+ "code": "w = \"test\" + \"ing\"
",
+ "changes": {
+ "var4": "testing"
+ }
+ },
+ {
+ "code": "w = 0.42 + \"ing\"
",
+ "changes": {
+ "var4": "0.42ing"
+ }
+ },
+ {
+ "code": "w = 0.42 + \"\"
",
+ "changes": {
+ "var4": "0.42"
+ }
+ },
+ {
+ "code": "w = \"test\" + 44
",
+ "changes": {
+ "var4": "test44"
+ }
+ },
+ {
+ "code": "w = \"test\" + false
",
+ "changes": {
+ "var4": "testfalse"
+ }
+ },
+ {
+ "code": "w = \"test\" + true + true
",
+ "changes": {
+ "var4": "testtruetrue"
+ }
+ },
+ {
+ "code": "w = true + true + \"test\"
",
+ "changes": {
+ "var4": "2test"
+ }
+ },
+ {
+ "code": "w += \" test\"
",
+ "changes": {
+ "var4": "Dummy text test"
+ }
+ },
+ {
+ "code": "x += \"test\"
",
+ "changes": {
+ "var1": "14test"
+ }
+ },
+ {
+ "code": "w += 42
",
+ "changes": {
+ "var4": "Dummy text42"
+ }
+ },
+ {
+ "code": "w += _a
",
+ "changes": {
+ "var4": "Dummy textfalse"
+ }
+ },
+ {
+ "code": "w += man
",
+ "changes": {
+ "var4": "Dummy textDifferent Text"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CSharp/__tests__/valid.json b/CSharp/__tests__/valid.json
new file mode 100644
index 0000000..6df3bb8
--- /dev/null
+++ b/CSharp/__tests__/valid.json
@@ -0,0 +1,478 @@
+{
+ "initialVars": {
+ "var1": {
+ "id": "var1",
+ "name": "x",
+ "type": "integer",
+ "value": 14
+ },
+ "var2": {
+ "id": "var2",
+ "name": "y",
+ "type": "integer",
+ "value": 15
+ },
+ "var3": {
+ "id": "var3",
+ "name": "z",
+ "type": "integer",
+ "value": 0
+ },
+ "var4": {
+ "id": "var4",
+ "name": "w",
+ "type": "string",
+ "value": "Dummy text"
+ },
+ "var5": {
+ "id": "var5",
+ "name": "$c5",
+ "type": "integer",
+ "value": 0
+ },
+ "var6": {
+ "id": "var6",
+ "name": "_a",
+ "type": "boolean",
+ "value": false
+ },
+ "var7": {
+ "id": "var7",
+ "name": "xy",
+ "type": "integer",
+ "value": -1
+ },
+ "var8": {
+ "id": "var8",
+ "name": "man",
+ "type": "string",
+ "value": "Different Text"
+ }
+ },
+ "cases": [
+ {
+ "code": "x=5
",
+ "changes": {
+ "var1": 5
+ }
+ },
+ {
+ "code": "x=-5
",
+ "changes": {
+ "var1": -5
+ }
+ },
+ {
+ "code": "x=5.23
",
+ "changes": {
+ "var1": 5.23
+ }
+ },
+ {
+ "code": "x=3
y=2
",
+ "changes": {
+ "var1": 3,
+ "var2": 2
+ }
+ },
+ {
+ "code": "x=\"test\"
",
+ "changes": {
+ "var1": "test"
+ }
+ },
+ {
+ "code": "x=true
",
+ "changes": {
+ "var1": true
+ }
+ },
+ {
+ "code": "x = y
",
+ "changes": {
+ "var1": 15
+ }
+ },
+ {
+ "code": "x = 5 + 3
",
+ "changes": {
+ "var1": 8
+ }
+ },
+ {
+ "code": "x = 3 / 4 * 5
",
+ "changes": {
+ "var1": 3.75
+ }
+ },
+ {
+ "code": "x = 3 + 4 * 5
",
+ "changes": {
+ "var1": 23
+ }
+ },
+ {
+ "code": "x = 3 * 4 + 5
",
+ "changes": {
+ "var1": 17
+ }
+ },
+ {
+ "code": "x = 3 / 4 / 5
",
+ "changes": {
+ "var1": 0.15
+ }
+ },
+ {
+ "code": "x = sqr(2)
",
+ "changes": {
+ "var1": 4
+ }
+ },
+ {
+ "code": "x = 3 > 2
",
+ "changes": {
+ "var1": true
+ }
+ },
+ {
+ "code": "x = 3 >= 2
",
+ "changes": {
+ "var1": true
+ }
+ },
+ {
+ "code": "x = 3 < 2
",
+ "changes": {
+ "var1": false
+ }
+ },
+ {
+ "code": "x = 3 <= 2
",
+ "changes": {
+ "var1": false
+ }
+ },
+ {
+ "code": "x = 3 == 2
",
+ "changes": {
+ "var1": false
+ }
+ },
+ {
+ "code": "x = 3 != 2
",
+ "changes": {
+ "var1": true
+ }
+ },
+ {
+ "code": "_a = !(3 == 2)
",
+ "changes": {
+ "var6": true
+ }
+ },
+ {
+ "code": "_a = not (3 == 2)
",
+ "changes": {
+ "var6": true
+ }
+ },
+ {
+ "code": "x = !0
",
+ "changes": {
+ "var1": true
+ }
+ },
+ {
+ "code": "x = true || false
",
+ "changes": {
+ "var1": true
+ }
+ },
+ {
+ "code": "x = true && false
",
+ "changes": {
+ "var1": false
+ }
+ },
+ {
+ "code": "x = -3
y = y + 3
z = sqr(x)
",
+ "changes": {
+ "var1": -3,
+ "var2": 18,
+ "var3": 9
+ }
+ },
+ {
+ "code": "x = sqrt(y + 2)
",
+ "changes": {
+ "var1": 4.123105625617661
+ }
+ },
+ {
+ "code": "x = z + 12 == 4 * 3
",
+ "changes": {
+ "var1": true
+ }
+ },
+ {
+ "code": "x = y == 15 && !(z == 1)
",
+ "changes": {
+ "var1": true
+ }
+ },
+ {
+ "code": "x = (x*x + 3)/y
",
+ "changes": {
+ "var1": 13.266666666666667
+ }
+ },
+ {
+ "code": "x = x*(x + 1)/y
",
+ "changes": {
+ "var1": 14
+ }
+ },
+ {
+ "code": "x += 2
",
+ "changes": {
+ "var1": 16
+ }
+ },
+ {
+ "code": "x -= 2
",
+ "changes": {
+ "var1": 12
+ }
+ },
+ {
+ "code": "x *= 2
",
+ "changes": {
+ "var1": 28
+ }
+ },
+ {
+ "code": "x /= 2
",
+ "changes": {
+ "var1": 7
+ }
+ },
+ {
+ "code": "x = abs(-6)
",
+ "changes": {
+ "var1": 6
+ }
+ },
+ {
+ "code": "x = min(5, -y, 2.3)
",
+ "changes": {
+ "var1": -15
+ }
+ },
+ {
+ "code": "x = max(5, -y, 2.3)
",
+ "changes": {
+ "var1": 5
+ }
+ },
+ {
+ "code": "x = random()
",
+ "changes": {}
+ },
+ {
+ "code": "x = roll(6)
",
+ "changes": {}
+ },
+ {
+ "code": "x = roll(6, 5)
",
+ "changes": {}
+ },
+ {
+ "code": "x = roll(y)
",
+ "changes": {}
+ },
+ {
+ "code": "x = round(2.65)
",
+ "changes": {
+ "var1": 3
+ }
+ },
+ {
+ "code": "show(\"x is \", x)
",
+ "changes": {},
+ "output": "x is 14
"
+ },
+ {
+ "code": "y = 33
reset(y)
",
+ "changes": {
+ "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"
+ }
+ },
+ {
+ "code": "$c5 = x
",
+ "changes": {
+ "var5": 14
+ }
+ },
+ {
+ "code": "if (x==14)
x=5
endif
",
+ "changes": {
+ "var1": 5
+ }
+ },
+ {
+ "code": "if (x==11)
x=5
else
x=7
endif
",
+ "changes": {
+ "var1": 7
+ }
+ },
+ {
+ "code": "if x==3
x=5
elseif x==14
x=2
else
x = 0
endif
",
+ "changes": {
+ "var1": 2
+ }
+ },
+ {
+ "code": "Mein gott!
x = 1
What is that?
",
+ "changes": {
+ "var1": 1
+ },
+ "output": "Mein gott! What is that?
"
+ },
+ {
+ "code": "Hello my dear
if x == 1
We might ride to the mountains.
endif
",
+ "changes": {},
+ "output": "Hello my dear
"
+ },
+ {
+ "code": "You carry
if x == 12
a small knife
elseif y == 12
a large sword
elseif z == 1
just a rotten tomato
else
nothing really
endif
",
+ "changes": {},
+ "output": "You carry nothing really
"
+ },
+ {
+ "code": "How are you?
x = 1
I am fine, thank you.
",
+ "changes": {},
+ "output": "How are you?
I am fine, thank you.
"
+ },
+ {
+ "code": "show(\"Hello friends:\", x)
",
+ "changes": {},
+ "output": "Hello friends:14
"
+ },
+ {
+ "code": " _a = true
if (((_a)))
x = 1
endif
",
+ "changes": {
+ "var1": 1,
+ "var6": true
+ }
+ },
+ {
+ "code": "visits() == 1
",
+ "visits": {
+ "a": 1,
+ "b": 1,
+ "c": 0
+ },
+ "elementId": "b",
+ "result": true
+ },
+ {
+ "code": "visits(Untitled Comp) == 9
",
+ "visits": {
+ "a": 1,
+ "b": 1,
+ "c": 0
+ },
+ "elementId": "b",
+ "result": false
+ },
+ {
+ "code": "this
text
must
not
be
in
one
paragraph
",
+ "output": "this
text
must
not
be
in
one
paragraph
"
+ },
+ {
+ "code": "three
separate
lines
just
show(\"one\")
show(\"and\")
show(\"single\")
line
three
more
lines
",
+ "output": "three
separate
lines
just one and single line
three
more
lines
"
+ },
+ {
+ "code": "This is a blockquote
if x
This is a separate line
endif
This is another blockquote
",
+ "output": "This is a blockquote
This is a separate line
This is another blockquote
"
+ },
+ {
+ "code": "This is a blockquote
show(x)
test
",
+ "output": "This is a blockquote 14
test
"
+ },
+ {
+ "code": "This is a blockquote
show(x)
test
",
+ "output": "This is a blockquote 14 test
"
+ },
+ {
+ "code": "one
if true
two
endif
three
",
+ "output": "one
two
three
"
+ },
+ {
+ "code": "one
if true
two
endif
three
",
+ "output": "one two
three
"
+ },
+ {
+ "code": "one
if true
two
endif
three
",
+ "output": "one two
three
"
+ },
+ {
+ "code": "one
if true
show(x)
endif
three
",
+ "output": "one 14
three
"
+ },
+ {
+ "code": "one
if true
two
test
four
endif
three
",
+ "output": "one two
test
four
three
"
+ },
+ {
+ "code": "one
if true
two
test
show(\"four\")
endif
three
",
+ "output": "one two
test four
three
"
+ },
+ {
+ "code": "one
if false
two
endif
three
",
+ "output": "one
three
"
+ },
+ {
+ "code": "This is a blockquote
if true
Second blockquote
Separate Line
endif
Outside blockquote
",
+ "output": "This is a blockquote
Second blockquote
Separate Line
Outside blockquote
"
+ },
+ {
+ "code": "Test
_a = true
this
",
+ "output": "Test this
",
+ "changes": {
+ "var6": true
+ }
+ },
+ {
+ "code": "Test
show(x)
this
",
+ "output": "Test 14 this
"
+ },
+ {
+ "code": "this is a test
show(x)
continue
testing
",
+ "output": "this is a test
14 continue
testing
"
+ },
+ {
+ "code": "This is a test
Manolis
inside blockquotes
if true
John
endif
",
+ "output": "This is a test
Manolis
inside blockquotes
John
"
+ },
+ {
+ "code": "w = \"String with \\\"quotes\\\" \\t tabs and \\n new lines\"
show(w)
",
+ "output": "String with \"quotes\" \t tabs and \n new lines
"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/generate.ps1 b/generate.ps1
new file mode 100644
index 0000000..593c942
--- /dev/null
+++ b/generate.ps1
@@ -0,0 +1,33 @@
+# Download antlr4.jar if it doesn't exist
+if (-Not (Test-Path "./antlr4.jar")) {
+ Invoke-WebRequest -Uri "https://www.antlr.org/download/antlr-4.13.1-complete.jar" -OutFile "antlr4.jar"
+}
+
+Set-Location "grammar"
+
+# JavaScript generation
+java -Xmx500M -cp ../antlr4.jar org.antlr.v4.Tool -Dlanguage=JavaScript ArcscriptLexer.g4 ArcscriptParser.g4 -visitor -no-listener -o ./JavaScript
+New-Item -ItemType Directory -Force -Path ../JavaScript/src/Generated | Out-Null
+Copy-Item JavaScript\*.js ../JavaScript/src/Generated\
+Remove-Item -Recurse -Force ./JavaScript
+
+# Copy grammar files for Cpp
+Copy-Item Arcscript*.g4 ./Cpp/
+Set-Location "Cpp"
+python transformGrammar.py
+java -Xmx500M -cp ../../antlr4.jar org.antlr.v4.Tool -Dlanguage=Cpp ArcscriptLexer.g4 ArcscriptParser.g4 -visitor -no-listener -o ./Generated -package Arcweave
+New-Item -ItemType Directory -Force -Path ../../Cpp/src/Generated/ArcscriptLexer | Out-Null
+New-Item -ItemType Directory -Force -Path ../../Cpp/src/Generated/ArcscriptParser | Out-Null
+Copy-Item Generated\*Lexer*.cpp ../../Cpp/src/Generated/ArcscriptLexer
+Copy-Item Generated\*Lexer*.h ../../Cpp/src/Generated/ArcscriptLexer
+Copy-Item Generated\*Parser*.cpp ../../Cpp/src/Generated/ArcscriptParser
+Copy-Item Generated\*Parser*.h ../../Cpp/src/Generated/ArcscriptParser
+Remove-Item -Recurse -Force ./Generated
+Remove-Item *.g4,*.g4.bak -Force
+Set-Location ..
+
+# CSharp generation
+java -Xmx500M -cp ../antlr4.jar org.antlr.v4.Tool -Dlanguage=CSharp ArcscriptLexer.g4 ArcscriptParser.g4 -visitor -no-listener -o ./CSharp -package Arcweave.Interpreter
+New-Item -ItemType Directory -Force -Path ../CSharp/src/Generated | Out-Null
+Copy-Item CSharp\*.cs ../CSharp/src/Generated\
+Remove-Item -Recurse -Force ./CSharp
\ No newline at end of file
diff --git a/generate.sh b/generate.sh
index 7a66e68..5c253ec 100644
--- a/generate.sh
+++ b/generate.sh
@@ -23,6 +23,6 @@ rm -rf *.g4 *.g4.bak
cd ..
java -Xmx500M -cp ../antlr4.jar org.antlr.v4.Tool -Dlanguage=CSharp ArcscriptLexer.g4 ArcscriptParser.g4 -visitor -no-listener -o ./CSharp -package Arcweave.Interpreter
-mkdir -p ../CSharp/src/Generated
-cp CSharp/*.cs ../CSharp/src/Generated/.
+mkdir -p ../CSharp/Interpreter/Generated
+cp CSharp/*.cs ../CSharp/Interpreter/Generated/.
rm -rf ./CSharp
\ No newline at end of file