ソースを参照

Add standalone compiler

Bepsi 6 年 前
コミット
641a52b412

+ 50 - 0
NTERA.Compiler/App.config

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Diagnostics.Tracing" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.Extensions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.IO" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.3.0.0" newVersion="1.3.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Xml.ReaderWriter" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 301 - 0
NTERA.Compiler/Compiler.cs

@@ -0,0 +1,301 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NTERA.Interpreter;
+using NTERA.Interpreter.Compiler;
+
+namespace NTERA.Compiler
+{
+	public class Compiler
+	{
+		public string InputDirectory { get; }
+
+		public List<Tuple<ParserError, string>> Errors { get; } = new List<Tuple<ParserError, string>>();
+
+		public Dictionary<FunctionDefinition, string> DeclaredFunctions = new Dictionary<FunctionDefinition, string>();
+
+		public Compiler(string inputDirectory)
+		{
+			InputDirectory = inputDirectory;
+		}
+
+		public void Compile(string outputDirectory)
+		{
+			var globals = new VariableDictionary
+			{
+				["LOCAL"] = 0,
+				["LOCALS"] = "",
+				["ARG"] = "",
+				["ARGS"] = "",
+				["GLOBAL"] = "",
+				["GLOBALS"] = "",
+				["NICKNAME"] = "",
+				["MASTERNAME"] = "",
+				["CSTR"] = "",
+				["CUP"] = "",
+				["CDOWN"] = "",
+				["DOWNBASE"] = "",
+				["TCVAR"] = "",
+				["CDFLAG"] = "",
+				["ITEMPRICE"] = "",
+				["TRAINNAME"] = "",
+				["BASENAME"] = "",
+				["EQUIPNAME"] = "",
+				["TEQUIPNAME"] = "",
+				["STAINNAME"] = "",
+				["EXNAME"] = "",
+				["SOURCENAME"] = "",
+				["FLAGNAME"] = "",
+				["TFLAGNAME"] = "",
+				["CFLAGNAME"] = "",
+				["TCVARNAME"] = "",
+				["STRNAME"] = "",
+				["TSTRNAME"] = "",
+				["CSTRNAME"] = "",
+				["SAVESTRNAME"] = "",
+				["CDFLAGNAME"] = "",
+				["CDFLAGNAME"] = "",
+				["GLOBALNAME"] = "",
+				["GLOBALSNAME"] = "",
+				["GAMEBASE_AUTHOR"] = "",
+				["GAMEBASE_INFO"] = "",
+				["GAMEBASE_YEAR"] = "",
+				["GAMEBASE_TITLE"] = "",
+				["GAMEBASE_GAMECODE"] = "",
+				["GAMEBASE_VERSION"] = "",
+				["GAMEBASE_ALLOWVERSION"] = "",
+				["GAMEBASE_DEFAULTCHARA"] = "",
+				["GAMEBASE_NOITEM"] = "",
+				["WINDOW_TITLE"] = "",
+				["MONEYLABEL"] = "",
+				["DRAWLINESTR"] = "",
+				["LASTLOAD_VERSION"] = "",
+				["LASTLOAD_NO"] = "",
+				["LASTLOAD_TEXT"] = "",
+				["SAVEDATA_TEXT"] = "",
+				["TSTR"] = "",
+				["RANDDATA"] = "",
+				["LINECOUNT"] = "",
+				["ISTIMEOUT"] = "",
+				["__INT_MAX__"] = "",
+				["__INT_MIN__"] = "",
+				["NAME"] = "",
+				["CALLNAME"] = "",
+				["RAND"] = "",
+				["CHARANUM"] = "",
+				["TALENT"] = "",
+				["FLAG"] = "",
+				["TFLAG"] = "",
+				["CFLAG"] = "",
+				["MASTER"] = "",
+				["BASE"] = "",
+				["MAXBASE"] = "",
+				["SOURCE"] = "",
+				["ABL"] = "",
+				["PALAM"] = "",
+				["TEQUIP"] = "",
+				["EQUIP"] = "",
+				["DAY"] = "",
+				["MARK"] = "",
+				["PALAMLV"] = "",
+				["TARGET"] = "",
+				["PLAYER"] = "",
+				["NOWEX"] = "",
+				["EX"] = "",
+				["EXNAME"] = "",
+				["STAIN"] = "",
+				["EXP"] = "",
+				["ASSIPLAY"] = "",
+				["ASSI"] = "",
+				["ITEM"] = "",
+				["EXPLV"] = "",
+				["TIME"] = "",
+				["MONEY"] = "",
+				["SELECTCOM"] = "",
+				["JUEL"] = "",
+				["STR"] = "",
+				["PREVCOM"] = "",
+				["SAVESTR"] = "",
+				["ABLNAME"] = "",
+				["MARKNAME"] = "",
+				["TALENTNAME"] = "",
+				["ITEMNAME"] = "",
+				["PALAMNAME"] = "",
+				["EXPNAME"] = "",
+				["DITEMTYPE"] = "",
+				["NO"] = "",
+				["RELATION"] = "",
+				["IS"] = ""
+			};
+
+			foreach (char c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+				globals[c.ToString()] = "";
+
+			var funcs = new List<FunctionDefinition>
+			{
+				new FunctionDefinition("RAND", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("TOSTR", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0], "") }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("STRCOUNT", new[] { new FunctionParameter("input", new string[0]), new FunctionParameter("match", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("MIN", new[] { new FunctionParameter("a", new string[0], isArrayParameter: true), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("MAX", new[] { new FunctionParameter("a", new string[0], isArrayParameter: true), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("POWER", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("GETPALAMLV", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("GETBIT", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("UNICODE", new[] { new FunctionParameter("a", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("MATCH", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]), new FunctionParameter("c", new string[0], "a"), new FunctionParameter("d", new string[0], "f"), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("INRANGE", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]), new FunctionParameter("c", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("HE_SHE", new[] { new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("SUBSTRING", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("STRLENS", new[] { new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("CSVNAME", new[] { new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("CSVNAME", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("CSVTALENT", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("CSVCSTR", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("GETNUM", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("FINDCHARA", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("LIMIT", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("GETTIME", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("SAVENOS", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("GETCOLOR", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+				new FunctionDefinition("CURRENTREDRAW", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()),
+			};
+
+			var stringStatements = new List<string>
+			{
+				"DRAWLINEFORM",
+				"PRINTFORML",
+				"PRINT",
+				"PRINTW",
+				"PRINTV",
+				"PRINTL",
+				"PRINTLC",
+				"PRINTC",
+				"ALIGNMENT",
+				"CALL",
+				"CUSTOMDRAWLINE",
+				"DATAFORM",
+				"GOTO",
+				"PRINTFORMDL",
+				"PRINTFORMW",
+				"PRINTFORMDW",
+				"PRINTFORMC",
+				"PRINTFORMLC",
+				"PRINTFORM",
+				"PRINTPLAINFORM",
+				"DEBUGPRINTL",
+				"DEBUGPRINTFORML",
+				"REUSELASTLINE",
+				"PRINTPLAIN",
+				"PRINT_TRAIN_NAME",
+				"PRINT_STR",
+			};
+
+			string csvPath = Path.Combine(InputDirectory, "CSV");
+			string erbPath = Path.Combine(InputDirectory, "ERB");
+
+			var csvDefinition = new CSVDefinition();
+
+
+			foreach (var file in Directory.EnumerateFiles(csvPath, "*.csv", SearchOption.AllDirectories))
+			{
+				string path = Path.GetFileNameWithoutExtension(file);
+
+				if (path.EndsWith("_TR"))
+					continue;
+
+				Preprocessor.ProcessCSV(csvDefinition, path, File.ReadLines(file, Encoding.UTF8));
+			}
+
+			ConcurrentDictionary<FunctionDefinition, string> preprocessedFunctions = new ConcurrentDictionary<FunctionDefinition, string>();
+
+#if DEBUG
+			foreach (var file in Directory.EnumerateFiles(erbPath, "*.erb", SearchOption.AllDirectories))
+#else
+			Parallel.ForEach(Directory.EnumerateFiles(erbPath, "*.erb", SearchOption.AllDirectories), new ParallelOptions
+			{
+				MaxDegreeOfParallelism = 4
+			}, file =>
+#endif
+			{
+				try
+				{
+					foreach (var kv in Preprocessor.PreprocessFile(File.ReadAllText(file), Path.GetFileName(file)))
+						preprocessedFunctions[kv.Key] = kv.Value;
+				}
+				catch (Exception ex)
+				{
+					lock (Errors)
+						Errors.Add(new Tuple<ParserError, string>(new ParserError($"Internal pre-process lexer error [{ex}]", new Marker()), Path.GetFileName(file)));
+				}
+#if DEBUG
+			}
+#else
+			});
+#endif
+
+			DeclaredFunctions = preprocessedFunctions.ToDictionary(kv => kv.Key, kv => kv.Value);
+
+			funcs.AddRange(DeclaredFunctions.Keys.Where(x => x.IsReturnFunction));
+			var procedures = DeclaredFunctions.Keys.Where(x => !x.IsReturnFunction).ToList();
+
+
+#if DEBUG
+			foreach (var kv in DeclaredFunctions)
+#else
+			Parallel.ForEach(DeclaredFunctions, new ParallelOptions
+			{
+				MaxDegreeOfParallelism = 4
+			}, kv =>
+#endif
+			{
+				try
+				{
+					var locals = new VariableDictionary();
+					foreach (var param in kv.Key.Variables)
+						locals[param.Name] = param.DefaultValue ?? param.ValueType == Interpreter.ValueType.String ? new Value("") : new Value(0d);
+
+					Parser parser = new Parser(kv.Value, kv.Key, funcs, procedures, globals, locals, stringStatements, csvDefinition);
+
+					var nodes = parser.Parse(out var localErrors);
+
+					lock (Errors)
+						Errors.AddRange(localErrors.Select(x => new Tuple<ParserError, string>(x, $"{kv.Key.Filename} - {kv.Key.Name}")));
+				}
+				catch (Exception ex)
+				{
+					lock (Errors)
+						Errors.Add(new Tuple<ParserError, string>(new ParserError($"Internal post-process lexer error [{ex}]", new Marker()), $"{kv.Key.Filename} - {kv.Key.Name}"));
+				}
+#if DEBUG
+			}
+#else
+			});
+#endif
+
+			long fileSize = 0;
+			int fileCount = 0;
+
+			foreach (var file in Directory.EnumerateFiles(InputDirectory, "*.erb", SearchOption.AllDirectories))
+			{
+				var info = new FileInfo(file);
+				fileSize += info.Length;
+				fileCount++;
+			}
+
+			var htmlWriter = new HTMLWriter
+			{
+				Errors = Errors.OrderBy(x => x.Item2).ToList(),
+				FunctionCount = DeclaredFunctions.Count,
+				TotalFileSize = fileSize,
+				TotalFileCount = fileCount
+			};
+
+			using (var stream = File.Create(Path.Combine(outputDirectory, "report.html")))
+				htmlWriter.WriteReportToFile(stream);
+		}
+	}
+}

+ 4 - 0
NTERA.Compiler/FodyWeavers.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Weavers>
+  <Costura />
+</Weavers>

+ 74 - 0
NTERA.Compiler/HTMLWriter.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using NTERA.Interpreter.Compiler;
+
+namespace NTERA.Compiler
+{
+	public class HTMLWriter
+	{
+		protected virtual string ReportHeader { get; } =
+			@"<!DOCTYPE html>
+
+<html lang=""en"" xmlns=""http://www.w3.org/1999/xhtml"">
+	<head>
+		<meta charset=""utf-8""/>
+		<title></title>
+	</head>
+	<body style=""font-family: sans-serif"">";
+
+		protected virtual string ReportFooter { get; } =
+			@"	</body>
+</html>";
+
+		public long TotalFileSize { get; set; }
+		public int TotalFileCount { get; set; }
+		public int FunctionCount { get; set; }
+
+		public IList<Tuple<ParserError, string>> Errors { get; set; }
+
+		public void WriteReportToFile(Stream outputStream)
+		{
+			using (StreamWriter writer = new StreamWriter(outputStream, Encoding.UTF8, 4096, true))
+			{
+				writer.WriteLine(ReportHeader);
+
+				EmitSummary(writer);
+
+				writer.Write(ReportFooter);
+			}
+		}
+
+		protected virtual void EmitSummary(StreamWriter streamWriter)
+		{
+			string fileSizeStr = (TotalFileSize / 1048576D).ToString("0.0");
+
+			streamWriter.WriteLine($@"		<h3>NTERA Compilation report</h3>
+		<hr />
+		<p>NTERA v0.X</p>
+		<p>Processed {fileSizeStr} MB in {TotalFileCount} files<br />
+			Processed {FunctionCount} functions
+		</p>
+		<p>{Errors.Count} errors</p>
+		<table style=""height: 98px;"" width=""100%"">
+			<tbody>");
+
+			foreach (var error in Errors)
+			{
+				EmitError(streamWriter, error.Item1, error.Item2);
+			}
+			
+			streamWriter.WriteLine($@"			</tbody>
+		</table>");
+		}
+
+		protected virtual void EmitError(StreamWriter streamWriter, ParserError error, string message)
+		{
+			streamWriter.WriteLine($@"					<tr>
+						<td style=""width: 30.6667%;"">{message}</td>
+						<td style=""width: 66.3333%;"">{error.SymbolMarker} : {error.ErrorMessage}</td>
+					</tr>");
+		}
+	}
+}

+ 76 - 0
NTERA.Compiler/NTERA.Compiler.csproj

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\Costura.Fody.3.1.6\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.3.1.6\build\Costura.Fody.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{3E7BF6CC-3227-4C84-8D85-7ADA597288C1}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>NTERA.Compiler</RootNamespace>
+    <AssemblyName>NTERA.Compiler</AssemblyName>
+    <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Costura, Version=3.1.6.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
+      <HintPath>..\packages\Costura.Fody.3.1.6\lib\net46\Costura.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Compiler.cs" />
+    <Compile Include="HTMLWriter.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\NTERA.Interpreter\NTERA.Interpreter.csproj">
+      <Project>{f3b58ef3-e3ff-4b76-92b8-2ab87663fc4d}</Project>
+      <Name>NTERA.Interpreter</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="FodyWeavers.xml">
+      <SubType>Designer</SubType>
+    </Content>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Costura.Fody.3.1.6\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.3.1.6\build\Costura.Fody.props'))" />
+    <Error Condition="!Exists('..\packages\Fody.3.2.16\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.3.2.16\build\Fody.targets'))" />
+  </Target>
+  <Import Project="..\packages\Fody.3.2.16\build\Fody.targets" Condition="Exists('..\packages\Fody.3.2.16\build\Fody.targets')" />
+</Project>

+ 35 - 0
NTERA.Compiler/Program.cs

@@ -0,0 +1,35 @@
+using System;
+using System.IO;
+
+namespace NTERA.Compiler
+{
+	class Program
+	{
+		static void Main(string[] args)
+		{
+			string path = args.Length > 0
+				? Path.GetFullPath(args[0])
+				: Environment.CurrentDirectory;
+
+			string outputPath = Path.Combine(path, "output");
+
+			if (!Directory.Exists(outputPath))
+				Directory.CreateDirectory(outputPath);
+
+			Console.WriteLine("NTERA 0.X");
+			Console.WriteLine("-------------------------");
+			Console.WriteLine($"Compiling '{path}' to '{outputPath}'");
+			Console.WriteLine("Using 4 threads");
+			
+
+
+			Compiler compiler = new Compiler(path);
+			compiler.Compile(outputPath);
+
+			Console.WriteLine();
+			Console.WriteLine($"{compiler.DeclaredFunctions.Count} total functions");
+			Console.WriteLine($"{compiler.Errors.Count} errors");
+			Console.WriteLine("Report written");
+		}
+	}
+}

+ 36 - 0
NTERA.Compiler/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("NTERA.Compiler")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("NTERA.Compiler")]
+[assembly: AssemblyCopyright("Copyright ©  2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("3e7bf6cc-3227-4c84-8d85-7ada597288c1")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 5 - 0
NTERA.Compiler/packages.config

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Costura.Fody" version="3.1.6" targetFramework="net462" />
+  <package id="Fody" version="3.2.16" targetFramework="net462" developmentDependency="true" />
+</packages>

+ 34 - 50
NTERA.Interpreter/Compiler/Lexer.cs

@@ -72,52 +72,58 @@ namespace NTERA.Interpreter.Compiler
 			return currentChar;
 		}
 
-		private static Dictionary<string, Token> TokenDictionary;
+		protected static Dictionary<string, Token> TokenDictionary;
 
-		private static Dictionary<string, Token> TokenLineDictionary;
+		protected Dictionary<char, Token> TokenCharDictionary;
 
-		private Dictionary<char, Token> TokenCharDictionary;
+		protected static Dictionary<char, Token> BothModeTokens;
+		protected static Dictionary<char, Token> StringModeTokens;
 
-		private static Dictionary<char, Token> BothModeTokens;
-		private static Dictionary<char, Token> StringModeTokens;
+		private static bool _initialized = false;
+		private static readonly object _initializedLock = new object();
 
 		private void InitTokenDictionaries()
 		{
-			if (TokenDictionary == null || TokenLineDictionary == null)
+			if (_initialized)
+				return;
+
+			lock (_initializedLock)
 			{
-				TokenDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
-				TokenLineDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
+				if (_initialized)
+					return;
 
-				foreach (Token token in Enum.GetValues(typeof(Token)))
+				if (TokenDictionary == null)
 				{
-					foreach (var attribute in Utility.GetEnumAttributes<Token, LexerKeywordAttribute>(token))
+					TokenDictionary = new Dictionary<string, Token>(StringComparer.InvariantCultureIgnoreCase);
+
+					foreach (Token token in Enum.GetValues(typeof(Token)))
 					{
-						if (attribute.IsLineKeyword)
-							TokenLineDictionary[attribute.Keyword] = token;
-						else
+						foreach (var attribute in Utility.GetEnumAttributes<Token, LexerKeywordAttribute>(token))
+						{
 							TokenDictionary[attribute.Keyword] = token;
+						}
 					}
 				}
-			}
-
-			if (BothModeTokens == null || StringModeTokens == null)
-			{
-				BothModeTokens = new Dictionary<char, Token>();
-				StringModeTokens = new Dictionary<char, Token>();
 
-				foreach (Token token in Enum.GetValues(typeof(Token)))
+				if (BothModeTokens == null || StringModeTokens == null)
 				{
-					foreach (var attribute in Utility.GetEnumAttributes<Token, LexerCharacterAttribute>(token))
+					BothModeTokens = new Dictionary<char, Token>();
+					StringModeTokens = new Dictionary<char, Token>();
+
+					foreach (Token token in Enum.GetValues(typeof(Token)))
 					{
-						if ((attribute.LexerContext & LexerType.String) > 0)
-							StringModeTokens[attribute.Character] = token;
+						foreach (var attribute in Utility.GetEnumAttributes<Token, LexerCharacterAttribute>(token))
+						{
+							if ((attribute.LexerContext & LexerType.String) > 0)
+								StringModeTokens[attribute.Character] = token;
 
-						BothModeTokens[attribute.Character] = token;
+							BothModeTokens[attribute.Character] = token;
+						}
 					}
 				}
-			}
 
-			TokenCharDictionary = Type == LexerType.String ? StringModeTokens : BothModeTokens;
+				TokenCharDictionary = Type == LexerType.String ? StringModeTokens : BothModeTokens;
+			}
 		}
 
 		private static Regex PowRegex = new Regex(@"(\d+)p(\d+)");
@@ -281,7 +287,7 @@ namespace NTERA.Interpreter.Compiler
 						goto case '"';
 					}
 
-					return Token.Function;
+					return Token.AtSymbol;
 
 				case '"':
 
@@ -408,28 +414,6 @@ namespace NTERA.Interpreter.Compiler
 					continue;
 				}
 
-				if (TokenLineDictionary.TryGetValue(Identifer, out token))
-				{
-					bodyBuilder = new StringBuilder();
-
-					while (!IsEndOfLine(GetNextChar(true)))
-						bodyBuilder.Append(GetNextChar());
-
-					yield return token;
-
-					string strValue = bodyBuilder.ToString();
-
-					if (strValue.Length > 0 && char.IsWhiteSpace(strValue[0]))
-						strValue = strValue.Substring(1);
-
-					Value = new Value(strValue);
-					yield return Token.Value;
-
-					yield return currentChar == '\0' ? Token.EOF : Token.NewLine;
-
-					continue;
-				}
-
 				yield return Token.Identifer;
 
 				if (currentChar == '\n')
@@ -456,7 +440,7 @@ namespace NTERA.Interpreter.Compiler
 			{ Token.Asterisk, 3 }, { Token.Slash, 3 },
 			{ Token.Caret, 4 }
 		};
-
+		
 		public Value Expression()
 		{
 			Stack<Value> stack = new Stack<Value>();

+ 20 - 10
NTERA.Interpreter/Compiler/Parser.cs

@@ -270,6 +270,7 @@ namespace NTERA.Interpreter.Compiler
 							}
 
 							if (Enumerator.Current != Token.Comma
+								&& Enumerator.Current != Token.RParen
 								&& Enumerator.Current != Token.NewLine
 								&& Enumerator.Current != Token.EOF)
 							{
@@ -278,6 +279,17 @@ namespace NTERA.Interpreter.Compiler
 							}
 						}
 
+						if (Enumerator.Current == Token.RParen)
+							Enumerator.MoveNext();
+
+
+						if (Enumerator.Current != Token.NewLine
+							&& Enumerator.Current != Token.EOF)
+						{
+							error = new ParserError($"Unexpected token: {Enumerator.Current}", CurrentPosition);
+							return null;
+						}
+
 						return CallMethod(target, symbolMarker, parameters.ToArray());
 					}
 					else if (Lexer.Identifer == "CALLFORM"
@@ -432,7 +444,7 @@ namespace NTERA.Interpreter.Compiler
 						return node;
 					}
 
-				case Token.Function:
+				case Token.AtSymbol:
 				case Token.Sharp:
 					while (Enumerator.MoveNext()
 						   && Enumerator.Current != Token.NewLine
@@ -548,7 +560,6 @@ namespace NTERA.Interpreter.Compiler
 		protected ExecutionNode GetFunction(out ParserError error)
 		{
 			error = null;
-			Token token;
 			Marker symbolMarker = CurrentPosition;
 			List<ExecutionNode> parameters = new List<ExecutionNode>();
 
@@ -560,10 +571,12 @@ namespace NTERA.Interpreter.Compiler
 				return null;
 			}
 
-			while ((token = GetNextToken(true)) == Token.Identifer
-				   || token == Token.Value
-				   || token.IsUnary())
+			while (Enumerator.Current == Token.Comma
+				   || Enumerator.Current == Token.LParen)
 			{
+				if (GetNextToken(true) == Token.RParen)
+					break;
+
 				parameters.Add(Expression(out error));
 				if (error != null)
 					return null;
@@ -574,9 +587,6 @@ namespace NTERA.Interpreter.Compiler
 					error = new ParserError($"Unexpected token: {Enumerator.Current}", CurrentPosition);
 					return null;
 				}
-
-				if (Enumerator.Current == Token.RParen)
-					break;
 			}
 
 			if (Enumerator.Current != Token.RParen)
@@ -783,7 +793,7 @@ namespace NTERA.Interpreter.Compiler
 		{
 			error = null;
 			ExecutionNode value = null;
-
+			
 			Lexer.Type = LexerType.String;
 
 			while (Enumerator.MoveNext()
@@ -823,7 +833,7 @@ namespace NTERA.Interpreter.Compiler
 					Lexer.Type = LexerType.String;
 				}
 			}
-
+			
 			Lexer.Type = LexerType.Both;
 
 			return value;

+ 1 - 1
NTERA.Interpreter/Compiler/Preprocessor.cs

@@ -47,7 +47,7 @@ namespace NTERA.Interpreter.Compiler
 					if (lexer.TokenMarker.Column != 1)
 						continue;
 
-					if (enumerator.Current == Token.Function)
+					if (enumerator.Current == Token.AtSymbol)
 					{
 						Commit();
 

+ 3 - 3
NTERA.Interpreter/Compiler/Token.cs

@@ -10,8 +10,8 @@
 		[LexerCharacter('#', LexerType.Real)]
 		Sharp,
 
-		//[LexerCharacter('@', LexerType.Real)]
-		Function,
+		[LexerCharacter('@', LexerType.Real)]
+		AtSymbol,
 
 		[LexerKeyword("DIM")]
 		Dim,
@@ -40,7 +40,7 @@
 
 		[LexerCharacter(',', LexerType.Real)]
 		Comma,
-
+		
 		[LexerCharacter('{', LexerType.Both)]
 		OpenBracket,
 

+ 6 - 0
NTERA.sln

@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTERA.Interpreter", "NTERA.
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTERA.Interpreter.Tests", "NTERA.Interpreter.Tests\NTERA.Interpreter.Tests.csproj", "{F55AC007-A763-4869-9529-4324CF28A9DE}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NTERA.Compiler", "NTERA.Compiler\NTERA.Compiler.csproj", "{3E7BF6CC-3227-4C84-8D85-7ADA597288C1}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -39,6 +41,10 @@ Global
 		{F55AC007-A763-4869-9529-4324CF28A9DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F55AC007-A763-4869-9529-4324CF28A9DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F55AC007-A763-4869-9529-4324CF28A9DE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3E7BF6CC-3227-4C84-8D85-7ADA597288C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3E7BF6CC-3227-4C84-8D85-7ADA597288C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3E7BF6CC-3227-4C84-8D85-7ADA597288C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3E7BF6CC-3227-4C84-8D85-7ADA597288C1}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 2 - 2
NTERA/formMain.cs

@@ -16,9 +16,9 @@ namespace NTERA
 			InitializeComponent();
 
             //var scriptEngine = new EmuEraGameInstance();
-            var scriptEngine = new Engine();
+            //var scriptEngine = new Engine();
 
-            Task.Factory.StartNew(() => instance.Run(new EraConsoleInstance(consoleControl1.Renderer, scriptEngine), scriptEngine));
+            //Task.Factory.StartNew(() => instance.Run(new EraConsoleInstance(consoleControl1.Renderer, scriptEngine), scriptEngine));
 		}
 
 		private void txtInput_KeyDown(object sender, KeyEventArgs e)