diff --git a/src/WattleScript.Interpreter/DataTypes/Module.cs b/src/WattleScript.Interpreter/DataTypes/Module.cs new file mode 100644 index 00000000..0188cb44 --- /dev/null +++ b/src/WattleScript.Interpreter/DataTypes/Module.cs @@ -0,0 +1,46 @@ +using System.IO; + +namespace WattleScript.Interpreter +{ + public class Module + { + public static Module LocalModule = new Module(true); + + /// + /// Gets/sets the Wattlescript source code of this module + /// + public string Code { get; set; } + + /// + /// Gets/sets the Wattlescript bytecode of this module. If not null, this has priority over + /// + public byte[] Bytecode { get; set; } + + /// + /// Gets/sets the Stream with Wattlescript bytecode of this module. If not null, this has priority over + /// + public Stream Stream { get; set; } + + internal bool IsEntryRef { get; set; } + + internal Module(bool isEntryRef) + { + IsEntryRef = isEntryRef; + } + + public Module(string code) + { + Code = code; + } + + public Module(byte[] bytecode) + { + Bytecode = bytecode; + } + + public Module(Stream stream) + { + Stream = stream; + } + } +} \ No newline at end of file diff --git a/src/WattleScript.Interpreter/Execution/InstructionFieldUsage.cs b/src/WattleScript.Interpreter/Execution/InstructionFieldUsage.cs index 495616ca..c3d61593 100644 --- a/src/WattleScript.Interpreter/Execution/InstructionFieldUsage.cs +++ b/src/WattleScript.Interpreter/Execution/InstructionFieldUsage.cs @@ -98,6 +98,7 @@ internal static InstructionFieldUsage GetFieldUsage(this OpCode op) case OpCode.PushInt: case OpCode.BaseChk: case OpCode.SetMetaTab: + case OpCode.PrepNmspc: return InstructionFieldUsage.NumVal; case OpCode.Call: case OpCode.ThisCall: diff --git a/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScope.cs b/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScope.cs index c1236eeb..b4f21301 100644 --- a/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScope.cs +++ b/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScope.cs @@ -24,6 +24,11 @@ public void SetHasVarArgs() m_Frames.Last().HasVarArgs = true; } + public void PushBlock(string nmspc) + { + m_Frames.Last().PushBlock(nmspc); + } + public void PushBlock() { m_Frames.Last().PushBlock(); diff --git a/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScopeBlock.cs b/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScopeBlock.cs index d6a5318f..7ff6148a 100644 --- a/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScopeBlock.cs +++ b/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScopeBlock.cs @@ -10,8 +10,10 @@ internal class BuildTimeScopeBlock internal BuildTimeScopeBlock Parent { get; private set; } internal List ChildNodes { get; private set; } internal RuntimeScopeBlock ScopeBlock { get; private set; } + internal string Namespace { get; private set; } + Dictionary m_DefinedNames = new Dictionary(); - + internal void Rename(string name) { SymbolRef sref = m_DefinedNames[name]; @@ -19,17 +21,17 @@ internal void Rename(string name) m_DefinedNames.Add(string.Format("@{0}_{1}", name, Guid.NewGuid().ToString("N")), sref); } - internal BuildTimeScopeBlock(BuildTimeScopeBlock parent) + internal BuildTimeScopeBlock(BuildTimeScopeBlock parent, string nmspc = "") { Parent = parent; ChildNodes = new List(); ScopeBlock = new RuntimeScopeBlock(); + Namespace = nmspc; } - - internal BuildTimeScopeBlock AddChild() + internal BuildTimeScopeBlock AddChild(string nmspc = "") { - BuildTimeScopeBlock block = new BuildTimeScopeBlock(this); + BuildTimeScopeBlock block = new BuildTimeScopeBlock(this, nmspc); ChildNodes.Add(block); return block; } diff --git a/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScopeFrame.cs b/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScopeFrame.cs index 637965b1..68102d18 100644 --- a/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScopeFrame.cs +++ b/src/WattleScript.Interpreter/Execution/Scopes/BuildTimeScopeFrame.cs @@ -19,6 +19,11 @@ internal BuildTimeScopeFrame(bool isConstructor) m_ScopeTreeHead = m_ScopeTreeRoot = new BuildTimeScopeBlock(null); } + internal void PushBlock(string nmspc) + { + m_ScopeTreeHead = m_ScopeTreeHead.AddChild(nmspc); + } + internal void PushBlock() { m_ScopeTreeHead = m_ScopeTreeHead.AddChild(); diff --git a/src/WattleScript.Interpreter/Execution/ScriptLoadingContext.cs b/src/WattleScript.Interpreter/Execution/ScriptLoadingContext.cs index 237cf078..97c826c2 100644 --- a/src/WattleScript.Interpreter/Execution/ScriptLoadingContext.cs +++ b/src/WattleScript.Interpreter/Execution/ScriptLoadingContext.cs @@ -14,6 +14,7 @@ class ScriptLoadingContext public Lexer Lexer { get; set; } public ScriptSyntax Syntax { get; set; } + public Linker Linker { get; set; } //Compiler state internal List ChunkAnnotations { get; set; } = new List(); diff --git a/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs b/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs index fa43129a..1cbbb039 100755 --- a/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs +++ b/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs @@ -8,6 +8,7 @@ using System.Text; using System.Xml.Schema; using WattleScript.Interpreter.Debugging; +using WattleScript.Interpreter.Tree; namespace WattleScript.Interpreter.Execution.VM { @@ -421,6 +422,24 @@ public int Emit_MixInit(string mixinName) return AppendInstruction(new Instruction(OpCode.MixInit, StringArg(mixinName))); } + public int Emit_PrepNmspc(List namespaceComponents) + { + if (namespaceComponents == null) + { + throw new InternalErrorException("List of namespace components cannot be null"); + } + + int parts = 0; + + foreach (Token token in namespaceComponents.Where(x => x.Type == TokenType.Name)) + { + parts++; + Emit_IndexSet(0, 0, token.Text, true); + } + + return AppendInstruction(new Instruction(OpCode.PrepNmspc, parts)); + } + public int Emit_NewCall(int argCount, string className) { return AppendInstruction(new Instruction(OpCode.NewCall, argCount) {NumValB = (uint) StringArg(className)}); diff --git a/src/WattleScript.Interpreter/Execution/VM/OpCode.cs b/src/WattleScript.Interpreter/Execution/VM/OpCode.cs index eb19a0f1..e23291ea 100644 --- a/src/WattleScript.Interpreter/Execution/VM/OpCode.cs +++ b/src/WattleScript.Interpreter/Execution/VM/OpCode.cs @@ -118,20 +118,21 @@ internal enum OpCode // OOP // AnnotX instructions, add annotation to table //NumValB = annotation name string - AnnotI, //NumVal = int - AnnotN, //NumVal = number - AnnotS, //NumVal = string or nil - AnnotB, //NumVal = bool - AnnotT, //pop table from v-stack - LoopChk, //Checks if local in NumVal is < threshold. If not, throw error using NumValB as the class name - BaseChk, //Checks if v-stack top is a class. If not, throw error using NumVal as the base class name - NewCall, //Calls the new() function stored in table at v-stack offset NumVal with NumVal arguments. - //Throws error using class name in NumValB if type check fails - MixInit, //Checks type of mixin on v-stack top, stores init to v-stack + 1, adds functions to v-stack + 2, pops top - //Error check uses NumVal for mixin name - SetFlags, //Sets WattleFieldsInfo for NumVal keys, using modifier in NumVal2 (pops NumVal Items) - MergeFlags, //Merges the WattleFieldsInfo of v-stack(NumVal) into v-stack(NumVal2) - CopyFlags, //Copies the WattleFieldsInfo of v-stack top into v-stack +1, pops 1 value + AnnotI, // NumVal = int + AnnotN, // NumVal = number + AnnotS, // NumVal = string or nil + AnnotB, // NumVal = bool + AnnotT, // pop table from v-stack + LoopChk, // Checks if local in NumVal is < threshold. If not, throw error using NumValB as the class name + BaseChk, // Checks if v-stack top is a class. If not, throw error using NumVal as the base class name + NewCall, // Calls the new() function stored in table at v-stack offset NumVal with NumVal arguments. + // Throws error using class name in NumValB if type check fails + MixInit, // Checks type of mixin on v-stack top, stores init to v-stack + 1, adds functions to v-stack + 2, pops top + // Error check uses NumVal for mixin name + SetFlags, // Sets WattleFieldsInfo for NumVal keys, using modifier in NumVal2 (pops NumVal Items) + MergeFlags, // Merges the WattleFieldsInfo of v-stack(NumVal) into v-stack(NumVal2) + CopyFlags, // Copies the WattleFieldsInfo of v-stack top into v-stack +1, pops 1 value + PrepNmspc, // Pop next NumVal values from v-stack (namespace components), next [todo] NewCall/../.. will use this for namespace resolution. This should cover static class access eg. val = myNamespace.myStaticClass.staticField or myNamespace.myStaticClass.staticMethod() // Meta Invalid, // Crashes the executor with an unrecoverable NotImplementedException. This MUST always be the last opcode in enum } diff --git a/src/WattleScript.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs b/src/WattleScript.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs index b8f0f468..64d69489 100644 --- a/src/WattleScript.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs +++ b/src/WattleScript.Interpreter/Execution/VM/Processor/Processor_InstructionLoop.cs @@ -410,6 +410,9 @@ private DynValue Processing_Loop(int instructionPtr, bool canAwait = false) } break; } + case OpCode.PrepNmspc: + ExecPrepNamespace(i); + break; case OpCode.Invalid: throw new NotImplementedException($"Invalid opcode {i.OpCode}"); default: @@ -661,6 +664,14 @@ private void ExecMergeFlags(Instruction i) dest.Table.Members.Merge(src.Table.Members); } } + + private void ExecPrepNamespace(Instruction i) + { + for (int j = 0; j < i.NumVal; j++) + { + DynValue component = m_ValueStack.Pop(); + } + } private void ExecIterPrep() { diff --git a/src/WattleScript.Interpreter/Loaders/IScriptLoader.cs b/src/WattleScript.Interpreter/Loaders/IScriptLoader.cs index 7df1e932..d1321222 100644 --- a/src/WattleScript.Interpreter/Loaders/IScriptLoader.cs +++ b/src/WattleScript.Interpreter/Loaders/IScriptLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace WattleScript.Interpreter.Loaders { @@ -36,5 +37,10 @@ public interface IScriptLoader /// The global context. /// string ResolveModuleName(string modname, Table globalContext); + /// + /// Resolves the name of a module imported by "using" statement + /// + /// + public Func UsingResolver { get; set; } } } diff --git a/src/WattleScript.Interpreter/Loaders/InvalidScriptLoader.cs b/src/WattleScript.Interpreter/Loaders/InvalidScriptLoader.cs index a1ac4d76..3a0f88a1 100644 --- a/src/WattleScript.Interpreter/Loaders/InvalidScriptLoader.cs +++ b/src/WattleScript.Interpreter/Loaders/InvalidScriptLoader.cs @@ -15,6 +15,8 @@ internal InvalidScriptLoader(string frameworkname) @"Loading scripts from files is not automatically supported on {0}. Please implement your own IScriptLoader (possibly, extending ScriptLoaderBase for easier implementation), use a preexisting loader like EmbeddedResourcesScriptLoader or UnityAssetsScriptLoader or load scripts from strings.", frameworkname); + + UsingResolver = s => throw new PlatformNotSupportedException(m_Error); } public object LoadFile(string file, Table globalContext) @@ -31,5 +33,7 @@ public string ResolveModuleName(string modname, Table globalContext) { throw new PlatformNotSupportedException(m_Error); } + + public Func UsingResolver { get; set; } } } diff --git a/src/WattleScript.Interpreter/Loaders/ScriptLoaderBase.cs b/src/WattleScript.Interpreter/Loaders/ScriptLoaderBase.cs index 176c753c..52e287f4 100644 --- a/src/WattleScript.Interpreter/Loaders/ScriptLoaderBase.cs +++ b/src/WattleScript.Interpreter/Loaders/ScriptLoaderBase.cs @@ -75,6 +75,12 @@ public virtual string ResolveModuleName(string modname, Table globalContext) return ResolveModuleName(modname, this.ModulePaths); } + /// + /// Resolves the name of a module imported by "using" statement + /// + /// + public Func UsingResolver { get; set; } + /// /// Gets or sets the modules paths used by the "require" function. If null, the default paths are used (using /// environment variables etc.). diff --git a/src/WattleScript.Interpreter/Script.cs b/src/WattleScript.Interpreter/Script.cs index a311d02c..ab563cea 100755 --- a/src/WattleScript.Interpreter/Script.cs +++ b/src/WattleScript.Interpreter/Script.cs @@ -109,7 +109,7 @@ public Script() /// The core modules to be pre-registered in the default global table. public Script(CoreModules coreModules) { - Options = new ScriptOptions(DefaultOptions); + Options = new ScriptOptions(DefaultOptions) { CoreModules = coreModules }; PerformanceStats = new PerformanceStatistics(); Registry = new Table(this); @@ -117,7 +117,6 @@ public Script(CoreModules coreModules) m_GlobalTable = new Table(this).RegisterCoreModules(coreModules); } - /// /// Gets or sets the script loader which will be used as the value of the /// ScriptLoader property for all newly created scripts. diff --git a/src/WattleScript.Interpreter/ScriptOptions.cs b/src/WattleScript.Interpreter/ScriptOptions.cs index 2536b7a6..3ec26d44 100644 --- a/src/WattleScript.Interpreter/ScriptOptions.cs +++ b/src/WattleScript.Interpreter/ScriptOptions.cs @@ -152,5 +152,10 @@ public enum ParserErrorModes /// If set to 0, this property is ignored (no limit is applied). /// public ulong InstructionLimit { get; set; } = 0; + + /// + /// Gets/sets CoreModules used in script. + /// + internal CoreModules CoreModules { get; set; } } } diff --git a/src/WattleScript.Interpreter/Tree/Expressions/NewExpression.cs b/src/WattleScript.Interpreter/Tree/Expressions/NewExpression.cs index f80c5fb8..2c475964 100644 --- a/src/WattleScript.Interpreter/Tree/Expressions/NewExpression.cs +++ b/src/WattleScript.Interpreter/Tree/Expressions/NewExpression.cs @@ -13,21 +13,30 @@ class NewExpression : Expression private SymbolRefExpression classRef; private List arguments; private string className; + private List namespaceQualifier = null; + private bool referencesNamespace => namespaceQualifier != null; public NewExpression(ScriptLoadingContext lcontext) : base(lcontext) { lcontext.Lexer.Next(); //lexer at "new" token var classTok = CheckTokenType(lcontext, TokenType.Name); - className = classTok.Text; - CheckTokenType(lcontext, TokenType.Brk_Open_Round); - if (lcontext.Lexer.Current.Type == TokenType.Brk_Close_Round) - { - arguments = new List(); - } - else + + if (lcontext.Lexer.Current.Type == TokenType.Dot) // possible namespace qualifier { - arguments = ExprList(lcontext); + namespaceQualifier = ParseNamespace(lcontext, true); + namespaceQualifier.Insert(0, classTok); + + if (namespaceQualifier.Count < 2) // at least ident-dot + { + throw new SyntaxErrorException(namespaceQualifier[namespaceQualifier.Count - 1], $"Unexpected token '{namespaceQualifier[namespaceQualifier.Count - 1].Text}' while parsing namespace in 'new' expresson"); + } + + classTok = CheckTokenType(lcontext, TokenType.Name); } + + className = classTok.Text; + CheckTokenType(lcontext, TokenType.Brk_Open_Round); + arguments = lcontext.Lexer.Current.Type == TokenType.Brk_Close_Round ? new List() : ExprList(lcontext); var end = CheckTokenType(lcontext, TokenType.Brk_Close_Round); SourceRef = classTok.GetSourceRef(end); } @@ -38,6 +47,13 @@ public override void Compile(FunctionBuilder bc) classRef.Compile(bc); foreach(var a in arguments) a.CompilePossibleLiteral(bc); + + if (referencesNamespace) + { + // [todo] update indexing for fully qualified access + // bc.Emit_PrepNmspc(namespaceQualifier); + } + bc.Emit_NewCall(arguments.Count, className); bc.PopSourceRef(); } diff --git a/src/WattleScript.Interpreter/Tree/Fast_Interface/Loader_Fast.cs b/src/WattleScript.Interpreter/Tree/Fast_Interface/Loader_Fast.cs index e0123c35..6bca833b 100644 --- a/src/WattleScript.Interpreter/Tree/Fast_Interface/Loader_Fast.cs +++ b/src/WattleScript.Interpreter/Tree/Fast_Interface/Loader_Fast.cs @@ -36,42 +36,53 @@ internal static DynamicExprExpression LoadDynamicExpr(Script script, SourceCode } } - private static ScriptLoadingContext CreateLoadingContext(Script script, SourceCode source, - string preprocessedCode = null, - Dictionary defines = null) + internal static ScriptLoadingContext CreateLoadingContext(Script script, SourceCode source, string preprocessedCode = null, Dictionary defines = null, bool lexerAutoSkipComments = true, bool lexerKeepInsignificantChars = false, Linker staticImport = null) { return new ScriptLoadingContext(script) { Source = source, - Lexer = new Lexer(source.SourceID, preprocessedCode ?? source.Code, true, script.Options.Syntax, script.Options.Directives, defines), - Syntax = script.Options.Syntax + Lexer = new Lexer(source.SourceID, preprocessedCode ?? source.Code, lexerAutoSkipComments, script.Options.Syntax, script.Options.Directives, defines, lexerKeepInsignificantChars), + Syntax = script.Options.Syntax, + Linker = staticImport }; } - internal static FunctionProto LoadChunk(Script script, SourceCode source) + internal static FunctionProto LoadChunk(Script script, SourceCode source, Linker staticImport = null) { #if !DEBUG_PARSER try { #endif - ScriptLoadingContext lcontext; ChunkStatement stat; using (script.PerformanceStats.StartStopwatch(Diagnostics.PerformanceCounter.AstCreation)) { + ScriptLoadingContext lcontext; + bool staticImportIsNull = staticImport == null; + if (script.Options.Syntax == ScriptSyntax.Wattle) { - var preprocess = new Preprocessor(script, source.SourceID, source.Code); + Preprocessor preprocess = new Preprocessor(script, source.SourceID, source.Code); preprocess.Process(); - lcontext = CreateLoadingContext(script, source, preprocess.ProcessedSource, - preprocess.Defines); + + staticImport ??= new Linker(script, source.SourceID, preprocess.ProcessedSource, preprocess.Defines); + staticImport.Process(); + + lcontext = CreateLoadingContext(script, source, preprocess.ProcessedSource, preprocess.Defines, staticImport: staticImport); } else { lcontext = CreateLoadingContext(script, source); } + stat = new ChunkStatement(lcontext); + + if (script.Options.Syntax == ScriptSyntax.Wattle && staticImportIsNull) + { + stat.Block.InsertStatements(staticImport?.Export()); + } + lcontext.Scope = new BuildTimeScope(); stat.ResolveScope(lcontext); } diff --git a/src/WattleScript.Interpreter/Tree/IStaticallyImportableStatement.cs b/src/WattleScript.Interpreter/Tree/IStaticallyImportableStatement.cs new file mode 100644 index 00000000..440d7a47 --- /dev/null +++ b/src/WattleScript.Interpreter/Tree/IStaticallyImportableStatement.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using WattleScript.Interpreter.Execution; +using WattleScript.Interpreter.Tree.Expressions; +using WattleScript.Interpreter.Tree.Statements; + +namespace WattleScript.Interpreter.Tree +{ + interface IStaticallyImportableStatement + { + public Token NameToken { get; } + public string DefinitionType { get; } + public string Namespace { get; } + } +} diff --git a/src/WattleScript.Interpreter/Tree/Lexer/Lexer.cs b/src/WattleScript.Interpreter/Tree/Lexer/Lexer.cs index 631655a3..ea10fd5e 100755 --- a/src/WattleScript.Interpreter/Tree/Lexer/Lexer.cs +++ b/src/WattleScript.Interpreter/Tree/Lexer/Lexer.cs @@ -20,11 +20,19 @@ class Lexer private ScriptSyntax m_Syntax; private HashSet m_Directives; private Dictionary m_Defines; + private bool keepInsignificantChars; + private StringBuilder whitespaceStringBuilder; - public Lexer(int sourceID, string scriptContent, bool autoSkipComments, ScriptSyntax syntax, HashSet directives, Dictionary defines) + public Lexer(int sourceID, string scriptContent, bool autoSkipComments, ScriptSyntax syntax, HashSet directives, Dictionary defines, bool keepInsignificantChars = false) { m_Code = scriptContent; m_SourceId = sourceID; + this.keepInsignificantChars = keepInsignificantChars; + + if (keepInsignificantChars) + { + whitespaceStringBuilder = new StringBuilder(); + } // remove unicode BOM if any if (m_Code.Length > 0 && m_Code[0] == 0xFEFF) @@ -210,6 +218,11 @@ private char CursorCharNext() return CursorChar(); } + private char CursorCharPeekNext() + { + return m_Cursor + 1 < m_Code.Length ? m_Code[m_Cursor + 1] : '\0'; + } + private bool CursorMatches(string pattern) { for (int i = 0; i < pattern.Length; i++) @@ -233,6 +246,18 @@ private bool IsWhiteSpace(char c) { return char.IsWhiteSpace(c); } + + private bool IsWhiteSpace(char c, StringBuilder sb) + { + bool isWs = char.IsWhiteSpace(c); + + if (isWs) + { + sb.Append(c); + } + + return isWs; + } private void SkipWhiteSpace() { @@ -240,6 +265,15 @@ private void SkipWhiteSpace() { } } + + private void StashWhiteSpace() + { + whitespaceStringBuilder.Clear(); + + for (; CursorNotEof() && IsWhiteSpace(CursorChar(), whitespaceStringBuilder); CursorNext()) + { + } + } void ProcessLineDirective(string directive, int line, int col) { @@ -273,7 +307,14 @@ void ProcessLineDirective(string directive, int line, int col) private Token ReadToken() { - SkipWhiteSpace(); + if (keepInsignificantChars) + { + StashWhiteSpace(); + } + else + { + SkipWhiteSpace(); + } int fromLine = m_Line; int fromCol = m_Col; @@ -574,7 +615,7 @@ private Token ReadToken() case '#' when m_Syntax == ScriptSyntax.Wattle: if (m_Cursor == 0 && m_Code.Length > 1 && m_Code[1] == '!') return ReadHashBang(fromLine, fromCol); - else if (m_StartOfLine && CursorMatches("#line")) + else if (CursorMatches("#line")) // [todo] fix { //Read in line CursorNext(); @@ -990,7 +1031,7 @@ private Token ReadComment(int fromLine, int fromCol) text.Append(c); } } - + return CreateToken(TokenType.Comment, fromLine, fromCol, text.ToString()); } diff --git a/src/WattleScript.Interpreter/Tree/Lexer/Token.cs b/src/WattleScript.Interpreter/Tree/Lexer/Token.cs index c71a804f..c558b6ee 100644 --- a/src/WattleScript.Interpreter/Tree/Lexer/Token.cs +++ b/src/WattleScript.Interpreter/Tree/Lexer/Token.cs @@ -71,6 +71,10 @@ public override string ToString() return TokenType.Public; case "sealed": return TokenType.Sealed; + case "using": + return TokenType.Using; + case "namespace": + return TokenType.Namespace; } } diff --git a/src/WattleScript.Interpreter/Tree/Lexer/TokenType.cs b/src/WattleScript.Interpreter/Tree/Lexer/TokenType.cs index 8caadc2e..6a03076a 100644 --- a/src/WattleScript.Interpreter/Tree/Lexer/TokenType.cs +++ b/src/WattleScript.Interpreter/Tree/Lexer/TokenType.cs @@ -126,6 +126,9 @@ enum TokenType Private, Sealed, + Using, + Namespace, + Preprocessor_Defined //Reserved only in preprocessor } diff --git a/src/WattleScript.Interpreter/Tree/Linker/Linker.cs b/src/WattleScript.Interpreter/Tree/Linker/Linker.cs new file mode 100644 index 00000000..78a67d20 --- /dev/null +++ b/src/WattleScript.Interpreter/Tree/Linker/Linker.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using WattleScript.Interpreter.Debugging; +using WattleScript.Interpreter.Execution; +using WattleScript.Interpreter.Tree.Fast_Interface; +using WattleScript.Interpreter.Tree.Statements; +using static WattleScript.Interpreter.Tree.NodeBase; + +namespace WattleScript.Interpreter.Tree +{ + class LinkerException : Exception + { + public LinkerException(string message) : base(message) { } + } + + internal class Linker + { + public string CurrentNamespace { get; set; } = ""; + + private Script script; + private int sourceIndex; + private bool outputChars = true; + private ScriptLoadingContext lcontextLocal; + private bool firstUsingEncountered = false; + private bool anyNonUsingEncounterd = false; + private string text; + private StringBuilder usingIdent = new StringBuilder(); + private string lastNamespace; + + internal class StatementInfo + { + public IStaticallyImportableStatement Statement { get; set; } + public bool Compiled { get; set; } + } + + //public string ProcessedSource => output.ToString(); + public Dictionary ResolvedUsings = new Dictionary(); + public Dictionary> ImportMap = new Dictionary>(); + + + public Linker(Script script, int sourceIndex, string text, Dictionary defines = null) + { + this.script = script; + this.sourceIndex = sourceIndex; + this.text = text; + + lcontextLocal = Loader_Fast.CreateLoadingContext(script, script.GetSourceCode(sourceIndex), text, defines, false, true, this); + } + + public void StoreNamespace() + { + lastNamespace = CurrentNamespace; + } + + public void RestoreNamespace() + { + CurrentNamespace = lastNamespace; + } + + void ProcessNamespaceStatement(ScriptLoadingContext lcontext) + { + CheckTokenType(lcontext, TokenType.Namespace); + bool canBeDot = false; + List namespaceTokens = ParseNamespace(lcontext, false, true); + + string namespaceIdentStr = string.Join(string.Empty, namespaceTokens.Select(x => x.Text)).ToString(); + lcontext.Linker.CurrentNamespace = namespaceIdentStr; + + if (lcontext.Lexer.PeekNext().Type == TokenType.Brk_Open_Curly) + { + lcontext.Lexer.Next(); + CheckTokenType(lcontext, TokenType.Brk_Open_Curly); + Loop(lcontext, true); + } + } + + void ProcessUsingStatement(ScriptLoadingContext lcontext) + { + CheckTokenType(lcontext, TokenType.Using); + int currentLineFrom = lcontext.Lexer.Current.FromLine; + bool canBeDot = false; + int charTo = 0; + Token prev = null; + usingIdent.Clear(); + + while (lcontext.Lexer.PeekNext().Type != TokenType.Eof) + { + Token tkn = lcontext.Lexer.Current; + + if (tkn.FromLine != currentLineFrom) + { + break; + } + + prev = lcontext.Lexer.Current; + + canBeDot = canBeDot switch + { + false when tkn.Type != TokenType.Name => throw new SyntaxErrorException(tkn, $"unexpected token '{tkn.Text}' found in using statement"), + true when tkn.Type != TokenType.Dot => throw new SyntaxErrorException(tkn, $"unexpected token '{tkn.Text}' found in using statement"), + _ => !canBeDot + }; + + usingIdent.Append(tkn.Text); + lcontext.Lexer.Next(); + } + + string usingIdentStr = usingIdent.ToString(); + + if (ResolvedUsings.ContainsKey(usingIdentStr)) + { + throw new SyntaxErrorException(prev, $"duplicate using '{usingIdentStr}' found"); + } + + Module resolvedModule = script.Options.ScriptLoader.UsingResolver(usingIdentStr); + + if (resolvedModule == null) + { + throw new LinkerException($"module '{usingIdentStr}' failed to resolve"); + } + + if (resolvedModule.IsEntryRef) + { + return; + } + + ResolvedUsings.Add(usingIdentStr, resolvedModule); + Process(usingIdentStr, resolvedModule.Code); + } + + void EnlistNamespace(string nmspc) + { + ImportMap.GetOrCreate(nmspc, () => new Dictionary()); + } + + void EnlistMember(IStaticallyImportableStatement statement) + { + string nmspc = statement.Namespace ?? ""; + + EnlistNamespace(nmspc); + + if (ImportMap[nmspc].TryGetValue(statement.NameToken.Text, out _)) + { + throw new SyntaxErrorException(statement.NameToken, $"Member '{statement.NameToken.Text}' is already defined as {statement.DefinitionType}"); + } + + ImportMap[nmspc].Add(statement.NameToken.Text, new StatementInfo() {Compiled = false, Statement = statement}); + } + + public List Export() + { + List statements = new List(); + + foreach (KeyValuePair> nmspc in ImportMap) + { + foreach (KeyValuePair member in nmspc.Value) + { + statements.Add((Statement) member.Value.Statement); + } + } + + return statements; + } + + void Process(string nmspc, string code) + { + Script tmp = new Script(script.Options.CoreModules); + SourceCode source = new SourceCode($"linker - {nmspc}", code, 0, tmp); + Preprocessor preprocess = new Preprocessor(tmp, source.SourceID, source.Code); + preprocess.Process(); + + ScriptLoadingContext lcontextLib = Loader_Fast.CreateLoadingContext(script, source, preprocess.ProcessedSource, staticImport: this); + + Process(lcontextLib); + } + + void Loop(ScriptLoadingContext lcontext, bool breakOnNextBlockEnd = false) + { + while (lcontext.Lexer.PeekNext().Type != TokenType.Eof) + { + lcontext.Lexer.Next(); + afterUsingStatement: + Token tkn = lcontext.Lexer.Current; + + switch (tkn.Type) + { + case TokenType.Brk_Close_Curly when breakOnNextBlockEnd: + break; + case TokenType.Namespace: + anyNonUsingEncounterd = true; + ProcessNamespaceStatement(lcontext); + break; + case TokenType.Using: + firstUsingEncountered = true; + ProcessUsingStatement(lcontext); + goto afterUsingStatement; + case TokenType.Enum: + anyNonUsingEncounterd = true; + EnlistMember(new EnumDefinitionStatement(lcontext)); + goto afterUsingStatement; + case TokenType.Class: + anyNonUsingEncounterd = true; + EnlistMember(new ClassDefinitionStatement(lcontext)); + goto afterUsingStatement; + case TokenType.Mixin: + anyNonUsingEncounterd = true; + EnlistMember(new MixinDefinitionStatement(lcontext)); + goto afterUsingStatement; + default: + anyNonUsingEncounterd = true; + break; + } + } + } + + void Process(ScriptLoadingContext lcontext) + { + if (!text.Contains("using")) // heuristic, will trip on comments, variable names, etc. but might be worth it + { + // PushToOutput(text); + // return; + } + + Loop(lcontext); + int z = 0; + } + + public void Link(string nmspc) + { + if (ResolvedUsings.ContainsKey(nmspc)) + { + return; + } + + Module resolvedModule = script.Options.ScriptLoader.UsingResolver(nmspc); + + if (resolvedModule == null) + { + throw new LinkerException($"module '{nmspc}' failed to resolve"); + } + + if (resolvedModule.IsEntryRef) + { + return; + } + + ResolvedUsings.Add(nmspc, resolvedModule); + Process(nmspc, resolvedModule.Code); + } + + public void Process() + { + Process(lcontextLocal); + } + } +} \ No newline at end of file diff --git a/src/WattleScript.Interpreter/Tree/NodeBase.cs b/src/WattleScript.Interpreter/Tree/NodeBase.cs index 0d69869f..b770cb6b 100644 --- a/src/WattleScript.Interpreter/Tree/NodeBase.cs +++ b/src/WattleScript.Interpreter/Tree/NodeBase.cs @@ -1,4 +1,6 @@ -using WattleScript.Interpreter.Execution; +using System.Collections.Generic; +using System.Linq; +using WattleScript.Interpreter.Execution; using WattleScript.Interpreter.Execution.VM; namespace WattleScript.Interpreter.Tree @@ -25,7 +27,59 @@ internal static Token UnexpectedTokenType(Token t) }; } - protected static Token CheckTokenTypeEx(ScriptLoadingContext lcontext, TokenType tokenType1, TokenType tokenType2) + internal static void ParseSemicolons(ScriptLoadingContext lcontext) + { + while (lcontext.Lexer.PeekNext().Type == TokenType.SemiColon) + { + lcontext.Lexer.Next(); + } + } + + /// + /// Parses a sequence of tokens in form of (name->dot->name->..) + /// + /// Current ScriptLoadingContext + /// Whether the first token should be "name" or "dot" + /// + /// A list of tokens representing the qualifier + internal static List ParseNamespace(ScriptLoadingContext lcontext, bool currentTokenShouldBeDot, bool includeLastToken = false) + { + List tokens = new List(); + + while (lcontext.Lexer.PeekNext().Type != TokenType.Eof) + { + if (currentTokenShouldBeDot && lcontext.Lexer.PeekNext().Type != TokenType.Name) + { + if (includeLastToken) + { + tokens.Add(lcontext.Lexer.Current); + } + break; + } + + if (!currentTokenShouldBeDot && lcontext.Lexer.PeekNext().Type != TokenType.Dot) + { + if (includeLastToken) + { + tokens.Add(lcontext.Lexer.Current); + } + break; + } + + currentTokenShouldBeDot = !currentTokenShouldBeDot; + tokens.Add(lcontext.Lexer.Current); + lcontext.Lexer.Next(); + } + + if (tokens.Last().Type == TokenType.Dot) + { + tokens.Remove(tokens.Last()); + } + + return tokens; + } + + internal static Token CheckTokenTypeEx(ScriptLoadingContext lcontext, TokenType tokenType1, TokenType tokenType2) { if (lcontext.Syntax != ScriptSyntax.Lua) { @@ -40,7 +94,7 @@ protected static Token CheckTokenTypeEx(ScriptLoadingContext lcontext, TokenType return CheckTokenType(lcontext, tokenType1); } - protected static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType tokenType) + internal static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType tokenType) { Token t = lcontext.Lexer.Current; if (t.Type != tokenType) @@ -53,7 +107,7 @@ protected static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType t - protected static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType tokenType1, TokenType tokenType2) + internal static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType tokenType1, TokenType tokenType2) { Token t = lcontext.Lexer.Current; if (t.Type != tokenType1 && t.Type != tokenType2) @@ -63,7 +117,7 @@ protected static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType t return t; } - protected static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType tokenType1, TokenType tokenType2, TokenType tokenType3) + internal static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType tokenType1, TokenType tokenType2, TokenType tokenType3) { Token t = lcontext.Lexer.Current; if (t.Type != tokenType1 && t.Type != tokenType2 && t.Type != tokenType3) @@ -74,14 +128,14 @@ protected static Token CheckTokenType(ScriptLoadingContext lcontext, TokenType t return t; } - protected static void CheckTokenTypeNotNext(ScriptLoadingContext lcontext, TokenType tokenType) + internal static void CheckTokenTypeNotNext(ScriptLoadingContext lcontext, TokenType tokenType) { Token t = lcontext.Lexer.Current; if (t.Type != tokenType) UnexpectedTokenType(t); } - protected static Token CheckMatch(ScriptLoadingContext lcontext, Token originalToken, TokenType expectedTokenType, string expectedTokenText) + internal static Token CheckMatch(ScriptLoadingContext lcontext, Token originalToken, TokenType expectedTokenType, string expectedTokenText) { Token t = lcontext.Lexer.Current; if (t.Type != expectedTokenType) diff --git a/src/WattleScript.Interpreter/Tree/Statement.cs b/src/WattleScript.Interpreter/Tree/Statement.cs index fe1a0a58..6ebd1744 100644 --- a/src/WattleScript.Interpreter/Tree/Statement.cs +++ b/src/WattleScript.Interpreter/Tree/Statement.cs @@ -25,6 +25,33 @@ protected IVariable CheckVar(ScriptLoadingContext lcontext, Expression firstExpr public abstract void ResolveScope(ScriptLoadingContext lcontext); + static void ProcessUsing(ScriptLoadingContext lcontext) + { + int line = lcontext.Lexer.Current.FromLine; + bool canBeDot = false; + + lcontext.Lexer.Next(); + + while (lcontext.Lexer.PeekNext().Type != TokenType.Eof) + { + Token tkn = lcontext.Lexer.Current; + + if (tkn.FromLine != line || tkn.ToLine != line) + { + break; + } + + canBeDot = canBeDot switch + { + false when tkn.Type != TokenType.Name => throw new SyntaxErrorException(tkn, $"unexpected token '{tkn.Text}' found in using statement"), + true when tkn.Type != TokenType.Dot => throw new SyntaxErrorException(tkn, $"unexpected token '{tkn.Text}' found in using statement"), + _ => !canBeDot + }; + + lcontext.Lexer.Next(); + } + } + static void ProcessDirective(ScriptLoadingContext lcontext) { var str = lcontext.Lexer.Current.Text; @@ -142,6 +169,21 @@ static void ProcessFunctionAnnotation(ScriptLoadingContext lcontext) } } + protected static void ParseStaticImports(ScriptLoadingContext lcontext) + { + while (true) + { + switch (lcontext.Lexer.Current.Type) + { + case TokenType.Using: + ProcessUsing(lcontext); + continue; + } + + break; + } + } + protected static void ParseAnnotations(ScriptLoadingContext lcontext) { //Process Annotations @@ -223,6 +265,8 @@ protected static Statement CreateStatement(ScriptLoadingContext lcontext, out bo return DispatchForLoopStatement(lcontext); case TokenType.Repeat: return new RepeatStatement(lcontext); + case TokenType.Namespace: + return new NamespaceStatement(lcontext); case TokenType.Function: return new FunctionDefinitionStatement(lcontext, false, null); case TokenType.Local: diff --git a/src/WattleScript.Interpreter/Tree/Statements/ChunkStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/ChunkStatement.cs index 627e51a8..8057dc86 100644 --- a/src/WattleScript.Interpreter/Tree/Statements/ChunkStatement.cs +++ b/src/WattleScript.Interpreter/Tree/Statements/ChunkStatement.cs @@ -6,7 +6,8 @@ namespace WattleScript.Interpreter.Tree.Statements { class ChunkStatement : Statement, IClosureBuilder { - Statement m_Block; + public CompositeStatement Block { get; } + RuntimeScopeFrame m_StackFrame; SymbolRef m_Env; SymbolRef m_VarArgs; @@ -15,7 +16,7 @@ class ChunkStatement : Statement, IClosureBuilder public ChunkStatement(ScriptLoadingContext lcontext) : base(lcontext) { - m_Block = new CompositeStatement(lcontext, BlockEndType.Normal); + Block = new CompositeStatement(lcontext, BlockEndType.Normal); if (lcontext.Lexer.Current.Type != TokenType.Eof) throw new SyntaxErrorException(lcontext.Lexer.Current, " expected near '{0}'", lcontext.Lexer.Current.Text); @@ -30,7 +31,7 @@ public override void ResolveScope(ScriptLoadingContext lcontext) m_VarArgs = lcontext.Scope.DefineLocal(WellKnownSymbols.VARARGS); m_Env = lcontext.Scope.DefineLocal(WellKnownSymbols.ENV); - m_Block.ResolveScope(lcontext); + Block.ResolveScope(lcontext); m_StackFrame = lcontext.Scope.PopFunction(); } @@ -42,7 +43,7 @@ public FunctionProto CompileFunction(Script script) bc.Emit_Load(SymbolRef.Upvalue(WellKnownSymbols.ENV, 0)); bc.Emit_Store(m_Env, 0, 0); bc.Emit_Pop(); - m_Block.Compile(bc); + Block.Compile(bc); bc.Emit_Ret(0); var proto = bc.GetProto("", m_StackFrame); diff --git a/src/WattleScript.Interpreter/Tree/Statements/ClassDefinitionStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/ClassDefinitionStatement.cs index f023c529..62a37597 100644 --- a/src/WattleScript.Interpreter/Tree/Statements/ClassDefinitionStatement.cs +++ b/src/WattleScript.Interpreter/Tree/Statements/ClassDefinitionStatement.cs @@ -8,8 +8,12 @@ namespace WattleScript.Interpreter.Tree.Statements { - class ClassDefinitionStatement : Statement + class ClassDefinitionStatement : Statement, IStaticallyImportableStatement { + public Token NameToken { get; } + public string DefinitionType => "class"; + public string Namespace { get; } + //Class construction related private SymbolRefExpression classStoreGlobal; private SymbolRefExpression classStoreLocal; @@ -47,9 +51,13 @@ class ClassDefinitionStatement : Statement private Dictionary mixinRefs = new Dictionary(); private MemberModifierFlags flags = MemberModifierFlags.None; + private ScriptLoadingContext lcontext; public ClassDefinitionStatement(ScriptLoadingContext lcontext) : base(lcontext) { + this.lcontext = lcontext; + Namespace = lcontext.Linker.CurrentNamespace; + while (lcontext.Lexer.Current.IsMemberModifier()) { MemberUtilities.AddModifierFlag(ref flags, lcontext.Lexer.Current, WattleMemberType.Class); @@ -59,6 +67,7 @@ public ClassDefinitionStatement(ScriptLoadingContext lcontext) : base(lcontext) lcontext.Lexer.Next(); var nameToken = CheckTokenType(lcontext, TokenType.Name); + NameToken = nameToken; className = nameToken.Text; localName = $"$class:{className}"; //base class @@ -416,6 +425,19 @@ void CompileNew(FunctionBuilder parent) public override void Compile(FunctionBuilder bc) { + if (false && lcontext.Linker.ImportMap.ContainsKey(Namespace)) + { + if (lcontext.Linker.ImportMap[Namespace].ContainsKey(className)) + { + if (lcontext.Linker.ImportMap[Namespace][className].Compiled) + { + return; + } + + lcontext.Linker.ImportMap[Namespace][className].Compiled = true; + } + } + bc.PushSourceRef(defSource); bc.Emit_Enter(classBlock); //mixin names diff --git a/src/WattleScript.Interpreter/Tree/Statements/CompositeStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/CompositeStatement.cs index 418efb17..3d28088b 100644 --- a/src/WattleScript.Interpreter/Tree/Statements/CompositeStatement.cs +++ b/src/WattleScript.Interpreter/Tree/Statements/CompositeStatement.cs @@ -16,13 +16,23 @@ class CompositeStatement : Statement { List m_Statements = new List(); + public void InsertStatements(List statements) + { + // push to front + statements.AddRange(m_Statements); + m_Statements = statements; + } + public CompositeStatement(ScriptLoadingContext lcontext, BlockEndType endType) : base(lcontext) { + lcontext.Linker?.StoreNamespace(); + while (true) { try { + ParseStaticImports(lcontext); ParseAnnotations(lcontext); Token t = lcontext.Lexer.Current; if (t.IsEndOfBlock()) break; @@ -62,6 +72,8 @@ public CompositeStatement(ScriptLoadingContext lcontext, BlockEndType endType) } } + lcontext.Linker?.RestoreNamespace(); + // eat away all superfluos ';'s while (lcontext.Lexer.Current.Type == TokenType.SemiColon) lcontext.Lexer.Next(); @@ -90,6 +102,10 @@ private void Synchronize(ScriptLoadingContext lcontext) case TokenType.Directive: case TokenType.Do: case TokenType.If: + case TokenType.Switch: + case TokenType.Enum: + case TokenType.Class: + case TokenType.Mixin: { goto endSynchronize; } diff --git a/src/WattleScript.Interpreter/Tree/Statements/EnumDefinitionStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/EnumDefinitionStatement.cs index 280b0143..25ed90eb 100644 --- a/src/WattleScript.Interpreter/Tree/Statements/EnumDefinitionStatement.cs +++ b/src/WattleScript.Interpreter/Tree/Statements/EnumDefinitionStatement.cs @@ -5,8 +5,12 @@ namespace WattleScript.Interpreter.Tree.Statements { - class EnumDefinitionStatement : Statement + class EnumDefinitionStatement : Statement, IStaticallyImportableStatement { + public Token NameToken { get; } + public string DefinitionType => "enum"; + public string Namespace { get; } + private string enumName; private SourceRef assignment; private SourceRef buildCode; @@ -18,12 +22,14 @@ class EnumDefinitionStatement : Statement public EnumDefinitionStatement(ScriptLoadingContext lcontext) : base(lcontext) { + Namespace = lcontext.Linker.CurrentNamespace; annotations = lcontext.FunctionAnnotations.ToArray(); lcontext.FunctionAnnotations = new List(); //lexer is at "enum" var start = lcontext.Lexer.Current; lcontext.Lexer.Next(); var name = CheckTokenType(lcontext, TokenType.Name); + NameToken = name; enumName = name.Text; assignment = start.GetSourceRef(name); var buildStart = CheckTokenType(lcontext, TokenType.Brk_Open_Curly); diff --git a/src/WattleScript.Interpreter/Tree/Statements/MixinDefinitionStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/MixinDefinitionStatement.cs index 354581cd..06c1c07f 100644 --- a/src/WattleScript.Interpreter/Tree/Statements/MixinDefinitionStatement.cs +++ b/src/WattleScript.Interpreter/Tree/Statements/MixinDefinitionStatement.cs @@ -8,8 +8,12 @@ namespace WattleScript.Interpreter.Tree.Statements { - class MixinDefinitionStatement : Statement + class MixinDefinitionStatement : Statement, IStaticallyImportableStatement { + public Token NameToken { get; } + public string DefinitionType => "mixin"; + public string Namespace { get; } + private SymbolRefExpression storeValue; private string name; private GeneratedClosure init; @@ -23,9 +27,11 @@ class MixinDefinitionStatement : Statement public MixinDefinitionStatement(ScriptLoadingContext lcontext) : base(lcontext) { + Namespace = lcontext.Linker.CurrentNamespace; annotations = lcontext.FunctionAnnotations.ToArray(); lcontext.Lexer.Next(); var nameToken = CheckTokenType(lcontext, TokenType.Name); + NameToken = nameToken; name = nameToken.Text; sourceRef = nameToken.GetSourceRef(CheckTokenType(lcontext, TokenType.Brk_Open_Curly)); diff --git a/src/WattleScript.Interpreter/Tree/Statements/NamespaceStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/NamespaceStatement.cs new file mode 100644 index 00000000..180ae650 --- /dev/null +++ b/src/WattleScript.Interpreter/Tree/Statements/NamespaceStatement.cs @@ -0,0 +1,66 @@ +using System.Text; +using WattleScript.Interpreter.Execution; +using WattleScript.Interpreter.Execution.VM; + +namespace WattleScript.Interpreter.Tree.Statements +{ + class NamespaceStatement : Statement + { + private CompositeStatement block; + private RuntimeScopeBlock stackFrame; + private string namespaceIdentStr; + + public NamespaceStatement(ScriptLoadingContext lcontext) : base(lcontext) + { + CheckTokenType(lcontext, TokenType.Namespace); + bool canBeDot = false; + StringBuilder namespaceIdent = new StringBuilder(); + + while (lcontext.Lexer.PeekNext().Type != TokenType.Eof) + { + Token tkn = lcontext.Lexer.Current; + + if (!canBeDot && tkn.Type != TokenType.Name) + { + break; + } + + if (canBeDot && tkn.Type != TokenType.Dot) + { + break; + } + + canBeDot = !canBeDot; + + namespaceIdent.Append(tkn.Text); + lcontext.Lexer.Next(); + } + + namespaceIdentStr = namespaceIdent.ToString(); + lcontext.Linker.CurrentNamespace = namespaceIdentStr; + + if (lcontext.Lexer.Current.Type == TokenType.Brk_Open_Curly) + { + block = new CompositeStatement(lcontext, BlockEndType.CloseCurly); + } + else + { + block = new CompositeStatement(lcontext, BlockEndType.Normal); + } + } + + public override void Compile(FunctionBuilder bc) + { + bc.Emit_Enter(stackFrame); + block.Compile(bc); + bc.Emit_Leave(stackFrame); + } + + public override void ResolveScope(ScriptLoadingContext lcontext) + { + lcontext.Scope.PushBlock(namespaceIdentStr); + block.ResolveScope(lcontext); + stackFrame = lcontext.Scope.PopBlock(); + } + } +} \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/1-using.lua b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/1-using.lua new file mode 100644 index 00000000..4dc3a48e --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/1-using.lua @@ -0,0 +1,10 @@ +using ws.tests.classTest + +namespace ws.tests.entry { + print("hello world") + + class ClassA : ClassB { ClassA() { base() } } + class ClassC { ClassC() { print("hello world2") } } + + a = new ClassA() +} \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/1-using.txt b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/1-using.txt new file mode 100644 index 00000000..96b5e706 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/1-using.txt @@ -0,0 +1,2 @@ +hello world +hello world2 \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/2-namespace-empty.lua b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/2-namespace-empty.lua new file mode 100644 index 00000000..17d32c5d --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/2-namespace-empty.lua @@ -0,0 +1,8 @@ +namespace lib.a { + class test { + x = 10 + } +} + +a = new lib.a.test() +print(a.x) \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/2-namespace-empty.txt b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/2-namespace-empty.txt new file mode 100644 index 00000000..9a037142 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/2-namespace-empty.txt @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/ws.tests.classTest.wtlib b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/ws.tests.classTest.wtlib new file mode 100644 index 00000000..3b662044 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Linker/ws.tests.classTest.wtlib @@ -0,0 +1,6 @@ +using caller + +namespace ws.tests.classTest { + print("test") + class ClassB { cInst = nil; ClassB() { this.cInst = new ClassC() } } +} \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLikeTestRunner.cs b/src/WattleScript.Tests/EndToEnd/CLikeTestRunner.cs index f761b95f..2c518bb0 100644 --- a/src/WattleScript.Tests/EndToEnd/CLikeTestRunner.cs +++ b/src/WattleScript.Tests/EndToEnd/CLikeTestRunner.cs @@ -34,7 +34,7 @@ public async Task RunReportErrors(string path) await RunCore(path, true); } - Script InitScript() + Script InitScript(string scriptFolderPath) { Script script = new Script(CoreModules.Preset_SoftSandboxWattle); script.Options.IndexTablesFrom = 0; @@ -46,6 +46,22 @@ Script InitScript() script.Options.AnnotationPolicy = new CustomPolicy(AnnotationValueParsingPolicy.ForceTable); script.Globals["CurrentLine"] = (ScriptExecutionContext c, CallbackArguments a) => c.CallingLocation.FromLine; script.Globals["CurrentColumn"] = (ScriptExecutionContext c, CallbackArguments a) => c.CallingLocation.FromChar; + script.Options.ScriptLoader.UsingResolver = (path) => + { + if (path == "caller") + { + return Module.LocalModule; + } + + if (File.Exists($"{scriptFolderPath}\\{path}.wtlib")) + { + string libText = File.ReadAllText($"{scriptFolderPath}\\{path}.wtlib"); + return new Module(libText); + } + + Assert.Fail($"Library file {scriptFolderPath}\\{path}.wtlib not found"); + throw new ScriptRuntimeException($"Library file {scriptFolderPath}\\{path}.wtlib not found"); + }; return script; } @@ -60,11 +76,13 @@ public async Task RunCore(string path, bool reportErrors = false) return; } + string rootPath = Path.GetFullPath(Path.Combine(outputPath.Replace(".lua", ""), "..\\")); string code = await File.ReadAllTextAsync(path); string output = File.Exists(outputPath) ? await File.ReadAllTextAsync(outputPath) : ""; stdOut = new StringBuilder(); - Script script = InitScript(); + Script script = InitScript(rootPath); + if (path.Contains("flaky")) { Assert.Inconclusive($"Test {path} marked as flaky"); @@ -93,7 +111,7 @@ public async Task RunCore(string path, bool reportErrors = false) if (TEST_SOURCE_REFS_DUMP) { - Exception e = TestSourceRefsBinDump(code); + Exception e = TestSourceRefsBinDump(code, rootPath); if (e != null) { Assert.Fail($"Dumped source refs not equal to original.\n{e.Message}\n{e.StackTrace}"); @@ -128,16 +146,16 @@ public async Task RunCore(string path, bool reportErrors = false) } } - public Exception TestSourceRefsBinDump(string code) + public Exception TestSourceRefsBinDump(string code, string path) { - Script sc = InitScript(); + Script sc = InitScript(path); DynValue dv = sc.LoadString(code); IReadOnlyList originalSourceRefs = dv.Function.Function.SourceRefs; using MemoryStream ms = new MemoryStream(); sc.Dump(dv, ms); ms.Seek(0, SeekOrigin.Begin); - sc = InitScript(); + sc = InitScript(path); dv = sc.LoadStream(ms); dv.Function.Call(); diff --git a/src/WattleScript.Tests/EndToEnd/CSyntaxTests.cs b/src/WattleScript.Tests/EndToEnd/CSyntaxTests.cs index b964332e..f3d30860 100644 --- a/src/WattleScript.Tests/EndToEnd/CSyntaxTests.cs +++ b/src/WattleScript.Tests/EndToEnd/CSyntaxTests.cs @@ -144,10 +144,10 @@ public void UsingDirective() { var s = new Script(); s.Options.Syntax = ScriptSyntax.Wattle; - s.Options.Directives.Add("using"); - var chunk = s.LoadString("using a.b.c;"); + s.Options.Directives.Add("usingDirective"); + var chunk = s.LoadString("usingDirective a.b.c;"); var a = chunk.Function.Annotations[0]; - Assert.AreEqual("using", a.Name); + Assert.AreEqual("usingDirective", a.Name); Assert.AreEqual("a.b.c", a.Value.String); }