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);
}