From 868bec10b630dbcd381c0b7ed09b5bc9de0624e7 Mon Sep 17 00:00:00 2001 From: lofcz Date: Thu, 14 Apr 2022 18:45:57 +0200 Subject: [PATCH 01/74] Implement basic tests runner --- .../Options/ScriptSyntax.cs | 8 +- .../Serialization/Json/JsonTableConverter.cs | 2 +- .../Tree/Expression_.cs | 8 +- .../Expressions/BinaryOperatorExpression.cs | 2 +- .../FunctionDefinitionExpression.cs | 2 +- .../Tree/Expressions/IndexExpression.cs | 2 +- .../Tree/Fast_Interface/Loader_Fast.cs | 2 +- .../Tree/Lexer/Lexer.cs | 36 +- .../Tree/Statement.cs | 4 +- .../Tree/Statements/AssignmentStatement.cs | 6 +- .../Tree/Statements/CompositeStatement.cs | 2 +- .../Tree/Statements/LabelStatement.cs | 2 +- src/WattleScript.Templating/Class1.cs | 5 + src/WattleScript.Templating/Template.cs | 88 +++++ src/WattleScript.Templating/Token.cs | 33 ++ src/WattleScript.Templating/Tokenizer.cs | 355 ++++++++++++++++++ .../WattleScript.Templating.csproj | 9 + .../CLike/SyntaxCLike/Misc/1-call-chain.lua | 9 + .../CLike/SyntaxCLike/Misc/1-call-chain.txt | 1 + .../EndToEnd/CLikeTestRunner.cs | 2 +- .../EndToEnd/CSyntaxTests.cs | 80 ++-- .../Templating/TemplatingTestsRunner.cs | 110 ++++++ .../Templating/Tests/1-simple.html | 1 + .../Templating/Tests/1-simple.wthtml | 4 + .../Templating/Tests/2-simple-newline.html | 3 + .../Templating/Tests/2-simple-newline.wthtml | 6 + .../Templating/Tests/3-simple-table.html | 1 + .../Templating/Tests/3-simple-table.wthtml | 4 + .../Templating/Tests/4-nested-table.html | 1 + .../Templating/Tests/4-nested-table.wthtml | 4 + .../Templating/Tests/5-call.html | 1 + .../Templating/Tests/5-call.wthtml | 10 + .../WattleScript.Tests.csproj | 4 + src/WattleScript.sln | 14 + 34 files changed, 740 insertions(+), 81 deletions(-) create mode 100644 src/WattleScript.Templating/Class1.cs create mode 100644 src/WattleScript.Templating/Template.cs create mode 100644 src/WattleScript.Templating/Token.cs create mode 100644 src/WattleScript.Templating/Tokenizer.cs create mode 100644 src/WattleScript.Templating/WattleScript.Templating.csproj create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/1-call-chain.lua create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/1-call-chain.txt create mode 100644 src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs create mode 100644 src/WattleScript.Tests/Templating/Tests/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/1-simple.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/2-simple-newline.html create mode 100644 src/WattleScript.Tests/Templating/Tests/2-simple-newline.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/3-simple-table.html create mode 100644 src/WattleScript.Tests/Templating/Tests/3-simple-table.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/4-nested-table.html create mode 100644 src/WattleScript.Tests/Templating/Tests/4-nested-table.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/5-call.html create mode 100644 src/WattleScript.Tests/Templating/Tests/5-call.wthtml diff --git a/src/WattleScript.Interpreter/Options/ScriptSyntax.cs b/src/WattleScript.Interpreter/Options/ScriptSyntax.cs index a5f19148..82fbf558 100644 --- a/src/WattleScript.Interpreter/Options/ScriptSyntax.cs +++ b/src/WattleScript.Interpreter/Options/ScriptSyntax.cs @@ -10,13 +10,9 @@ public enum ScriptSyntax /// Lua, /// - /// Backwards compatible C-like syntax - /// - CompatibleCLike, - /// - /// C-like syntax including breaking changes + /// WattleScript syntax /// e.g. ++ and -- operators /// - CLike, + WattleScript } } \ No newline at end of file diff --git a/src/WattleScript.Interpreter/Serialization/Json/JsonTableConverter.cs b/src/WattleScript.Interpreter/Serialization/Json/JsonTableConverter.cs index e950619d..09ba0a3e 100755 --- a/src/WattleScript.Interpreter/Serialization/Json/JsonTableConverter.cs +++ b/src/WattleScript.Interpreter/Serialization/Json/JsonTableConverter.cs @@ -140,7 +140,7 @@ private static bool IsValueJsonCompatible(DynValue value) /// A table containing the representation of the given json. public static Table JsonToTable(string json, Script script = null) { - Lexer L = new Lexer(0, json, false, false, false, null); + Lexer L = new Lexer(0, json, false, false, script?.Options.Syntax ?? ScriptSyntax.Lua, null); if (L.Current.Type == TokenType.Brk_Open_Curly) return ParseJsonObject(L, script); diff --git a/src/WattleScript.Interpreter/Tree/Expression_.cs b/src/WattleScript.Interpreter/Tree/Expression_.cs index c314adad..864352d9 100644 --- a/src/WattleScript.Interpreter/Tree/Expression_.cs +++ b/src/WattleScript.Interpreter/Tree/Expression_.cs @@ -172,7 +172,7 @@ internal static Expression SimpleExp(ScriptLoadingContext lcontext) case TokenType.Number: case TokenType.Number_Hex: case TokenType.Number_HexFloat: - case TokenType.String when lcontext.Syntax != ScriptSyntax.CLike: + case TokenType.String when lcontext.Syntax != ScriptSyntax.WattleScript: case TokenType.String_Long: case TokenType.Nil: case TokenType.True: @@ -266,8 +266,8 @@ internal static Expression PrimaryExp(ScriptLoadingContext lcontext) e = ne; break; } - case TokenType.Colon when lcontext.Syntax != ScriptSyntax.CLike: - case TokenType.DoubleColon when lcontext.Syntax == ScriptSyntax.CLike: + case TokenType.Colon when lcontext.Syntax != ScriptSyntax.WattleScript: + case TokenType.DoubleColon when lcontext.Syntax == ScriptSyntax.WattleScript: lcontext.Lexer.Next(); thisCallName = CheckTokenType(lcontext, TokenType.Name); goto case TokenType.Brk_Open_Round; @@ -305,7 +305,7 @@ private static Expression PrefixExp(ScriptLoadingContext lcontext) Token T = lcontext.Lexer.Current; switch (T.Type) { - case TokenType.String when lcontext.Syntax == ScriptSyntax.CLike: + case TokenType.String when lcontext.Syntax == ScriptSyntax.WattleScript: case TokenType.String_EndTemplate: return new LiteralExpression(lcontext, T); case TokenType.String_TemplateFragment: diff --git a/src/WattleScript.Interpreter/Tree/Expressions/BinaryOperatorExpression.cs b/src/WattleScript.Interpreter/Tree/Expressions/BinaryOperatorExpression.cs index 0f0c3e3b..c04d42d5 100755 --- a/src/WattleScript.Interpreter/Tree/Expressions/BinaryOperatorExpression.cs +++ b/src/WattleScript.Interpreter/Tree/Expressions/BinaryOperatorExpression.cs @@ -299,7 +299,7 @@ private BinaryOperatorExpression(Expression exp1, Expression exp2, Operator op, m_Exp1 = exp1; m_Exp2 = exp2; m_Operator = op; - if (op == Operator.Add && lcontext.Syntax == ScriptSyntax.CLike) + if (op == Operator.Add && lcontext.Syntax == ScriptSyntax.WattleScript) m_Operator = Operator.AddConcat; } diff --git a/src/WattleScript.Interpreter/Tree/Expressions/FunctionDefinitionExpression.cs b/src/WattleScript.Interpreter/Tree/Expressions/FunctionDefinitionExpression.cs index 05ff9e1e..1ab64977 100644 --- a/src/WattleScript.Interpreter/Tree/Expressions/FunctionDefinitionExpression.cs +++ b/src/WattleScript.Interpreter/Tree/Expressions/FunctionDefinitionExpression.cs @@ -171,7 +171,7 @@ private Statement CreateBody(ScriptLoadingContext lcontext, bool openCurly) // method decls with ':' must push an implicit 'self' param if (pushSelfParam) - paramnames.Add(lcontext.Syntax == ScriptSyntax.CLike ? new FunctionDefinitionStatement.FunctionParamRef("this") : new FunctionDefinitionStatement.FunctionParamRef("self")); + paramnames.Add(lcontext.Syntax == ScriptSyntax.WattleScript ? new FunctionDefinitionStatement.FunctionParamRef("this") : new FunctionDefinitionStatement.FunctionParamRef("self")); bool parsingDefaultParams = false; while (lcontext.Lexer.Current.Type != closeToken) diff --git a/src/WattleScript.Interpreter/Tree/Expressions/IndexExpression.cs b/src/WattleScript.Interpreter/Tree/Expressions/IndexExpression.cs index f9746437..04cc73a0 100644 --- a/src/WattleScript.Interpreter/Tree/Expressions/IndexExpression.cs +++ b/src/WattleScript.Interpreter/Tree/Expressions/IndexExpression.cs @@ -46,7 +46,7 @@ public IndexExpression(Expression baseExp, Token nameToken, bool nilCheck, Scrip m_Name = nameToken.Text; this.nilCheck = nilCheck; // - if (lcontext.Syntax == ScriptSyntax.CLike && m_Name.Equals("length")) { + if (lcontext.Syntax == ScriptSyntax.WattleScript && m_Name.Equals("length")) { isLength = true; } //inc/dec expr diff --git a/src/WattleScript.Interpreter/Tree/Fast_Interface/Loader_Fast.cs b/src/WattleScript.Interpreter/Tree/Fast_Interface/Loader_Fast.cs index dce19396..aca3571d 100644 --- a/src/WattleScript.Interpreter/Tree/Fast_Interface/Loader_Fast.cs +++ b/src/WattleScript.Interpreter/Tree/Fast_Interface/Loader_Fast.cs @@ -40,7 +40,7 @@ private static ScriptLoadingContext CreateLoadingContext(Script script, SourceCo return new ScriptLoadingContext(script) { Source = source, - Lexer = new Lexer(source.SourceID, source.Code, true, script.Options.Syntax != ScriptSyntax.Lua, script.Options.Syntax == ScriptSyntax.CLike, script.Options.Directives), + Lexer = new Lexer(source.SourceID, source.Code, true, script.Options.Syntax != ScriptSyntax.Lua, script.Options.Syntax, script.Options.Directives), Syntax = script.Options.Syntax }; } diff --git a/src/WattleScript.Interpreter/Tree/Lexer/Lexer.cs b/src/WattleScript.Interpreter/Tree/Lexer/Lexer.cs index c1d20ddf..3446ca57 100755 --- a/src/WattleScript.Interpreter/Tree/Lexer/Lexer.cs +++ b/src/WattleScript.Interpreter/Tree/Lexer/Lexer.cs @@ -16,10 +16,10 @@ class Lexer int m_SourceId; bool m_AutoSkipComments = false; bool m_Extended = false; - private bool m_IncDec; + private ScriptSyntax m_Syntax; private HashSet m_Directives; - public Lexer(int sourceID, string scriptContent, bool autoSkipComments, bool extended, bool incdec, HashSet directives) + public Lexer(int sourceID, string scriptContent, bool autoSkipComments, bool extended, ScriptSyntax syntax, HashSet directives) { m_Code = scriptContent; m_SourceId = sourceID; @@ -30,7 +30,7 @@ public Lexer(int sourceID, string scriptContent, bool autoSkipComments, bool ext m_AutoSkipComments = autoSkipComments; m_Extended = extended; - m_IncDec = incdec; + m_Syntax = syntax; m_Directives = directives; } @@ -242,11 +242,11 @@ private Token ReadToken() switch (c) { - case '@' when m_IncDec: + case '@' when m_Syntax == ScriptSyntax.WattleScript: return PotentiallyDoubleCharOperator('@', TokenType.FunctionAnnotation, TokenType.ChunkAnnotation, fromLine, fromCol); case '|': - if (m_IncDec) + if (m_Syntax == ScriptSyntax.WattleScript) { var next = CursorCharNext(); if (next == '=') @@ -279,11 +279,11 @@ private Token ReadToken() CursorCharNext(); return CreateToken(TokenType.And, fromLine, fromCol, "&&"); } - else if (m_IncDec && next == '=') { + else if (m_Syntax == ScriptSyntax.WattleScript && next == '=') { CursorCharNext(); return CreateToken(TokenType.Op_AndEq, fromLine, fromCol, "&="); } - else if (m_IncDec) { + else if (m_Syntax == ScriptSyntax.WattleScript) { return CreateToken(TokenType.Op_And, fromLine, fromCol, "&"); } else { @@ -313,7 +313,7 @@ private Token ReadToken() fromCol); } } - case '<' when m_IncDec: + case '<' when m_Syntax == ScriptSyntax.WattleScript: { char next = CursorCharNext(); if (next == '<') @@ -327,7 +327,7 @@ private Token ReadToken() } return CreateToken(TokenType.Op_LessThan, fromLine, fromCol, "<"); } - case '>' when m_IncDec: + case '>' when m_Syntax == ScriptSyntax.WattleScript: { char next = CursorCharNext(); if (next == '>') @@ -353,16 +353,16 @@ private Token ReadToken() } return CreateToken(TokenType.Op_GreaterThan, fromLine, fromCol, ">"); } - case '<' when !m_IncDec: + case '<' when m_Syntax != ScriptSyntax.WattleScript: return PotentiallyDoubleCharOperator('=', TokenType.Op_LessThan, TokenType.Op_LessThanEqual, fromLine, fromCol); - case '>' when !m_IncDec: + case '>' when m_Syntax != ScriptSyntax.WattleScript: return PotentiallyDoubleCharOperator('=', TokenType.Op_GreaterThan, TokenType.Op_GreaterThanEqual, fromLine, fromCol); case '!' when m_Extended: return PotentiallyDoubleCharOperator('=', TokenType.Not, TokenType.Op_NotEqual, fromLine, fromCol); - case '~' when m_IncDec: + case '~' when m_Syntax == ScriptSyntax.WattleScript: return CreateSingleCharToken(TokenType.Op_Not, fromLine, fromCol); case '!' when !m_Extended: - case '~' when !m_IncDec: + case '~' when m_Syntax != ScriptSyntax.WattleScript: if (CursorCharNext() != '=') throw new SyntaxErrorException(CreateToken(TokenType.Invalid, fromLine, fromCol), "unexpected symbol near '{0}'", c); CursorCharNext(); @@ -402,7 +402,7 @@ private Token ReadToken() if (m_Extended) { char next = CursorCharNext(); - if (m_IncDec && next == '+') + if (m_Syntax == ScriptSyntax.WattleScript && next == '+') { CursorCharNext(); return CreateToken(TokenType.Op_Inc, fromLine, fromCol, "++"); @@ -427,7 +427,7 @@ private Token ReadToken() char next = CursorCharNext(); if (next == '-') { - if (m_IncDec) + if (m_Syntax == ScriptSyntax.WattleScript) { CursorCharNext(); return CreateToken(TokenType.Op_Dec, fromLine, fromCol, "--"); @@ -493,7 +493,7 @@ private Token ReadToken() fromCol); return CreateSingleCharToken(TokenType.Op_Mod, fromLine, fromCol); case '^': - if (m_IncDec) + if (m_Syntax == ScriptSyntax.WattleScript) { return PotentiallyDoubleCharOperator('=', TokenType.Op_Xor, TokenType.Op_XorEq, fromLine, fromCol); @@ -509,7 +509,7 @@ private Token ReadToken() case '[': { char next = CursorCharNext(); - if (next == '=' || next == '[') + if (m_Syntax != ScriptSyntax.WattleScript && (next == '=' || next == '[')) { string str = ReadLongString(fromLine, fromCol, null, "string"); return CreateToken(TokenType.String_Long, fromLine, fromCol, str); @@ -537,7 +537,7 @@ private Token ReadToken() return CreateSingleCharToken(TokenType.Brk_Close_Curly, fromLine, fromCol); case ',': return CreateSingleCharToken(TokenType.Comma, fromLine, fromCol); - case '?' when m_IncDec: + case '?' when m_Syntax == ScriptSyntax.WattleScript: { char next = CursorCharNext(); diff --git a/src/WattleScript.Interpreter/Tree/Statement.cs b/src/WattleScript.Interpreter/Tree/Statement.cs index a5d2edab..2ff241b1 100644 --- a/src/WattleScript.Interpreter/Tree/Statement.cs +++ b/src/WattleScript.Interpreter/Tree/Statement.cs @@ -185,7 +185,7 @@ protected static Statement CreateStatement(ScriptLoadingContext lcontext, out bo switch (tkn.Type) { - case TokenType.DoubleColon when lcontext.Syntax != ScriptSyntax.CLike: + case TokenType.DoubleColon when lcontext.Syntax != ScriptSyntax.WattleScript: return new LabelStatement(lcontext); case TokenType.Goto: return new GotoStatement(lcontext); @@ -223,7 +223,7 @@ protected static Statement CreateStatement(ScriptLoadingContext lcontext, out bo //Check for labels in CLike mode lcontext.Lexer.SavePos(); Token l = lcontext.Lexer.Current; - if (lcontext.Syntax == ScriptSyntax.CLike && l.Type == TokenType.Name) + if (lcontext.Syntax == ScriptSyntax.WattleScript && l.Type == TokenType.Name) { lcontext.Lexer.Next(); if (lcontext.Lexer.Current.Type == TokenType.Colon) { diff --git a/src/WattleScript.Interpreter/Tree/Statements/AssignmentStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/AssignmentStatement.cs index d2e53487..c7fc48aa 100644 --- a/src/WattleScript.Interpreter/Tree/Statements/AssignmentStatement.cs +++ b/src/WattleScript.Interpreter/Tree/Statements/AssignmentStatement.cs @@ -43,13 +43,13 @@ public AssignmentStatement(ScriptLoadingContext lcontext, Token startToken) CheckTokenType(lcontext, TokenType.Op_Assignment); m_RValues = Expression.ExprList(lcontext); } - else if (lcontext.Syntax == ScriptSyntax.CLike && lcontext.Lexer.Current.Type == TokenType.Op_NilCoalescingAssignment) + else if (lcontext.Syntax == ScriptSyntax.WattleScript && lcontext.Lexer.Current.Type == TokenType.Op_NilCoalescingAssignment) { CheckTokenType(lcontext, TokenType.Op_NilCoalescingAssignment); AssignmentOp = Operator.NilCoalescing; m_RValues = Expression.ExprList(lcontext); } - else if (lcontext.Syntax == ScriptSyntax.CLike && lcontext.Lexer.Current.Type == TokenType.Op_NilCoalesceInverse) + else if (lcontext.Syntax == ScriptSyntax.WattleScript && lcontext.Lexer.Current.Type == TokenType.Op_NilCoalesceInverse) { CheckTokenType(lcontext, TokenType.Op_NilCoalesceInverse); AssignmentOp = Operator.NilCoalescingInverse; @@ -123,7 +123,7 @@ public AssignmentStatement(ScriptLoadingContext lcontext, Expression firstExpres if (lcontext.Syntax != ScriptSyntax.Lua) { switch (lcontext.Lexer.Current.Type) { case TokenType.Op_AddEq: - if (lcontext.Syntax == ScriptSyntax.CLike) + if (lcontext.Syntax == ScriptSyntax.WattleScript) AssignmentOp = Operator.AddConcat; else AssignmentOp = Operator.Add; diff --git a/src/WattleScript.Interpreter/Tree/Statements/CompositeStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/CompositeStatement.cs index 6259b0e1..ebb0dab2 100644 --- a/src/WattleScript.Interpreter/Tree/Statements/CompositeStatement.cs +++ b/src/WattleScript.Interpreter/Tree/Statements/CompositeStatement.cs @@ -97,7 +97,7 @@ private void Synchronize(ScriptLoadingContext lcontext) public override void ResolveScope(ScriptLoadingContext lcontext) { - if (lcontext.Syntax == ScriptSyntax.CLike) + if (lcontext.Syntax == ScriptSyntax.WattleScript) { //Perform declaration hoisting. //Define all locals upfront, then bring function definitions up diff --git a/src/WattleScript.Interpreter/Tree/Statements/LabelStatement.cs b/src/WattleScript.Interpreter/Tree/Statements/LabelStatement.cs index 7b613d1f..886e3f56 100644 --- a/src/WattleScript.Interpreter/Tree/Statements/LabelStatement.cs +++ b/src/WattleScript.Interpreter/Tree/Statements/LabelStatement.cs @@ -21,7 +21,7 @@ class LabelStatement : Statement public LabelStatement(ScriptLoadingContext lcontext) : base(lcontext) { - if (lcontext.Syntax == ScriptSyntax.CLike) + if (lcontext.Syntax == ScriptSyntax.WattleScript) { NameToken = CheckTokenType(lcontext, TokenType.Name); CheckTokenType(lcontext, TokenType.Colon); diff --git a/src/WattleScript.Templating/Class1.cs b/src/WattleScript.Templating/Class1.cs new file mode 100644 index 00000000..7b3d6c47 --- /dev/null +++ b/src/WattleScript.Templating/Class1.cs @@ -0,0 +1,5 @@ +namespace WattleScript.Templating; + +public class Class1 +{ +} \ No newline at end of file diff --git a/src/WattleScript.Templating/Template.cs b/src/WattleScript.Templating/Template.cs new file mode 100644 index 00000000..280b8f4a --- /dev/null +++ b/src/WattleScript.Templating/Template.cs @@ -0,0 +1,88 @@ +using System.Text; + +namespace WattleScript.Templating; + +public class Template +{ + public string EncodeJsString(string s) + { + StringBuilder sb = new StringBuilder(); + sb.Append("\""); + foreach (char c in s) + { + switch (c) + { + case '\"': + sb.Append("\\\""); + break; + case '\\': + sb.Append("\\\\"); + break; + case '\b': + sb.Append("\\b"); + break; + case '\f': + sb.Append("\\f"); + break; + case '\n': + sb.Append("\\n"); + break; + case '\r': + sb.Append("\\r"); + break; + case '\t': + sb.Append("\\t"); + break; + default: + int i = (int)c; + if (i < 32 || i > 127) + { + sb.AppendFormat("\\u{0:X04}", i); + } + else + { + sb.Append(c); + } + break; + } + } + sb.Append("\""); + + return sb.ToString(); + } + + public string Render(string code) + { + Tokenizer tk = new Tokenizer(); + List tokens = tk.Tokenize(code); + + StringBuilder sb = new StringBuilder(); + bool firstClientPending = true; + + foreach (Token tkn in tokens) + { + if (tkn.Type == TokenTypes.ClientText) + { + string lexeme = tkn.Lexeme; + if (firstClientPending) + { + lexeme = lexeme.TrimStart(); + firstClientPending = false; + } + + sb.AppendLine($"stdout({EncodeJsString(lexeme)})"); + } + else if (tkn.Type == TokenTypes.BlockExpr) + { + sb.AppendLine(tkn.Lexeme); + } + else if (tkn.Type == TokenTypes.ImplicitExpr) + { + sb.AppendLine($"stdout({tkn.Lexeme})"); + } + } + + string finalText = sb.ToString(); + return finalText; + } +} \ No newline at end of file diff --git a/src/WattleScript.Templating/Token.cs b/src/WattleScript.Templating/Token.cs new file mode 100644 index 00000000..07115c95 --- /dev/null +++ b/src/WattleScript.Templating/Token.cs @@ -0,0 +1,33 @@ +namespace WattleScript.Templating; + + +public enum TokenTypes +{ + BlockExpr, + ExplicitExpr, + ImplicitExpr, + ClientText, + Eof, + Length +} + +public class Token +{ + public TokenTypes Type { get; set; } + public string Lexeme { get; set; } + public object Literal { get; set; } + public int Line { get; set; } + + public Token(TokenTypes type, string lexeme, object literal, int line) + { + Type = type; + Lexeme = lexeme; + Literal = literal; + Line = line; + } + + public override string ToString() + { + return $"{Type} - {Lexeme} - {Literal}"; + } +} \ No newline at end of file diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs new file mode 100644 index 00000000..7b2044f1 --- /dev/null +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -0,0 +1,355 @@ +namespace WattleScript.Templating; + +public class Tokenizer +{ + enum ImplicitExpressionTypes + { + Literal, + AllowedKeyword, + BannedKeyword + } + + private string Source { get; set; } + private List Tokens { get; set; } = new List(); + private List Messages { get; set; } = new List(); + private List AllowedTransitionKeywords = new List() {"if", "for", "do", "while", "require", "function"}; + private List BannedTransitionKeywords = new List() {"else", "elseif"}; + + private Token LastToken => Tokens[^1]; + + public List Tokenize(string source, bool includeNewlines = false) + { + int line = 1; + int pos = 0; + bool tokenize = true; + char c; + string currentLexeme = ""; + bool anyErrors = false; + Source = source; + + ParseClient(); + + if (IsAtEnd()) + { + tokenize = false; + AddToken(TokenTypes.Eof); + } + + if (anyErrors) + { + tokenize = false; + } + + + bool IsAtEnd() + { + return pos >= Source.Length; + } + + + /* In client mode everything is a literal + * until we encouter @ + * then we lookahead at next char and if it's not another @ (escape) + * we enter server mode + */ + void ParseClient() + { + while (!IsAtEnd()) + { + if (Peek() == '@') + { + if (Peek(2) != '@') + { + AddToken(TokenTypes.ClientText); + ParseTransition(); + return; + } + } + + Step(); + } + + AddToken(TokenTypes.ClientText); + } + + void ParseTransition() + { + /* Valid transition sequences are + * @{ - block + * @( - explicit expression + * @: - line transition (back to client) + * @TKeyword - if, for, while, do... + * @TBannedKeyword - else, elseif + * @ContrainedLiteral - eg. @myVar. First char has to be either alpha or underscore (eg. @8 is invalid) + * --------- + * If a valid transition has not been found + * - if we found TBannedKeyword - we stash an errror, resume parsing client side. We should report: "@ can't be followed by a reserved keyword 'else'. Please remove the @ symbol at line X, char Y." + * - else - we stash an error and consider the sequence to be client side literal. Eg. @8 should report: "@ must be followed by a valid code block" + */ + + Step(); // @ + DiscardCurrentLexeme(); + Step(); + + if (c == '{') + { + DiscardCurrentLexeme(); + ParseCodeBlock(); + } + else if (c == '(') + { + DiscardCurrentLexeme(); + } + else if (c == ':') + { + DiscardCurrentLexeme(); + + } + else if (IsAlpha(c)) + { + ParseImplicitExpression(); + } + else + { + // [todo] report err, synchronise + } + } + + ImplicitExpressionTypes Str2ImplicitExprType(string str) + { + if (AllowedTransitionKeywords.Contains(str)) + { + return ImplicitExpressionTypes.AllowedKeyword; + } + + if (BannedTransitionKeywords.Contains(str)) + { + return ImplicitExpressionTypes.BannedKeyword; + } + + return ImplicitExpressionTypes.Literal; + } + + void ParseImplicitExpression() + { + void ParseLiteral() + { + while (true) + { + if (!IsAlphaNumeric(Peek())) + { + break; + } + + Step(); + } + } + void ParseLiteralStartsWithAlpha() + { + bool first = true; + + while (true) + { + if (first) + { + if (!IsAlpha(Peek())) + { + break; + } + + first = false; + } + else if (!IsAlphaNumeric(Peek())) + { + break; + } + + Step(); + } + } + void ParseUntilBalancedChar(char startBr, char endBr) + { + int inbalance = 0; + while (!IsAtEnd()) + { + Step(); + if (c == startBr) + { + inbalance++; + } + else if (c == endBr) + { + inbalance--; + if (inbalance <= 0) + { + break; + } + } + } + } + + while (!IsAtEnd()) + { + ParseLiteralStartsWithAlpha(); + + char bufC = Peek(); + if (bufC == '[') + { + ParseUntilBalancedChar('[', ']'); + } + else if (bufC == '(') + { + ParseUntilBalancedChar('(', ')'); + } + else if (bufC == '.') + { + Step(); + ParseLiteralStartsWithAlpha(); + } + + bufC = Peek(); + if (bufC is not ('.' or '(' or '[')) + { + break; + } + } + + + string buffer = GetCurrentLexeme(); + ImplicitExpressionTypes bufferType = Str2ImplicitExprType(buffer); + + if (bufferType == ImplicitExpressionTypes.Literal) + { + AddToken(TokenTypes.ImplicitExpr); + ParseClient(); + } + } + + /* Here the goal is to find the matching end of transition expression + * and recursively call ourselfs if we encounter a client transfer expression + * We need to understand a subset of ws grammar for this + * - comments (//, multiline) + * - strings ('', "", ``) + * --------- + * We can enter server side in two situations + * - looking for a } in case we've entered from a code block + * - looking for a ) when entered from an explicit expression + */ + void ParseCodeBlock() + { + c = Step(); + int missingBraces = 1; + + while (!IsAtEnd()) + { + c = Step(); + + if (c == '}') + { + missingBraces--; + if (missingBraces <= 0) + { + currentLexeme = currentLexeme.Substring(0, currentLexeme.Length - 1); + AddToken(TokenTypes.BlockExpr); + ParseClient(); + return; + } + } + else if (c == '{') + { + missingBraces++; + } + } + } + + string GetCurrentLexeme() + { + return currentLexeme; + } + + bool IsAlphaNumeric(char ch) + { + return !IsAtEnd() && (IsDigit(ch) || IsAlpha(ch)); + } + + bool IsAlpha(char ch) + { + return !IsAtEnd() && (char.IsLetter(ch) || ch is '_'); + } + + bool IsDigit(char ch) + { + return !IsAtEnd() && (ch is >= '0' and <= '9'); + } + + char Step(int i = 1) + { + char cc = Source[pos]; + currentLexeme += cc; + pos += i; + c = cc; + return cc; + } + + char Peek(int i = 1) + { + if (IsAtEnd()) + { + return '\n'; + } + + int peekedPos = pos + i - 1; + + if (peekedPos < 0) + { + pos = 0; + } + + if (Source.Length <= peekedPos) + { + return Source[^1]; + } + + return Source[peekedPos]; + } + + bool Match(char expected) + { + if (IsAtEnd()) + { + return false; + } + + if (Source[pos] != expected) + { + return false; + } + + Step(); + return true; + } + + void AddToken(TokenTypes type) + { + if (currentLexeme.Length > 0) + { + Token token = new Token(type, currentLexeme, null, line); + Tokens.Add(token); + DiscardCurrentLexeme(); + } + } + + void DiscardCurrentLexeme() + { + currentLexeme = ""; + } + + void Error(int line, string message) + { + anyErrors = true; + Messages.Add($"Error at line {line} - {message}"); + } + + return Tokens; + } +} \ No newline at end of file diff --git a/src/WattleScript.Templating/WattleScript.Templating.csproj b/src/WattleScript.Templating/WattleScript.Templating.csproj new file mode 100644 index 00000000..eb2460e9 --- /dev/null +++ b/src/WattleScript.Templating/WattleScript.Templating.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/1-call-chain.lua b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/1-call-chain.lua new file mode 100644 index 00000000..7b342ce2 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/1-call-chain.lua @@ -0,0 +1,9 @@ +acc = { + myTbl: [10] +} + +fn = () => { +return [ [acc] ] +} + +print(fn()[0][0].myTbl[0]); \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/1-call-chain.txt b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/1-call-chain.txt new file mode 100644 index 00000000..9a037142 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/1-call-chain.txt @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLikeTestRunner.cs b/src/WattleScript.Tests/EndToEnd/CLikeTestRunner.cs index 4edb58b5..849c25f8 100644 --- a/src/WattleScript.Tests/EndToEnd/CLikeTestRunner.cs +++ b/src/WattleScript.Tests/EndToEnd/CLikeTestRunner.cs @@ -57,7 +57,7 @@ public async Task RunCore(string path, bool reportErrors = false) if (path.Contains("SyntaxCLike")) { - script.Options.Syntax = ScriptSyntax.CLike; + script.Options.Syntax = ScriptSyntax.WattleScript; } if (reportErrors) diff --git a/src/WattleScript.Tests/EndToEnd/CSyntaxTests.cs b/src/WattleScript.Tests/EndToEnd/CSyntaxTests.cs index fd2c222d..b7e4d259 100644 --- a/src/WattleScript.Tests/EndToEnd/CSyntaxTests.cs +++ b/src/WattleScript.Tests/EndToEnd/CSyntaxTests.cs @@ -11,7 +11,7 @@ public class CSyntaxTests DynValue RunScript(string source) { var script = new Script(); - script.Options.Syntax = ScriptSyntax.CLike; + script.Options.Syntax = ScriptSyntax.WattleScript; return script.DoString(source); } @@ -38,7 +38,7 @@ function if_2(a, b) { assert.pass(); else assert.fail();", - s => s.Options.Syntax = ScriptSyntax.CLike); + s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -56,7 +56,7 @@ public void ForEach() assert.areequal('a', v); break; } - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -69,7 +69,7 @@ public void ForRange() values[x] = x; } assert.areequal(10, #values); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -114,7 +114,7 @@ public void CFor() if(a == 10) break; } assert.areequal(10, a, 'empty for'); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -126,14 +126,14 @@ public void SquareBracketTable() assert.areequal(3, table[3][1], 'nested list'); assert.areequal(5, table[4][1], 'nested list 2'); assert.areequal(5, table['g'], 'map field'); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] public void UsingDirective() { var s = new Script(); - s.Options.Syntax = ScriptSyntax.CLike; + s.Options.Syntax = ScriptSyntax.WattleScript; s.Options.Directives.Add("using"); var chunk = s.LoadString("using a.b.c;"); var a = chunk.Function.Annotations[0]; @@ -145,7 +145,7 @@ public void UsingDirective() public void ChunkAnnotations() { var s = new Script(); - s.Options.Syntax = ScriptSyntax.CLike; + s.Options.Syntax = ScriptSyntax.WattleScript; var chunk = s.LoadString(@" @@number (1.0) @@string ('hello') @@ -186,7 +186,7 @@ public void ChunkAnnotations() public void FunctionAnnotations() { var s = new Script(); - s.Options.Syntax = ScriptSyntax.CLike; + s.Options.Syntax = ScriptSyntax.WattleScript; s.DoString(@" @bind ({name: 'hello', value: 10 }) function myfunc(args) @@ -237,7 +237,7 @@ public void Continue() a = 5; } assert.areequal(3, a, 'numeric for'); -", s => s.Options.Syntax = ScriptSyntax.CLike); +", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -251,7 +251,7 @@ public void CFor_Closure() assert.areequal(1, tbl[1]()); assert.areequal(2, tbl[2]()); assert.areequal(3, tbl[3]()); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -261,7 +261,7 @@ public void CallDefaultFunc() f = (x = () => {print('yes')}) => { x() } - f();", s => s.Options.Syntax = ScriptSyntax.CLike); + f();", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -277,7 +277,7 @@ function getstring() { assert.areequal('hello world', getstring() + ' world'); assert.areequal('pancakes123', 'pancakes' + 123); assert.areequal('t is TABLE', 't is ' + t); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } @@ -291,7 +291,7 @@ function andfunc(a, b) { } assert.isfalse(andfunc(true, false), 'true && false') assert.istrue(andfunc(true, true), 'true && true')", - s => s.Options.Syntax = ScriptSyntax.CLike); + s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -303,7 +303,7 @@ function orfunc(a, b) { } assert.istrue(orfunc(true, false), 'true || false') assert.isfalse(orfunc(false, false), 'false && false')", - s => s.Options.Syntax = ScriptSyntax.CLike); + s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -334,7 +334,7 @@ public void CompoundAssignLcl() //assert.areequal(4, f, '^='); assert.areequal(1, g, '%='); assert.areequal('abc', h, '..='); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -350,7 +350,7 @@ public void DoLoop() a--; } while (a > 0); assert.areequal(0, a); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -368,7 +368,7 @@ public void CompoundAssignIndex() assert.areequal(6, tbl[3], '*= (table)'); assert.areequal(1, tbl[4], '%= (table)'); assert.areequal('abc', tbl[5], '..= (table)'); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -381,7 +381,7 @@ public void PrefixIncDec() var t = { 1 } assert.areequal(2, ++t[1], '++ (table)'); assert.areequal(1, --t[1], '-- (table)'); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -395,7 +395,7 @@ public void WhileLoop() assert.areequal(0, a); while (true) if (a > 5) break; else a++; assert.areequal(6, a);", - s => s.Options.Syntax = ScriptSyntax.CLike); + s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -413,7 +413,7 @@ public void PostfixIncDec() assert.areequal(2, t[1], 'after ++ (table)'); assert.areequal(2, t[1]--, '-- (table)'); assert.areequal(1, t[1], 'after -- (table)'); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -428,7 +428,7 @@ public void Ternary() assert.areequal(1, getfalse() ? 2 : 1, '? false func');", s => { - s.Options.Syntax = ScriptSyntax.CLike; + s.Options.Syntax = ScriptSyntax.WattleScript; s.Globals["gettrue"] = (Func)(() => true); s.Globals["getfalse"] = (Func) (() => false); }); @@ -447,7 +447,7 @@ public void CMultilineComment() */ assert.areequal(4, a); assert.areequal(3, b); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -459,7 +459,7 @@ goto fin a = 2; fin: assert.areequal(7, a); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -471,7 +471,7 @@ public void NilCoalesce() assert.areequal(2.0, 2.0 ?? 1.0); assert.areequal(1.0, tbl[3] ?? 1.0); assert.areequal(1.0, tbl[1] ?? 2.0); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -484,7 +484,7 @@ public void NilCheck() assert.areequal(nil, tbl?.x?.y()?.z); assert.areequal(nil, tbl['hello']?[2]); assert.areequal(2.0, tbl[asdf?['hello'] ?? 'extra']); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -496,7 +496,7 @@ public void LengthProperty() tbl['length'] = 1; assert.areequal(2, tbl.length); assert.areequal(1, tbl['length']); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -504,7 +504,7 @@ public void LengthStringLiteral() { TestScript.Run(@" assert.areequal(5, 'hello'.length); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -515,20 +515,20 @@ public void LengthPropertyReadonly() TestScript.Run(@" table = {} table.length = 3; - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); }); } [Test] public void NestedStringTemplate() { - TestScript.Run(@"assert.areequal('hello', ``{``hello``}``);", s => s.Options.Syntax = ScriptSyntax.CLike); + TestScript.Run(@"assert.areequal('hello', ``{``hello``}``);", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] public void EscapeStringTemplate() { - TestScript.Run(@"assert.areequal('${hello}', ``$\{hello}``);", s => s.Options.Syntax = ScriptSyntax.CLike); + TestScript.Run(@"assert.areequal('${hello}', ``$\{hello}``);", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -543,7 +543,7 @@ public void StringTemplate() }) //7 }``); assert.areequal('hello world', ``{'hello'} {'world'}``); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } @@ -554,7 +554,7 @@ public void BitNot() function bnot(a) { return ~a }; assert.areequal(-4096, ~4095); assert.areequal(-4096, bnot(4095)); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -564,7 +564,7 @@ public void BitAnd() function band(a,b) { return a & b }; assert.areequal(255, 4095 & 255); assert.areequal(255, band(4095, 255)); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -574,7 +574,7 @@ public void BitOr() function bor(a,b) { return a | b }; assert.areequal(1279, 1024 | 255); assert.areequal(1279, bor(1024, 255)); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -584,7 +584,7 @@ public void BitXor() function bxor(a,b) { return a ^ b }; assert.areequal(3840, 4095 ^ 255); assert.areequal(3840, bxor(4095, 255)); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -594,7 +594,7 @@ public void BitLShift() function blshift(a,b) { return a << b }; assert.areequal(64, 16 << 2); assert.areequal(64, blshift(16,2)); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -604,7 +604,7 @@ public void BitRShiftA() function brshifta(a,b) { return a >> b }; assert.areequal(-256, -1024 >> 2); assert.areequal(-256, brshifta(-1024, 2)); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] @@ -614,7 +614,7 @@ public void BitRShiftL() function brshiftl(a,b) { return a >>> b }; assert.areequal(0x3FFFFF00, -1024 >>> 2); assert.areequal(0x3FFFFF00, brshiftl(-1024, 2)); - ", s => s.Options.Syntax = ScriptSyntax.CLike); + ", s => s.Options.Syntax = ScriptSyntax.WattleScript); } @@ -622,7 +622,7 @@ public void BitRShiftL() public void Not() { TestScript.Run(@"assert.istrue(!false); assert.isfalse(!true);", - s => s.Options.Syntax = ScriptSyntax.CLike); + s => s.Options.Syntax = ScriptSyntax.WattleScript); } [Test] diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs new file mode 100644 index 00000000..d044c651 --- /dev/null +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using WattleScript.Templating; + +namespace WattleScript.Interpreter.Tests.Templating; + +public class TemplatingTestsRunner +{ + private const string ROOT_FOLDER = "Templating/Tests"; + + static string[] GetTestCases() + { + string[] files = Directory.GetFiles(ROOT_FOLDER, "*.wthtml*", SearchOption.AllDirectories); + return files; + } + + [Test, TestCaseSource(nameof(GetTestCases))] + public async Task RunThrowErros(string path) + { + await RunCore(path); + } + + public async Task RunCore(string path, bool reportErrors = false) + { + string outputPath = path.Replace(".wthtml", ".html"); + + if (!File.Exists(outputPath)) + { + Assert.Inconclusive($"Missing output file for test {path}"); + return; + } + + string code = await File.ReadAllTextAsync(path); + string output = await File.ReadAllTextAsync(outputPath); + StringBuilder stdOut = new StringBuilder(); + + Template tmp = new Template(); + string transpiled = tmp.Render(code); + + Script script = new Script(CoreModules.Preset_HardSandbox); + script.Options.DebugPrint = s => stdOut.AppendLine(s); + script.Options.IndexTablesFrom = 0; + script.Options.AnnotationPolicy = new CustomPolicy(AnnotationValueParsingPolicy.ForceTable); + script.Options.Syntax = ScriptSyntax.WattleScript; + + void PrintLine(Script script, CallbackArguments args) + { + stdOut.AppendLine(args[0].CastToString()); + } + + void Print(Script script, CallbackArguments args) + { + stdOut.Append(args[0].CastToString()); + } + + script.Globals["stdout_line"] = PrintLine; + script.Globals["stdout"] = Print; + + if (path.Contains("flaky")) + { + Assert.Inconclusive($"Test {path} marked as flaky"); + return; + } + + if (path.Contains("SyntaxCLike")) + { + script.Options.Syntax = ScriptSyntax.WattleScript; + } + + if (reportErrors) + { + script.Options.ParserErrorMode = ScriptOptions.ParserErrorModes.Report; + await script.DoStringAsync(transpiled); + return; + } + + try + { + DynValue dv = script.LoadString(transpiled); + await script.CallAsync(dv); + + Assert.AreEqual(output, stdOut.ToString(), $"Test {path} did not pass."); + + if (path.Contains("invalid")) + { + Assert.Fail("Expected to crash but 'passed'"); + } + } + catch (Exception e) + { + if (e is AssertionException ae) + { + Assert.Fail($"Test {path} did not pass.\nMessage: {ae.Message}\n{ae.StackTrace}"); + return; + } + + if (path.Contains("invalid")) + { + Assert.Pass($"Crashed as expected: {e.Message}"); + return; + } + + Assert.Fail($"Test {path} did not pass.\nMessage: {e.Message}\n{e.StackTrace}"); + } + } +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/1-simple.html b/src/WattleScript.Tests/Templating/Tests/1-simple.html new file mode 100644 index 00000000..a8ea4b8d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/1-simple.html @@ -0,0 +1 @@ +
10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/1-simple.wthtml new file mode 100644 index 00000000..40cc42d0 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/1-simple.wthtml @@ -0,0 +1,4 @@ +@{ + x = 10 +} +
@x
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/2-simple-newline.html b/src/WattleScript.Tests/Templating/Tests/2-simple-newline.html new file mode 100644 index 00000000..77d09894 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/2-simple-newline.html @@ -0,0 +1,3 @@ +
+ 10 +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/2-simple-newline.wthtml b/src/WattleScript.Tests/Templating/Tests/2-simple-newline.wthtml new file mode 100644 index 00000000..3e8dd8c9 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/2-simple-newline.wthtml @@ -0,0 +1,6 @@ +@{ + x = 10 +} +
+ @x +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/3-simple-table.html b/src/WattleScript.Tests/Templating/Tests/3-simple-table.html new file mode 100644 index 00000000..af507e77 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/3-simple-table.html @@ -0,0 +1 @@ +
hello world
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/3-simple-table.wthtml b/src/WattleScript.Tests/Templating/Tests/3-simple-table.wthtml new file mode 100644 index 00000000..c2b9946e --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/3-simple-table.wthtml @@ -0,0 +1,4 @@ +@{ + tbl = ["hello world"] +} +
@tbl[0]
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/4-nested-table.html b/src/WattleScript.Tests/Templating/Tests/4-nested-table.html new file mode 100644 index 00000000..af507e77 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/4-nested-table.html @@ -0,0 +1 @@ +
hello world
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/4-nested-table.wthtml b/src/WattleScript.Tests/Templating/Tests/4-nested-table.wthtml new file mode 100644 index 00000000..cb0ba923 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/4-nested-table.wthtml @@ -0,0 +1,4 @@ +@{ + tbl = [ ["hello world"] ] +} +
@tbl[0][0]
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/5-call.html b/src/WattleScript.Tests/Templating/Tests/5-call.html new file mode 100644 index 00000000..a8ea4b8d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/5-call.html @@ -0,0 +1 @@ +
10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/5-call.wthtml b/src/WattleScript.Tests/Templating/Tests/5-call.wthtml new file mode 100644 index 00000000..5f4ae3a6 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/5-call.wthtml @@ -0,0 +1,10 @@ +@{ + acc = { + myTbl: [10] + } + + fn = () => { + return [[acc]] + } +} +
@fn(((("param"))), [1, 2, acc.myTbl[0]])[0][0].myTbl[0]
\ No newline at end of file diff --git a/src/WattleScript.Tests/WattleScript.Tests.csproj b/src/WattleScript.Tests/WattleScript.Tests.csproj index 7f0a0f9a..aec44e9e 100644 --- a/src/WattleScript.Tests/WattleScript.Tests.csproj +++ b/src/WattleScript.Tests/WattleScript.Tests.csproj @@ -16,6 +16,7 @@ + @@ -24,6 +25,9 @@ Always + + Always + diff --git a/src/WattleScript.sln b/src/WattleScript.sln index 2c1d908a..fe81b518 100644 --- a/src/WattleScript.sln +++ b/src/WattleScript.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WattleScript.HardwireGen", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WattleScript.Tests", "WattleScript.Tests\WattleScript.Tests.csproj", "{416B592B-1943-4379-8824-5D9EC62484A7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WattleScript.Templating", "WattleScript.Templating\WattleScript.Templating.csproj", "{485637E6-8087-460B-A636-F8ECFE273770}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -86,5 +88,17 @@ Global {416B592B-1943-4379-8824-5D9EC62484A7}.Release|x64.Build.0 = Release|Any CPU {416B592B-1943-4379-8824-5D9EC62484A7}.Release|x86.ActiveCfg = Release|Any CPU {416B592B-1943-4379-8824-5D9EC62484A7}.Release|x86.Build.0 = Release|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Debug|Any CPU.Build.0 = Debug|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Debug|x64.ActiveCfg = Debug|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Debug|x64.Build.0 = Debug|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Debug|x86.ActiveCfg = Debug|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Debug|x86.Build.0 = Debug|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Release|Any CPU.ActiveCfg = Release|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Release|Any CPU.Build.0 = Release|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Release|x64.ActiveCfg = Release|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Release|x64.Build.0 = Release|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Release|x86.ActiveCfg = Release|Any CPU + {485637E6-8087-460B-A636-F8ECFE273770}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From f6890d66ba8a993b1570bbfc492be5d305742140 Mon Sep 17 00:00:00 2001 From: lofcz Date: Fri, 15 Apr 2022 14:44:39 +0200 Subject: [PATCH 02/74] Progress on implicit expr --- src/WattleScript.Templating/Tokenizer.cs | 349 ++++++++++++------ .../CLike/SyntaxCLike/Misc/2-length.lua | 1 + .../CLike/SyntaxCLike/Misc/2-length.txt | 1 + .../AllowedKeyword/1-if.html} | 0 .../ImplicitExpr/AllowedKeyword/1-if.wthtml | 3 + .../Literal/1-simple.html} | 0 .../Literal}/1-simple.wthtml | 0 .../Literal}/2-simple-newline.html | 0 .../Literal}/2-simple-newline.wthtml | 0 .../Literal}/3-simple-table.html | 0 .../Literal}/3-simple-table.wthtml | 0 .../Literal}/4-nested-table.html | 0 .../Literal}/4-nested-table.wthtml | 0 .../Tests/ImplicitExpr/Literal/5-call.html | 1 + .../{ => ImplicitExpr/Literal}/5-call.wthtml | 0 .../ImplicitExpr/Literal/6-single-dot.html | 1 + .../ImplicitExpr/Literal/6-single-dot.wthtml | 4 + .../Literal/7-single-dot-eof.html | 1 + .../Literal/7-single-dot-eof.wthtml | 4 + .../ImplicitExpr/Literal/8-multiple-dots.html | 1 + .../Literal/8-multiple-dots.wthtml | 4 + 21 files changed, 248 insertions(+), 122 deletions(-) create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/2-length.lua create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/2-length.txt rename src/WattleScript.Tests/Templating/Tests/{1-simple.html => ImplicitExpr/AllowedKeyword/1-if.html} (100%) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.wthtml rename src/WattleScript.Tests/Templating/Tests/{5-call.html => ImplicitExpr/Literal/1-simple.html} (100%) rename src/WattleScript.Tests/Templating/Tests/{ => ImplicitExpr/Literal}/1-simple.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/{ => ImplicitExpr/Literal}/2-simple-newline.html (100%) rename src/WattleScript.Tests/Templating/Tests/{ => ImplicitExpr/Literal}/2-simple-newline.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/{ => ImplicitExpr/Literal}/3-simple-table.html (100%) rename src/WattleScript.Tests/Templating/Tests/{ => ImplicitExpr/Literal}/3-simple-table.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/{ => ImplicitExpr/Literal}/4-nested-table.html (100%) rename src/WattleScript.Tests/Templating/Tests/{ => ImplicitExpr/Literal}/4-nested-table.wthtml (100%) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/5-call.html rename src/WattleScript.Tests/Templating/Tests/{ => ImplicitExpr/Literal}/5-call.wthtml (100%) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/6-single-dot.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/6-single-dot.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/7-single-dot-eof.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/7-single-dot-eof.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/8-multiple-dots.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/8-multiple-dots.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 7b2044f1..e6b82870 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -14,16 +14,166 @@ enum ImplicitExpressionTypes private List Messages { get; set; } = new List(); private List AllowedTransitionKeywords = new List() {"if", "for", "do", "while", "require", "function"}; private List BannedTransitionKeywords = new List() {"else", "elseif"}; - + private Dictionary?> KeywordsMap; private Token LastToken => Tokens[^1]; + int pos = 0; + char c; + string currentLexeme = ""; + int line = 1; + + public Tokenizer() + { + KeywordsMap = new Dictionary?> + { + { "if", ParseKeywordIf } + }; + } + + bool IsAtEnd() + { + return pos >= Source.Length; + } + + string GetCurrentLexeme() + { + return currentLexeme; + } + + bool IsAlphaNumeric(char ch) + { + return !IsAtEnd() && (IsDigit(ch) || IsAlpha(ch)); + } + + bool IsAlpha(char ch) + { + return !IsAtEnd() && (char.IsLetter(ch) || ch is '_'); + } + + bool IsDigit(char ch) + { + return !IsAtEnd() && (ch is >= '0' and <= '9'); + } + + char Step(int i = 1) + { + char cc = Source[pos]; + currentLexeme += cc; + pos += i; + c = cc; + return cc; + } + + char Peek(int i = 1) + { + if (IsAtEnd()) + { + return '\n'; + } + + int peekedPos = pos + i - 1; + + if (peekedPos < 0) + { + pos = 0; + } + + if (Source.Length <= peekedPos) + { + return Source[^1]; + } + + return Source[peekedPos]; + } + + bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced) + { + int inbalance = startsInbalanced ? 1 : 0; + while (!IsAtEnd()) + { + Step(); + if (c == startBr) + { + inbalance++; + } + else if (c == endBr) + { + inbalance--; + if (inbalance <= 0) + { + return true; + } + } + } + + return false; + } + + bool Match(char expected) + { + if (IsAtEnd()) + { + return false; + } + + if (Source[pos] != expected) + { + return false; + } + + Step(); + return true; + } + + bool MatchNextNonWhiteSpaceChar(char ch) + { + while (!IsAtEnd()) + { + if (Peek() == ' ') + { + Step(); + } + else if (Peek() == ch) + { + Step(); + return true; + } + else + { + return false; + } + } + + return false; + } + + // if (expr) {} + bool ParseKeywordIf() + { + bool openBrkMatched = MatchNextNonWhiteSpaceChar('('); + string l = GetCurrentLexeme(); + + if (!openBrkMatched) + { + return false; + } + + bool endExprMatched = ParseUntilBalancedChar('(', ')', true); + l = GetCurrentLexeme(); + + if (!endExprMatched) + { + return false; + } + + //bool openBlockBrkMatched = MatchNextNonWhiteSpaceChar('{'); + ParseCodeBlock(); + + return false; + } public List Tokenize(string source, bool includeNewlines = false) { - int line = 1; - int pos = 0; bool tokenize = true; - char c; - string currentLexeme = ""; bool anyErrors = false; Source = source; @@ -40,14 +190,16 @@ public List Tokenize(string source, bool includeNewlines = false) tokenize = false; } - - bool IsAtEnd() + void Error(int line, string message) { - return pos >= Source.Length; + anyErrors = true; + Messages.Add($"Error at line {line} - {message}"); } + return Tokens; + } - /* In client mode everything is a literal + /* In client mode everything is a literal * until we encouter @ * then we lookahead at next char and if it's not another @ (escape) * we enter server mode @@ -144,6 +296,7 @@ void ParseLiteral() Step(); } } + void ParseLiteralStartsWithAlpha() { bool first = true; @@ -167,42 +320,35 @@ void ParseLiteralStartsWithAlpha() Step(); } } - void ParseUntilBalancedChar(char startBr, char endBr) - { - int inbalance = 0; - while (!IsAtEnd()) - { - Step(); - if (c == startBr) - { - inbalance++; - } - else if (c == endBr) - { - inbalance--; - if (inbalance <= 0) - { - break; - } - } - } - } while (!IsAtEnd()) { ParseLiteralStartsWithAlpha(); + string firstPart = GetCurrentLexeme(); + ImplicitExpressionTypes firstPartType = Str2ImplicitExprType(firstPart); + + if (firstPartType is ImplicitExpressionTypes.AllowedKeyword or ImplicitExpressionTypes.BannedKeyword) + { + ParseKeyword(); + return; + } char bufC = Peek(); if (bufC == '[') { - ParseUntilBalancedChar('[', ']'); + ParseUntilBalancedChar('[', ']', false); } else if (bufC == '(') { - ParseUntilBalancedChar('(', ')'); + ParseUntilBalancedChar('(', ')', false); } else if (bufC == '.') { + if (!IsAlpha(Peek(2))) // next char after . has to be alpha else the dot itself is client side + { + break; + } + Step(); ParseLiteralStartsWithAlpha(); } @@ -213,15 +359,18 @@ void ParseUntilBalancedChar(char startBr, char endBr) break; } } + + AddToken(TokenTypes.ImplicitExpr); + ParseClient(); + } + void ParseKeyword() + { + string keyword = GetCurrentLexeme(); - string buffer = GetCurrentLexeme(); - ImplicitExpressionTypes bufferType = Str2ImplicitExprType(buffer); - - if (bufferType == ImplicitExpressionTypes.Literal) + if (KeywordsMap.TryGetValue(keyword, out Func? resolver)) { - AddToken(TokenTypes.ImplicitExpr); - ParseClient(); + resolver?.Invoke(); } } @@ -237,98 +386,62 @@ void ParseUntilBalancedChar(char startBr, char endBr) */ void ParseCodeBlock() { - c = Step(); - int missingBraces = 1; - - while (!IsAtEnd()) - { - c = Step(); - - if (c == '}') - { - missingBraces--; - if (missingBraces <= 0) - { - currentLexeme = currentLexeme.Substring(0, currentLexeme.Length - 1); - AddToken(TokenTypes.BlockExpr); - ParseClient(); - return; - } - } - else if (c == '{') - { - missingBraces++; - } - } - } - - string GetCurrentLexeme() - { - return currentLexeme; - } + bool matchedOpenBrk = MatchNextNonWhiteSpaceChar('{'); + string l = GetCurrentLexeme(); + AddToken(TokenTypes.BlockExpr); - bool IsAlphaNumeric(char ch) - { - return !IsAtEnd() && (IsDigit(ch) || IsAlpha(ch)); + ParseUntilHtmlOrClientTransition(); } - bool IsAlpha(char ch) + bool LastStoredCharNotWhitespaceMatches(params char[] chars) { - return !IsAtEnd() && (char.IsLetter(ch) || ch is '_'); - } - - bool IsDigit(char ch) - { - return !IsAtEnd() && (ch is >= '0' and <= '9'); - } - - char Step(int i = 1) - { - char cc = Source[pos]; - currentLexeme += cc; - pos += i; - c = cc; - return cc; - } - - char Peek(int i = 1) - { - if (IsAtEnd()) - { - return '\n'; - } - - int peekedPos = pos + i - 1; - - if (peekedPos < 0) + string str = GetCurrentLexeme(); + for (int i = str.Length; i > 0; i--) { - pos = 0; - } + char cc = str[i - 1]; + if (cc == ' ') + { + continue; + } - if (Source.Length <= peekedPos) - { - return Source[^1]; + return chars.Contains(cc); } - - return Source[peekedPos]; + + return false; } - bool Match(char expected) + /* A point of transition can be + * 0) @@ -344,12 +457,4 @@ void DiscardCurrentLexeme() currentLexeme = ""; } - void Error(int line, string message) - { - anyErrors = true; - Messages.Add($"Error at line {line} - {message}"); - } - - return Tokens; - } } \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/2-length.lua b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/2-length.lua new file mode 100644 index 00000000..43dc863e --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/2-length.lua @@ -0,0 +1 @@ +print([1, 2, 3].length); \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/2-length.txt b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/2-length.txt new file mode 100644 index 00000000..e440e5c8 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Misc/2-length.txt @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/1-simple.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/1-simple.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.wthtml new file mode 100644 index 00000000..bbb3b6c9 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.wthtml @@ -0,0 +1,3 @@ +@if (2 > 1) { +
10
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/5-call.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/1-simple.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/5-call.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/1-simple.html diff --git a/src/WattleScript.Tests/Templating/Tests/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/1-simple.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/1-simple.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/1-simple.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/2-simple-newline.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/2-simple-newline.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/2-simple-newline.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/2-simple-newline.html diff --git a/src/WattleScript.Tests/Templating/Tests/2-simple-newline.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/2-simple-newline.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/2-simple-newline.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/2-simple-newline.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/3-simple-table.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/3-simple-table.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/3-simple-table.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/3-simple-table.html diff --git a/src/WattleScript.Tests/Templating/Tests/3-simple-table.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/3-simple-table.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/3-simple-table.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/3-simple-table.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/4-nested-table.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/4-nested-table.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/4-nested-table.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/4-nested-table.html diff --git a/src/WattleScript.Tests/Templating/Tests/4-nested-table.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/4-nested-table.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/4-nested-table.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/4-nested-table.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/5-call.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/5-call.html new file mode 100644 index 00000000..a8ea4b8d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/5-call.html @@ -0,0 +1 @@ +
10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/5-call.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/5-call.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/5-call.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/5-call.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/6-single-dot.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/6-single-dot.html new file mode 100644 index 00000000..5f6a0b25 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/6-single-dot.html @@ -0,0 +1 @@ +
10.
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/6-single-dot.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/6-single-dot.wthtml new file mode 100644 index 00000000..c1fa2b2f --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/6-single-dot.wthtml @@ -0,0 +1,4 @@ +@{ + x = 10 +} +
@x.
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/7-single-dot-eof.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/7-single-dot-eof.html new file mode 100644 index 00000000..52ce0fc3 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/7-single-dot-eof.html @@ -0,0 +1 @@ +
5. \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/7-single-dot-eof.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/7-single-dot-eof.wthtml new file mode 100644 index 00000000..d6411bd3 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/7-single-dot-eof.wthtml @@ -0,0 +1,4 @@ +@{ + x = 5 +} +
@x. \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/8-multiple-dots.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/8-multiple-dots.html new file mode 100644 index 00000000..d6eda7ac --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/8-multiple-dots.html @@ -0,0 +1 @@ +
5... \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/8-multiple-dots.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/8-multiple-dots.wthtml new file mode 100644 index 00000000..47aeacb0 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/8-multiple-dots.wthtml @@ -0,0 +1,4 @@ +@{ + x = 5 +} +
@x... \ No newline at end of file From 1d6facf526aac8d1126376cae4e661299e000339 Mon Sep 17 00:00:00 2001 From: lofcz Date: Mon, 18 Apr 2022 16:26:04 +0200 Subject: [PATCH 03/74] If-else nested passing --- src/WattleScript.Templating/Tokenizer.cs | 257 +++++++++++++++++- .../AllowedKeyword/2-if-else.html | 1 + .../AllowedKeyword/2-if-else.wthtml | 6 + .../AllowedKeyword/3-if-html.html | 3 + .../AllowedKeyword/3-if-html.wthtml | 5 + .../4-if-html-self-closing.html | 5 + .../4-if-html-self-closing.wthtml | 7 + .../AllowedKeyword/5-if-html-deep-nested.html | 10 + .../5-if-html-deep-nested.wthtml | 12 + .../AllowedKeyword/6-if-html-code-block.html | 3 + .../6-if-html-code-block.wthtml | 10 + .../AllowedKeyword/7-if-block.html | 3 + .../AllowedKeyword/7-if-block.wthtml | 8 + 13 files changed, 322 insertions(+), 8 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index e6b82870..8bb9177e 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -63,6 +63,25 @@ char Step(int i = 1) return cc; } + private int storedPos; + void StorePos() + { + storedPos = pos; + } + + void RestorePos() + { + pos = storedPos; + if (pos >= Source.Length) + { + pos = Source.Length - 1; + } + + storedPos = 0; + c = Source[pos]; + DiscardCurrentLexeme(); + } + char Peek(int i = 1) { if (IsAtEnd()) @@ -146,6 +165,28 @@ bool MatchNextNonWhiteSpaceChar(char ch) return false; } + bool MatchNextNonWhiteSpaceNonNewlineChar(char ch) + { + while (!IsAtEnd()) + { + if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') + { + Step(); + } + else if (Peek() == ch) + { + Step(); + return true; + } + else + { + return false; + } + } + + return false; + } + // if (expr) {} bool ParseKeywordIf() { @@ -166,10 +207,110 @@ bool ParseKeywordIf() } //bool openBlockBrkMatched = MatchNextNonWhiteSpaceChar('{'); - ParseCodeBlock(); + ParseCodeBlock(true); + + bool matchesElse = NextLiteralSkipEmptyCharsMatches("else"); + if (matchesElse) + { + ParseKeywordElse(); + } + + return false; + } + + // else {} + bool ParseKeywordElse() + { + ParseWhitespaceAndNewlines(); + //DiscardCurrentLexeme(); + + string elseStr = StepN(4); + + if (elseStr != "else") + { + return false; + } + + string str = GetCurrentLexeme(); + + ParseCodeBlock(true); return false; } + + string StepN(int steps) + { + string str = ""; + while (!IsAtEnd() && steps > 0) + { + char chr = Step(); + str += chr; + steps--; + } + + return str; + } + + bool ParseWhitespaceAndNewlines() + { + while (!IsAtEnd()) + { + if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') + { + Step(); + } + else + { + break; + } + + Step(); + } + + return true; + } + + bool NextLiteralSkipEmptyCharsMatches(string literal) + { + if (IsAtEnd()) + { + return false; + } + + StorePos(); + + bool started = false; + while (!IsAtEnd()) + { + if (!started) + { + if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') + { + Step(); + } + else + { + string str = GetCurrentLexeme(); + DiscardCurrentLexeme(); + started = true; + } + } + else + { + if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') + { + break; + } + } + + Step(); + } + + bool match = literal == GetCurrentLexeme(); + RestorePos(); + + return match; + } public List Tokenize(string source, bool includeNewlines = false) { @@ -199,7 +340,7 @@ void Error(int line, string message) return Tokens; } - /* In client mode everything is a literal + /* In client mode everything is a literal * until we encouter @ * then we lookahead at next char and if it's not another @ (escape) * we enter server mode @@ -214,7 +355,7 @@ void ParseClient() { AddToken(TokenTypes.ClientText); ParseTransition(); - return; + continue; } } @@ -246,7 +387,7 @@ void ParseTransition() if (c == '{') { DiscardCurrentLexeme(); - ParseCodeBlock(); + ParseCodeBlock(false); } else if (c == '(') { @@ -361,7 +502,6 @@ void ParseLiteralStartsWithAlpha() } AddToken(TokenTypes.ImplicitExpr); - ParseClient(); } void ParseKeyword() @@ -384,13 +524,32 @@ void ParseKeyword() * - looking for a } in case we've entered from a code block * - looking for a ) when entered from an explicit expression */ - void ParseCodeBlock() + void ParseCodeBlock(bool keepClosingBrk) { bool matchedOpenBrk = MatchNextNonWhiteSpaceChar('{'); string l = GetCurrentLexeme(); AddToken(TokenTypes.BlockExpr); + + while (!IsAtEnd()) + { + ParseUntilHtmlOrClientTransition(); + StorePos(); + bool matchedClosingBrk = MatchNextNonWhiteSpaceNonNewlineChar('}'); - ParseUntilHtmlOrClientTransition(); + if (matchedClosingBrk) + { + break; + } + + RestorePos(); + } + + l = GetCurrentLexeme(); + if (!keepClosingBrk) + { + currentLexeme = currentLexeme.Substring(0, currentLexeme.Length - 1); + } + AddToken(TokenTypes.BlockExpr); } bool LastStoredCharNotWhitespaceMatches(params char[] chars) @@ -415,6 +574,8 @@ bool LastStoredCharNotWhitespaceMatches(params char[] chars) */ void ParseUntilHtmlOrClientTransition() { + int missingBrks = 1; + while (!IsAtEnd()) { if (Peek() == '<') @@ -423,6 +584,19 @@ void ParseUntilHtmlOrClientTransition() { AddToken(TokenTypes.BlockExpr); ParseHtmlTag(); + return; + } + } + else if (Peek() == '{') + { + missingBrks++; + } + else if (Peek() == '}') + { + missingBrks--; + if (missingBrks <= 0) + { + break; } } @@ -433,13 +607,80 @@ void ParseUntilHtmlOrClientTransition() void ParseHtmlTag() { - char chr = Step(); + char chr = Step(); // has to be < // First char in a proper HTML tag (after opening <) can be [_, !, /, Alpha] // If we have something else we can consider it a malformated tag // Razor renders first char = NUM as an error but compiles // If it is some other char, Razor throws + ParseUntilBalancedChar('<', '>', true); + string s = GetCurrentLexeme(); + + if (!CurrentLexemeIsSelfClosingHtmlTag()) + { + ParseHtmlOrPlaintextUntilClosingTag(); + ParseHtmlClosingTag(); + } + s = GetCurrentLexeme(); + AddToken(TokenTypes.ClientText); + } + + void ParseHtmlClosingTag() + { + ParseUntilBalancedChar('<', '>', false); + string str = GetCurrentLexeme(); + } + + void ParseHtmlOrPlaintextUntilClosingTag() + { + int missingBrks = 1; + + string s = GetCurrentLexeme(); + while (!IsAtEnd()) + { + if (Peek() == '@') + { + if (Peek(2) != '@') + { + AddToken(TokenTypes.ClientText); + ParseTransition(); + continue; + } + } + else if (Peek() == '<') + { + if (IsAlpha(Peek(2))) // we enter new tag + { + ParseHtmlTag(); + } + else if (Peek(2) == '/' && IsAlpha(Peek(3))) + { + missingBrks--; + } + + if (missingBrks <= 0) + { + break; + } + } + else if (Peek() == '>') + { + if (IsAlpha(Peek(2))) + { + missingBrks++; + } + } + + Step(); + } + + s = GetCurrentLexeme(); + } + + bool CurrentLexemeIsSelfClosingHtmlTag() + { + return GetCurrentLexeme().EndsWith("/>"); } void AddToken(TokenTypes type) diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.html new file mode 100644 index 00000000..5053de3d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.html @@ -0,0 +1 @@ +
5
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.wthtml new file mode 100644 index 00000000..82697417 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.wthtml @@ -0,0 +1,6 @@ +@if (1 > 2) { +
10
+} +else { +
5
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.html new file mode 100644 index 00000000..c2c7f798 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.html @@ -0,0 +1,3 @@ +
+ 10 +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.wthtml new file mode 100644 index 00000000..13995964 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.wthtml @@ -0,0 +1,5 @@ +@if (2 > 1) { +
+ 10 +
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.html new file mode 100644 index 00000000..2c294a62 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.html @@ -0,0 +1,5 @@ +
+ 10 + + 20 +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.wthtml new file mode 100644 index 00000000..d35c6d1f --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.wthtml @@ -0,0 +1,7 @@ +@if (2 > 1) { +
+ 10 + + 20 +
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.html new file mode 100644 index 00000000..17035366 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.html @@ -0,0 +1,10 @@ +
+
+
+ +
1
+
2
+
+
+
+
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.wthtml new file mode 100644 index 00000000..3c8b8542 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.wthtml @@ -0,0 +1,12 @@ +@if (2 > 1) { +
+
+
+ +
1
+
2
+
+
+
+
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.html new file mode 100644 index 00000000..abef679d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.html @@ -0,0 +1,3 @@ +
+
2
+
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.wthtml new file mode 100644 index 00000000..efc94cdc --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.wthtml @@ -0,0 +1,10 @@ +@if (2 > 1) { +
+ @if (3 > 10) { +
1
+ } + else { +
2
+ } +
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.html new file mode 100644 index 00000000..ad68dc44 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.html @@ -0,0 +1,3 @@ +
    +
  • 20
  • +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.wthtml new file mode 100644 index 00000000..6d67d252 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.wthtml @@ -0,0 +1,8 @@ +@{ + x = 20 +} +
    +@if (x > 2) { +
  • @x
  • +} +
\ No newline at end of file From 1c2f5e62615406565103071785e758629103acfb Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 11:18:14 +0200 Subject: [PATCH 04/74] Begin work on explicit expressions --- src/WattleScript.Templating/Template.cs | 4 + src/WattleScript.Templating/Tokenizer.cs | 257 +++++++++++++++--- .../Tests/ExplicitExpr/1-simple.html | 1 + .../Tests/ExplicitExpr/1-simple.wthtml | 4 + .../Templating/Tests/ExplicitExpr/2-attr.html | 1 + .../Tests/ExplicitExpr/2-attr.wthtml | 1 + .../Tests/ExplicitExpr/3-attr-2.html | 1 + .../Tests/ExplicitExpr/3-attr-2.wthtml | 1 + .../Tests/ExplicitExpr/4-escape.html | 1 + .../Tests/ExplicitExpr/4-escape.wthtml | 1 + 10 files changed, 230 insertions(+), 42 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/1-simple.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/2-attr.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/2-attr.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/3-attr-2.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/3-attr-2.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/4-escape.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/4-escape.wthtml diff --git a/src/WattleScript.Templating/Template.cs b/src/WattleScript.Templating/Template.cs index 280b8f4a..6d7621ba 100644 --- a/src/WattleScript.Templating/Template.cs +++ b/src/WattleScript.Templating/Template.cs @@ -80,6 +80,10 @@ public string Render(string code) { sb.AppendLine($"stdout({tkn.Lexeme})"); } + else if (tkn.Type == TokenTypes.ExplicitExpr) + { + sb.AppendLine($"stdout({tkn.Lexeme})"); + } } string finalText = sb.ToString(); diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 8bb9177e..32f4fe54 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -1,4 +1,6 @@ -namespace WattleScript.Templating; +using System.Text; + +namespace WattleScript.Templating; public class Tokenizer { @@ -15,6 +17,7 @@ enum ImplicitExpressionTypes private List AllowedTransitionKeywords = new List() {"if", "for", "do", "while", "require", "function"}; private List BannedTransitionKeywords = new List() {"else", "elseif"}; private Dictionary?> KeywordsMap; + private StringBuilder Buffer = new StringBuilder(); private Token LastToken => Tokens[^1]; int pos = 0; char c; @@ -57,7 +60,16 @@ bool IsDigit(char ch) char Step(int i = 1) { char cc = Source[pos]; - currentLexeme += cc; + + if (stepMode == StepModes.CurrentLexeme) + { + currentLexeme += cc; + } + else + { + Buffer.Append(cc); + } + pos += i; c = cc; return cc; @@ -104,22 +116,65 @@ char Peek(int i = 1) return Source[peekedPos]; } - bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced) + bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced, bool handleStrings) { + bool inString = false; + char stringChar = ' '; + int inbalance = startsInbalanced ? 1 : 0; while (!IsAtEnd()) { Step(); + if (handleStrings) + { + if (c == '\'') + { + if (!inString) + { + inString = true; + stringChar = '\''; + } + else + { + if (stringChar == '\'') + { + inString = false; + } + } + } + else if (c == '"') + { + if (!inString) + { + inString = true; + stringChar = '"'; + } + else + { + if (stringChar == '"') + { + inString = false; + } + } + } + } + if (c == startBr) { - inbalance++; + if (!handleStrings || (handleStrings && !inString)) + { + inbalance++; + } } else if (c == endBr) { - inbalance--; - if (inbalance <= 0) + if (!handleStrings || (handleStrings && !inString)) { - return true; + inbalance--; + if (inbalance <= 0) + { + return true; + } } } } @@ -198,7 +253,7 @@ bool ParseKeywordIf() return false; } - bool endExprMatched = ParseUntilBalancedChar('(', ')', true); + bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true); l = GetCurrentLexeme(); if (!endExprMatched) @@ -392,6 +447,7 @@ void ParseTransition() else if (c == '(') { DiscardCurrentLexeme(); + ParseExplicitExpression(); } else if (c == ':') { @@ -423,45 +479,61 @@ ImplicitExpressionTypes Str2ImplicitExprType(string str) return ImplicitExpressionTypes.Literal; } - void ParseImplicitExpression() + void ParseLiteral() { - void ParseLiteral() + while (true) { - while (true) + if (!IsAlphaNumeric(Peek())) { - if (!IsAlphaNumeric(Peek())) - { - break; - } + break; + } - Step(); - } - } + Step(); + } + } - void ParseLiteralStartsWithAlpha() - { - bool first = true; + void ParseLiteralStartsWithAlpha() + { + bool first = true; - while (true) + while (true) + { + if (first) { - if (first) - { - if (!IsAlpha(Peek())) - { - break; - } - - first = false; - } - else if (!IsAlphaNumeric(Peek())) + if (!IsAlpha(Peek())) { break; } + + first = false; + } + else if (!IsAlphaNumeric(Peek())) + { + break; + } - Step(); - } + Step(); + } + } + + void ParseExplicitExpression() + { + // parser is positioned after opening ( + ParseUntilBalancedChar('(', ')', true, true); + string str = GetCurrentLexeme(); + + // get rid of closing ) + if (str.EndsWith(')')) + { + str = str[..^1]; } + currentLexeme = str; + AddToken(TokenTypes.ExplicitExpr); + } + + void ParseImplicitExpression() + { while (!IsAtEnd()) { ParseLiteralStartsWithAlpha(); @@ -477,11 +549,11 @@ void ParseLiteralStartsWithAlpha() char bufC = Peek(); if (bufC == '[') { - ParseUntilBalancedChar('[', ']', false); + ParseUntilBalancedChar('[', ']', false, true); } else if (bufC == '(') { - ParseUntilBalancedChar('(', ')', false); + ParseUntilBalancedChar('(', ')', false, true); } else if (bufC == '.') { @@ -605,15 +677,82 @@ void ParseUntilHtmlOrClientTransition() } } - void ParseHtmlTag() + void ClearBuffer() + { + Buffer.Clear(); + } + + private StepModes stepMode = StepModes.CurrentLexeme; + enum StepModes + { + CurrentLexeme, + Buffer + } + + void SetStepMode(StepModes mode) + { + stepMode = mode; + } + + bool Throw(string message) + { + throw new Exception(message); + } + + bool ParseHtmlTag() { char chr = Step(); // has to be < // First char in a proper HTML tag (after opening <) can be [_, !, /, Alpha] - // If we have something else we can consider it a malformated tag - // Razor renders first char = NUM as an error but compiles - // If it is some other char, Razor throws - ParseUntilBalancedChar('<', '>', true); + if (!(Peek() == '_' || Peek() == '!' || IsAlpha(Peek()))) + { + Throw("First char after < in an opening HTML tag must be _, ! or alpha"); + } + + // The next few chars represent element's name + ClearBuffer(); + SetStepMode(StepModes.Buffer); + + while (!IsAtEnd() && IsAlphaNumeric(Peek())) + { + Step(); + } + + if (IsAtEnd()) + { + Throw("Unclosed HTML tag at the end of file"); + } + + string tagName = GetBuffer(); + /*char nextMeaningful = GetNextCharNotWhitespace(); + + if (nextMeaningful == '>') // self closing without / or end of start tag + { + if (IsSelfClosing(tagName)) + { + + } + } + else if (nextMeaningful == '/') // self closing with / + { + + } + else if (IsAlpha(nextMeaningful)) // start of an attribute + { + + } + else + { + return Throw("Unexpected char"); + }*/ + + AddBufferToCurrentLexeme(); + ClearBuffer(); + SetStepMode(StepModes.CurrentLexeme); + + + + ParseUntilBalancedChar('<', '>', true, true); string s = GetCurrentLexeme(); if (!CurrentLexemeIsSelfClosingHtmlTag()) @@ -624,11 +763,13 @@ void ParseHtmlTag() s = GetCurrentLexeme(); AddToken(TokenTypes.ClientText); + + return true; } void ParseHtmlClosingTag() { - ParseUntilBalancedChar('<', '>', false); + ParseUntilBalancedChar('<', '>', false, true); string str = GetCurrentLexeme(); } @@ -678,6 +819,29 @@ void ParseHtmlOrPlaintextUntilClosingTag() s = GetCurrentLexeme(); } + char GetNextCharNotWhitespace() + { + StorePos(); + char ch = '\0'; + + while (!IsAtEnd()) + { + ch = Step(); + if (Peek() != ' ') + { + break; + } + } + RestorePos(); + + return ch; + } + + string GetBuffer() + { + return Buffer.ToString(); + } + bool CurrentLexemeIsSelfClosingHtmlTag() { return GetCurrentLexeme().EndsWith("/>"); @@ -698,4 +862,13 @@ void DiscardCurrentLexeme() currentLexeme = ""; } + void AddBufferToCurrentLexeme() + { + currentLexeme += Buffer.ToString(); + } + + bool IsSelfClosing(string tagName) + { + return tagName == "area" || tagName == "base" || tagName == "br" || tagName == "col" || tagName == "embed" || tagName == "hr" || tagName == "img" || tagName == "input" || tagName == "keygen" || tagName == "link" || tagName == "menuitem" || tagName == "meta" || tagName == "param" || tagName == "source" || tagName == "track" || tagName == "wbr"; + } } \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/1-simple.html b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/1-simple.html new file mode 100644 index 00000000..b6468798 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/1-simple.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/1-simple.wthtml new file mode 100644 index 00000000..199897cc --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/1-simple.wthtml @@ -0,0 +1,4 @@ +@{ + x = 'myAttr' +} +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/2-attr.html b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/2-attr.html new file mode 100644 index 00000000..a696f34e --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/2-attr.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/2-attr.wthtml b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/2-attr.wthtml new file mode 100644 index 00000000..4facd4d8 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/2-attr.wthtml @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/3-attr-2.html b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/3-attr-2.html new file mode 100644 index 00000000..c449f96b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/3-attr-2.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/3-attr-2.wthtml b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/3-attr-2.wthtml new file mode 100644 index 00000000..ed1be671 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/3-attr-2.wthtml @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/4-escape.html b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/4-escape.html new file mode 100644 index 00000000..ef91ce1b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/4-escape.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/4-escape.wthtml b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/4-escape.wthtml new file mode 100644 index 00000000..436864b9 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/4-escape.wthtml @@ -0,0 +1 @@ +
\ No newline at end of file From 782cec85271fafaf2c1e451a1f3ee515d9f06016 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 12:29:30 +0200 Subject: [PATCH 05/74] Add support for comments in expressions, @* *@ comments --- src/WattleScript.Templating/Template.cs | 4 + src/WattleScript.Templating/Token.cs | 1 + src/WattleScript.Templating/Tokenizer.cs | 99 ++++++++++++++++--- .../Templating/Tests/Comments/1-simple.html | 1 + .../Templating/Tests/Comments/1-simple.wthtml | 2 + .../Templating/Tests/Escape/1-simple.html | 1 + .../Templating/Tests/Escape/1-simple.wthtml | 1 + .../Tests/Escape/2-escape-with-text.html | 1 + .../Tests/Escape/2-escape-with-text.wthtml | 1 + .../Tests/ExplicitExpr/5-escape-comment.html | 1 + .../ExplicitExpr/5-escape-comment.wthtml | 1 + 11 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/Comments/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Comments/1-simple.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Escape/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Escape/1-simple.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Escape/2-escape-with-text.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Escape/2-escape-with-text.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/5-escape-comment.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/5-escape-comment.wthtml diff --git a/src/WattleScript.Templating/Template.cs b/src/WattleScript.Templating/Template.cs index 6d7621ba..026498f0 100644 --- a/src/WattleScript.Templating/Template.cs +++ b/src/WattleScript.Templating/Template.cs @@ -84,6 +84,10 @@ public string Render(string code) { sb.AppendLine($"stdout({tkn.Lexeme})"); } + else if (tkn.Type == TokenTypes.ServerComment) + { + sb.AppendLine($"/*{tkn.Lexeme}*/"); + } } string finalText = sb.ToString(); diff --git a/src/WattleScript.Templating/Token.cs b/src/WattleScript.Templating/Token.cs index 07115c95..204995ef 100644 --- a/src/WattleScript.Templating/Token.cs +++ b/src/WattleScript.Templating/Token.cs @@ -7,6 +7,7 @@ public enum TokenTypes ExplicitExpr, ImplicitExpr, ClientText, + ServerComment, Eof, Length } diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 32f4fe54..5c0b9b6b 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -116,20 +116,26 @@ char Peek(int i = 1) return Source[peekedPos]; } - bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced, bool handleStrings) + bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced, bool handleStrings, bool handleServerComments) { bool inString = false; char stringChar = ' '; - + bool inMultilineComment = false; + + bool InSpecialSequence() + { + return inString || inMultilineComment; + } + int inbalance = startsInbalanced ? 1 : 0; while (!IsAtEnd()) { Step(); - if (handleStrings) + if (handleStrings && !inMultilineComment) { if (c == '\'') { - if (!inString) + if (!InSpecialSequence()) { inString = true; stringChar = '\''; @@ -144,7 +150,7 @@ bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced, boo } else if (c == '"') { - if (!inString) + if (!InSpecialSequence()) { inString = true; stringChar = '"'; @@ -158,17 +164,35 @@ bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced, boo } } } + + if (handleServerComments && !inString) + { + if (c == '/' && Peek() == '*') + { + if (!inMultilineComment) + { + inMultilineComment = true; + } + } + else if (c == '*' && Peek() == '/') + { + if (inMultilineComment) + { + inMultilineComment = false; + } + } + } if (c == startBr) { - if (!handleStrings || (handleStrings && !inString)) + if (!InSpecialSequence()) { inbalance++; } } else if (c == endBr) { - if (!handleStrings || (handleStrings && !inString)) + if (!InSpecialSequence()) { inbalance--; if (inbalance <= 0) @@ -253,7 +277,7 @@ bool ParseKeywordIf() return false; } - bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true); + bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true, true); l = GetCurrentLexeme(); if (!endExprMatched) @@ -406,12 +430,27 @@ void ParseClient() { if (Peek() == '@') { - if (Peek(2) != '@') + if (Peek(2) == '@') // @@ -> @ { - AddToken(TokenTypes.ClientText); - ParseTransition(); + Step(); + Step(); + RemoveLastCharFromCurrentLexeme(); + continue; + } + + if (Peek(2) == '*') // @* -> comment + { + Step(); + Step(); + DiscardCurrentLexeme(); + ParseServerComment(); continue; } + + + AddToken(TokenTypes.ClientText); + ParseTransition(); + continue; } Step(); @@ -420,6 +459,34 @@ void ParseClient() AddToken(TokenTypes.ClientText); } + // @* *@ + // parser must be positioned directly after opening @* + void ParseServerComment() + { + while (!IsAtEnd()) + { + if (Peek() == '*' && Peek(2) == '@') + { + Step(); + Step(); + RemoveLastCharFromCurrentLexeme(); + RemoveLastCharFromCurrentLexeme(); // eat and discard closing *@ + AddToken(TokenTypes.ServerComment); + break; + } + + Step(); + } + } + + void RemoveLastCharFromCurrentLexeme() + { + if (GetCurrentLexeme().Length > 0) + { + currentLexeme = currentLexeme[..^1]; + } + } + void ParseTransition() { /* Valid transition sequences are @@ -519,7 +586,7 @@ void ParseLiteralStartsWithAlpha() void ParseExplicitExpression() { // parser is positioned after opening ( - ParseUntilBalancedChar('(', ')', true, true); + ParseUntilBalancedChar('(', ')', true, true, true); string str = GetCurrentLexeme(); // get rid of closing ) @@ -549,11 +616,11 @@ void ParseImplicitExpression() char bufC = Peek(); if (bufC == '[') { - ParseUntilBalancedChar('[', ']', false, true); + ParseUntilBalancedChar('[', ']', false, true, true); } else if (bufC == '(') { - ParseUntilBalancedChar('(', ')', false, true); + ParseUntilBalancedChar('(', ')', false, true, true); } else if (bufC == '.') { @@ -752,7 +819,7 @@ bool ParseHtmlTag() - ParseUntilBalancedChar('<', '>', true, true); + ParseUntilBalancedChar('<', '>', true, true, true); string s = GetCurrentLexeme(); if (!CurrentLexemeIsSelfClosingHtmlTag()) @@ -769,7 +836,7 @@ bool ParseHtmlTag() void ParseHtmlClosingTag() { - ParseUntilBalancedChar('<', '>', false, true); + ParseUntilBalancedChar('<', '>', false, true, true); string str = GetCurrentLexeme(); } diff --git a/src/WattleScript.Tests/Templating/Tests/Comments/1-simple.html b/src/WattleScript.Tests/Templating/Tests/Comments/1-simple.html new file mode 100644 index 00000000..281c6866 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Comments/1-simple.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Comments/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/Comments/1-simple.wthtml new file mode 100644 index 00000000..ca570165 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Comments/1-simple.wthtml @@ -0,0 +1,2 @@ +@* comment *@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Escape/1-simple.html b/src/WattleScript.Tests/Templating/Tests/Escape/1-simple.html new file mode 100644 index 00000000..b516b2c4 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Escape/1-simple.html @@ -0,0 +1 @@ +@ \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Escape/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/Escape/1-simple.wthtml new file mode 100644 index 00000000..fffa12f0 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Escape/1-simple.wthtml @@ -0,0 +1 @@ +@@ \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Escape/2-escape-with-text.html b/src/WattleScript.Tests/Templating/Tests/Escape/2-escape-with-text.html new file mode 100644 index 00000000..f773d513 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Escape/2-escape-with-text.html @@ -0,0 +1 @@ +@text \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Escape/2-escape-with-text.wthtml b/src/WattleScript.Tests/Templating/Tests/Escape/2-escape-with-text.wthtml new file mode 100644 index 00000000..fe983c8c --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Escape/2-escape-with-text.wthtml @@ -0,0 +1 @@ +@@text \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/5-escape-comment.html b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/5-escape-comment.html new file mode 100644 index 00000000..ef91ce1b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/5-escape-comment.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/5-escape-comment.wthtml b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/5-escape-comment.wthtml new file mode 100644 index 00000000..88b54401 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/5-escape-comment.wthtml @@ -0,0 +1 @@ +
\ No newline at end of file From 4fd0590667f18e4318138e7e2c1e756db1da0c69 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 15:19:45 +0200 Subject: [PATCH 06/74] Add support for "else if" --- src/WattleScript.Templating/Tokenizer.cs | 46 ++++++++++++++++--- .../ExplicitExpr/6-escape-comment-mixed.html | 1 + .../6-escape-comment-mixed.wthtml | 1 + .../8-if-html-code-block-explicit.html | 3 ++ .../8-if-html-code-block-explicit.wthtml | 10 ++++ .../AllowedKeyword/9-if-elseif.html | 1 + .../AllowedKeyword/9-if-elseif.wthtml | 9 ++++ 7 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/6-escape-comment-mixed.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/6-escape-comment-mixed.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 5c0b9b6b..70343d9b 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -267,6 +267,7 @@ bool MatchNextNonWhiteSpaceNonNewlineChar(char ch) } // if (expr) {} + // parser has to be positioned after if, either at opening ( or at whitespace before it bool ParseKeywordIf() { bool openBrkMatched = MatchNextNonWhiteSpaceChar('('); @@ -291,12 +292,46 @@ bool ParseKeywordIf() bool matchesElse = NextLiteralSkipEmptyCharsMatches("else"); if (matchesElse) { - ParseKeywordElse(); + ParseKeywordElseOrElseIf(); } return false; } + // else {} + // or possibly else if () {} + bool ParseKeywordElseOrElseIf() + { + StorePos(); + ParseWhitespaceAndNewlines(); + string elseStr = StepN(4); + ParseWhitespaceAndNewlines(); + string elseIfStr = StepN(2); + RestorePos(); + + if (elseStr == "else" && elseIfStr == "if") + { + ParseKeywordElseIf(); + } + else if (elseStr == "else") + { + ParseKeywordElse(); + } + + return true; + } + + // else if () {} + bool ParseKeywordElseIf() + { + ParseWhitespaceAndNewlines(); + string elseStr = StepN(4); // eat else + ParseWhitespaceAndNewlines(); + string elseIfStr = StepN(2); // ear if + + return ParseKeywordIf(); + } + // else {} bool ParseKeywordElse() { @@ -337,13 +372,10 @@ bool ParseWhitespaceAndNewlines() if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') { Step(); + continue; } - else - { - break; - } - - Step(); + + break; } return true; diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/6-escape-comment-mixed.html b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/6-escape-comment-mixed.html new file mode 100644 index 00000000..ef91ce1b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/6-escape-comment-mixed.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/6-escape-comment-mixed.wthtml b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/6-escape-comment-mixed.wthtml new file mode 100644 index 00000000..da6f031e --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/6-escape-comment-mixed.wthtml @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.html new file mode 100644 index 00000000..abef679d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.html @@ -0,0 +1,3 @@ +
+
2
+
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.wthtml new file mode 100644 index 00000000..b6e9b548 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.wthtml @@ -0,0 +1,10 @@ +@if (2 > 1) { +
+ @if (3 > 10) { +
@(1)
+ } + else { +
@(2)
+ } +
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.html new file mode 100644 index 00000000..5053de3d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.html @@ -0,0 +1 @@ +
5
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.wthtml new file mode 100644 index 00000000..845fe5cc --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.wthtml @@ -0,0 +1,9 @@ +@if (1 > 2) { +
10
+} +else if (3 > 2) { +
5
+} +else { +
1
+} \ No newline at end of file From f595a224d31420e090fda98a72149de7b3b76e69 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 15:24:13 +0200 Subject: [PATCH 07/74] Add support for "elseif" --- src/WattleScript.Templating/Tokenizer.cs | 2 +- .../AllowedKeyword/10-if-elseif-multiple.html | 1 + .../AllowedKeyword/10-if-elseif-multiple.wthtml | 12 ++++++++++++ .../AllowedKeyword/11-if-elseif-no-whitespace.html | 1 + .../AllowedKeyword/11-if-elseif-no-whitespace.wthtml | 9 +++++++++ 5 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 70343d9b..ed8fc262 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -289,7 +289,7 @@ bool ParseKeywordIf() //bool openBlockBrkMatched = MatchNextNonWhiteSpaceChar('{'); ParseCodeBlock(true); - bool matchesElse = NextLiteralSkipEmptyCharsMatches("else"); + bool matchesElse = NextLiteralSkipEmptyCharsMatches("else") || NextLiteralSkipEmptyCharsMatches("elseif"); // else handles "else if" but we have to check for "elseif" manually if (matchesElse) { ParseKeywordElseOrElseIf(); diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.html new file mode 100644 index 00000000..eaa5a919 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.html @@ -0,0 +1 @@ +
4
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.wthtml new file mode 100644 index 00000000..b31464f8 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.wthtml @@ -0,0 +1,12 @@ +@if (1 > 2) { +
10
+} +else if (2 > 2) { +
5
+} +else if (3 > 2) { +
4
+} +else { +
1
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.html new file mode 100644 index 00000000..5053de3d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.html @@ -0,0 +1 @@ +
5
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.wthtml new file mode 100644 index 00000000..0e4734fb --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.wthtml @@ -0,0 +1,9 @@ +@if (1 > 2) { +
10
+} +elseif (3 > 2) { +
5
+} +else { +
1
+} \ No newline at end of file From 8303d67161c89830f329626466ae695023fafbc6 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 15:33:51 +0200 Subject: [PATCH 08/74] Add support for "for" --- src/WattleScript.Templating/Tokenizer.cs | 40 ++++++++++++++----- .../AllowedKeyword/For/1-for.html | 1 + .../AllowedKeyword/For/1-for.wthtml | 3 ++ .../AllowedKeyword/For/2-for-ul.html | 3 ++ .../AllowedKeyword/For/2-for-ul.wthtml | 5 +++ .../{ => IfElseElseif}/1-if.html | 0 .../{ => IfElseElseif}/1-if.wthtml | 0 .../10-if-elseif-multiple.html | 0 .../10-if-elseif-multiple.wthtml | 0 .../11-if-elseif-no-whitespace.html | 0 .../11-if-elseif-no-whitespace.wthtml | 0 .../{ => IfElseElseif}/2-if-else.html | 0 .../{ => IfElseElseif}/2-if-else.wthtml | 0 .../{ => IfElseElseif}/3-if-html.html | 0 .../{ => IfElseElseif}/3-if-html.wthtml | 0 .../4-if-html-self-closing.html | 0 .../4-if-html-self-closing.wthtml | 0 .../5-if-html-deep-nested.html | 0 .../5-if-html-deep-nested.wthtml | 0 .../6-if-html-code-block.html | 0 .../6-if-html-code-block.wthtml | 0 .../{ => IfElseElseif}/7-if-block.html | 0 .../{ => IfElseElseif}/7-if-block.wthtml | 0 .../8-if-html-code-block-explicit.html | 0 .../8-if-html-code-block-explicit.wthtml | 0 .../{ => IfElseElseif}/9-if-elseif.html | 0 .../{ => IfElseElseif}/9-if-elseif.wthtml | 0 27 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/1-for.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/1-for.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.wthtml rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/1-if.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/1-if.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/10-if-elseif-multiple.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/10-if-elseif-multiple.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/11-if-elseif-no-whitespace.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/11-if-elseif-no-whitespace.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/2-if-else.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/2-if-else.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/3-if-html.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/3-if-html.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/4-if-html-self-closing.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/4-if-html-self-closing.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/5-if-html-deep-nested.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/5-if-html-deep-nested.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/6-if-html-code-block.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/6-if-html-code-block.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/7-if-block.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/7-if-block.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/8-if-html-code-block-explicit.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/8-if-html-code-block-explicit.wthtml (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/9-if-elseif.html (100%) rename src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/{ => IfElseElseif}/9-if-elseif.wthtml (100%) diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index ed8fc262..511b03c1 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -28,7 +28,8 @@ public Tokenizer() { KeywordsMap = new Dictionary?> { - { "if", ParseKeywordIf } + { "if", ParseKeywordIf }, + { "for", ParseKeywordFor } }; } @@ -269,13 +270,37 @@ bool MatchNextNonWhiteSpaceNonNewlineChar(char ch) // if (expr) {} // parser has to be positioned after if, either at opening ( or at whitespace before it bool ParseKeywordIf() + { + ParseGenericBrkKeywordWithBlock("if"); + + bool matchesElse = NextLiteralSkipEmptyCharsMatches("else") || NextLiteralSkipEmptyCharsMatches("elseif"); // else handles "else if" but we have to check for "elseif" manually + if (matchesElse) + { + ParseKeywordElseOrElseIf(); + } + + return false; + } + + // for (i in a..b) + // for (i = 0; i < x; i++) + // for (;;) + // we always have () around expr/s + // parser has to be positioned after "for", either at opening ( or at a whitespace preceding it + bool ParseKeywordFor() + { + return ParseGenericBrkKeywordWithBlock("for"); + } + + // keyword () {} + bool ParseGenericBrkKeywordWithBlock(string keyword) { bool openBrkMatched = MatchNextNonWhiteSpaceChar('('); string l = GetCurrentLexeme(); - + if (!openBrkMatched) { - return false; + Throw($"Expected ( after {keyword}"); } bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true, true); @@ -286,16 +311,9 @@ bool ParseKeywordIf() return false; } - //bool openBlockBrkMatched = MatchNextNonWhiteSpaceChar('{'); ParseCodeBlock(true); - bool matchesElse = NextLiteralSkipEmptyCharsMatches("else") || NextLiteralSkipEmptyCharsMatches("elseif"); // else handles "else if" but we have to check for "elseif" manually - if (matchesElse) - { - ParseKeywordElseOrElseIf(); - } - - return false; + return true; } // else {} diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/1-for.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/1-for.html new file mode 100644 index 00000000..66d0d094 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/1-for.html @@ -0,0 +1 @@ +
1
2
3
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/1-for.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/1-for.wthtml new file mode 100644 index 00000000..22173ffc --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/1-for.wthtml @@ -0,0 +1,3 @@ +@for (i in 1..3) { +
@i
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.html new file mode 100644 index 00000000..0c45f675 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.html @@ -0,0 +1,3 @@ +
    +
  • 1
  • 2
  • 3
  • +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.wthtml new file mode 100644 index 00000000..8d41f3ce --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.wthtml @@ -0,0 +1,5 @@ +
    + @for (i in 1..3) { +
  • @i
  • + } +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/1-if.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/1-if.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/1-if.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/1-if.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/1-if.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/10-if-elseif-multiple.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/10-if-elseif-multiple.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/10-if-elseif-multiple.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/10-if-elseif-multiple.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/10-if-elseif-multiple.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/11-if-elseif-no-whitespace.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/11-if-elseif-no-whitespace.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/11-if-elseif-no-whitespace.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/11-if-elseif-no-whitespace.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/11-if-elseif-no-whitespace.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/2-if-else.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/2-if-else.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/2-if-else.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/2-if-else.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/2-if-else.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/3-if-html.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/3-if-html.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/3-if-html.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/3-if-html.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/3-if-html.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/4-if-html-self-closing.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/4-if-html-self-closing.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/4-if-html-self-closing.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/4-if-html-self-closing.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/4-if-html-self-closing.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/5-if-html-deep-nested.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/5-if-html-deep-nested.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/5-if-html-deep-nested.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/5-if-html-deep-nested.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/5-if-html-deep-nested.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/6-if-html-code-block.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/6-if-html-code-block.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/6-if-html-code-block.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/6-if-html-code-block.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/6-if-html-code-block.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/7-if-block.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/7-if-block.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/7-if-block.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/7-if-block.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/7-if-block.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/8-if-html-code-block-explicit.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/8-if-html-code-block-explicit.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/8-if-html-code-block-explicit.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/8-if-html-code-block-explicit.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/8-if-html-code-block-explicit.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/9-if-elseif.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.html rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/9-if-elseif.html diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/9-if-elseif.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/9-if-elseif.wthtml rename to src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/9-if-elseif.wthtml From bbff5440b7b14c4e5a7c8be66034bba662dc203f Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 15:47:51 +0200 Subject: [PATCH 09/74] Add support for implicitly self closed tags like
,
--- src/WattleScript.Templating/Tokenizer.cs | 19 +++++++++++++------ .../IfElseElseif/12-self-closing.html | 1 + .../IfElseElseif/12-self-closing.wthtml | 7 +++++++ .../ImplicitExpr/Literal/9-function-call.html | 1 + .../Literal/9-function-call.wthtml | 6 ++++++ 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/9-function-call.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/9-function-call.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 511b03c1..f9d308b7 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -867,14 +867,16 @@ bool ParseHtmlTag() ClearBuffer(); SetStepMode(StepModes.CurrentLexeme); - - ParseUntilBalancedChar('<', '>', true, true, true); string s = GetCurrentLexeme(); - if (!CurrentLexemeIsSelfClosingHtmlTag()) + bool isSelfClosing = IsSelfClosingHtmlTag(tagName); + bool isSelfClosed = CurrentLexemeIsSelfClosedHtmlTag(); + bool parseContent = !isSelfClosed && !isSelfClosing; + + if (parseContent) { - ParseHtmlOrPlaintextUntilClosingTag(); + ParseHtmlOrPlaintextUntilClosingTag(tagName); ParseHtmlClosingTag(); } @@ -890,7 +892,7 @@ void ParseHtmlClosingTag() string str = GetCurrentLexeme(); } - void ParseHtmlOrPlaintextUntilClosingTag() + void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) { int missingBrks = 1; @@ -959,11 +961,16 @@ string GetBuffer() return Buffer.ToString(); } - bool CurrentLexemeIsSelfClosingHtmlTag() + bool CurrentLexemeIsSelfClosedHtmlTag() { return GetCurrentLexeme().EndsWith("/>"); } + bool IsSelfClosingHtmlTag(string htmlTag) + { + return IsSelfClosing(htmlTag.ToLowerInvariant()); + } + void AddToken(TokenTypes type) { if (currentLexeme.Length > 0) diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.html new file mode 100644 index 00000000..55a0109a --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.html @@ -0,0 +1 @@ +


100
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.wthtml new file mode 100644 index 00000000..380eb4b0 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.wthtml @@ -0,0 +1,7 @@ +@if (3 > 2) { +
+
+
+ x = 100 +
@x
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/9-function-call.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/9-function-call.html new file mode 100644 index 00000000..af507e77 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/9-function-call.html @@ -0,0 +1 @@ +
hello world
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/9-function-call.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/9-function-call.wthtml new file mode 100644 index 00000000..0fd458ec --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/9-function-call.wthtml @@ -0,0 +1,6 @@ +@{ + myFn = () => { + return "hello world" + } +} +
@myFn()
\ No newline at end of file From 3496d473910c41391afd2b3e686de761aa72848e Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 16:01:38 +0200 Subject: [PATCH 10/74] Fix handling of server side comment when parsing inner content of a tag --- src/WattleScript.Templating/Tokenizer.cs | 77 +++++++++++-------- .../IfElseElseif/13-transition-in-tag.html | 4 + .../IfElseElseif/13-transition-in-tag.wthtml | 6 ++ 3 files changed, 57 insertions(+), 30 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/13-transition-in-tag.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/13-transition-in-tag.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index f9d308b7..50816b68 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -468,6 +468,45 @@ void Error(int line, string message) return Tokens; } + + /// + /// + /// + /// true if we should end current iteration + bool LookaheadForTransition(bool calledFromClientSide) + { + if (Peek() == '@') + { + if (Peek(2) == '@') // @@ -> @ + { + Step(); + Step(); + RemoveLastCharFromCurrentLexeme(); + return true; + } + + if (Peek(2) == '*') // @* -> comment + { + if (calledFromClientSide) + { + AddToken(TokenTypes.ClientText); + } + + Step(); + Step(); + DiscardCurrentLexeme(); + ParseServerComment(); + return true; + } + + + AddToken(TokenTypes.ClientText); + ParseTransition(); + return true; + } + + return false; + } /* In client mode everything is a literal * until we encouter @ @@ -478,31 +517,12 @@ void ParseClient() { while (!IsAtEnd()) { - if (Peek() == '@') + bool shouldContinue = LookaheadForTransition(true); + if (shouldContinue) { - if (Peek(2) == '@') // @@ -> @ - { - Step(); - Step(); - RemoveLastCharFromCurrentLexeme(); - continue; - } - - if (Peek(2) == '*') // @* -> comment - { - Step(); - Step(); - DiscardCurrentLexeme(); - ParseServerComment(); - continue; - } - - - AddToken(TokenTypes.ClientText); - ParseTransition(); continue; } - + Step(); } @@ -899,16 +919,13 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) string s = GetCurrentLexeme(); while (!IsAtEnd()) { - if (Peek() == '@') + bool shouldContinue = LookaheadForTransition(true); + if (shouldContinue) { - if (Peek(2) != '@') - { - AddToken(TokenTypes.ClientText); - ParseTransition(); - continue; - } + continue; } - else if (Peek() == '<') + + if (Peek() == '<') { if (IsAlpha(Peek(2))) // we enter new tag { diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/13-transition-in-tag.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/13-transition-in-tag.html new file mode 100644 index 00000000..a97caafa --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/13-transition-in-tag.html @@ -0,0 +1,4 @@ +
+ + 300 +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/13-transition-in-tag.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/13-transition-in-tag.wthtml new file mode 100644 index 00000000..00c19b1b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/13-transition-in-tag.wthtml @@ -0,0 +1,6 @@ +@if (3 > 2) { +
+ @* server comment *@ + @(100 + 200) +
+} \ No newline at end of file From f6aee34fec45545a825c5e1b08e1f326895c3043 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 16:20:23 +0200 Subject: [PATCH 11/74] Implement folding of tokens If we have multiple tokens of the same type directly after each other we can fold them to reduce the amount of instructions CLIENT
CLIENT 10 CLIENT
----> CLIENT
10
--- src/WattleScript.Templating/Template.cs | 46 ++++++++++++++++++- .../Templating/TemplatingTestsRunner.cs | 2 +- .../Tests/RawTextElements/1-simple.html | 5 ++ .../Tests/RawTextElements/1-simple.wthtml | 7 +++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/RawTextElements/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/RawTextElements/1-simple.wthtml diff --git a/src/WattleScript.Templating/Template.cs b/src/WattleScript.Templating/Template.cs index 026498f0..dc563e6a 100644 --- a/src/WattleScript.Templating/Template.cs +++ b/src/WattleScript.Templating/Template.cs @@ -50,14 +50,58 @@ public string EncodeJsString(string s) return sb.ToString(); } + + public List Optimise(List? tokens) + { + if (tokens == null) // if we have no tokens or only one we can't merge + { + return new List(); + } + + if (tokens.Count <= 1) + { + return tokens; + } + + int i = 0; + Token token = tokens[i]; + + while (true) + { + i++; + if (i > tokens.Count - 1) + { + break; + } + + Token nextToken = tokens[i]; + if (token.Type == nextToken.Type) + { + token.Lexeme += nextToken.Lexeme; + tokens.RemoveAt(i); + i--; + continue; + } + + // move to next token + token = tokens[i]; + } + + return tokens; + } - public string Render(string code) + public string Render(string code, bool optimise) { Tokenizer tk = new Tokenizer(); List tokens = tk.Tokenize(code); StringBuilder sb = new StringBuilder(); bool firstClientPending = true; + + if (optimise) + { + tokens = Optimise(tokens); + } foreach (Token tkn in tokens) { diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index d044c651..bc6996dc 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -39,7 +39,7 @@ public async Task RunCore(string path, bool reportErrors = false) StringBuilder stdOut = new StringBuilder(); Template tmp = new Template(); - string transpiled = tmp.Render(code); + string transpiled = tmp.Render(code, true); Script script = new Script(CoreModules.Preset_HardSandbox); script.Options.DebugPrint = s => stdOut.AppendLine(s); diff --git a/src/WattleScript.Tests/Templating/Tests/RawTextElements/1-simple.html b/src/WattleScript.Tests/Templating/Tests/RawTextElements/1-simple.html new file mode 100644 index 00000000..8c8b054c --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/RawTextElements/1-simple.html @@ -0,0 +1,5 @@ +
+ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/RawTextElements/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/RawTextElements/1-simple.wthtml new file mode 100644 index 00000000..90073e70 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/RawTextElements/1-simple.wthtml @@ -0,0 +1,7 @@ +@{ +
+ +
+} \ No newline at end of file From d541828d9d0cda513f2db6a2acab08bbf4e138cc Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 17:04:36 +0200 Subject: [PATCH 12/74] Don't return from ParseCodeBlock until we've matched brks --- src/WattleScript.Templating/Tokenizer.cs | 6 ++++-- .../Tests/ImplicitExpr/Literal/10-function-call-html.html | 2 ++ .../ImplicitExpr/Literal/10-function-call-html.wthtml | 6 ++++++ .../Literal/11-function-call-html-nested.html | 4 ++++ .../Literal/11-function-call-html-nested.wthtml | 8 ++++++++ 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/10-function-call-html.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/10-function-call-html.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/11-function-call-html-nested.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/11-function-call-html-nested.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 50816b68..cacc1e40 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -742,6 +742,7 @@ void ParseCodeBlock(bool keepClosingBrk) while (!IsAtEnd()) { ParseUntilHtmlOrClientTransition(); + string str = GetCurrentLexeme(); StorePos(); bool matchedClosingBrk = MatchNextNonWhiteSpaceNonNewlineChar('}'); @@ -793,7 +794,6 @@ void ParseUntilHtmlOrClientTransition() { AddToken(TokenTypes.BlockExpr); ParseHtmlTag(); - return; } } else if (Peek() == '{') @@ -809,9 +809,11 @@ void ParseUntilHtmlOrClientTransition() } } - string str = GetCurrentLexeme(); + string str2 = GetCurrentLexeme(); char chr = Step(); } + + string str = GetCurrentLexeme(); } void ClearBuffer() diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/10-function-call-html.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/10-function-call-html.html new file mode 100644 index 00000000..b6d1e6ce --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/10-function-call-html.html @@ -0,0 +1,2 @@ + +
hello
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/10-function-call-html.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/10-function-call-html.wthtml new file mode 100644 index 00000000..d4cf6a83 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/10-function-call-html.wthtml @@ -0,0 +1,6 @@ +@{ + say = (what) => { +
@what
+ } +} +@say("hello") \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/11-function-call-html-nested.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/11-function-call-html-nested.html new file mode 100644 index 00000000..ad5dd0b0 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/11-function-call-html-nested.html @@ -0,0 +1,4 @@ + +
+
hello
+
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/11-function-call-html-nested.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/11-function-call-html-nested.wthtml new file mode 100644 index 00000000..e7b5fb49 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/11-function-call-html-nested.wthtml @@ -0,0 +1,8 @@ +@{ + say = (what) => { +
+
@what
+
+ } +} +@say("hello") \ No newline at end of file From 79a992f48582bc79eb373ccfe884278b6e685b14 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 17:40:31 +0200 Subject: [PATCH 13/74] Support escape sequences in detected strings --- src/WattleScript.Templating/Tokenizer.cs | 158 ++++++++++++++---- .../12-function-call-html-nested-escape.html | 5 + ...12-function-call-html-nested-escape.wthtml | 11 ++ 3 files changed, 140 insertions(+), 34 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/12-function-call-html-nested-escape.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/12-function-call-html-nested-escape.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index cacc1e40..55326be9 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -129,6 +129,29 @@ bool InSpecialSequence() } int inbalance = startsInbalanced ? 1 : 0; + + void HandleStringSequence(char chr) + { + string str = GetCurrentLexeme(); + if (LastStoredCharMatches(2, '\\')) // check that string symbol is not escaped + { + return; + } + + if (!InSpecialSequence()) + { + inString = true; + stringChar = chr; + } + else + { + if (stringChar == chr) + { + inString = false; + } + } + } + while (!IsAtEnd()) { Step(); @@ -136,33 +159,11 @@ bool InSpecialSequence() { if (c == '\'') { - if (!InSpecialSequence()) - { - inString = true; - stringChar = '\''; - } - else - { - if (stringChar == '\'') - { - inString = false; - } - } + HandleStringSequence('\''); } else if (c == '"') { - if (!InSpecialSequence()) - { - inString = true; - stringChar = '"'; - } - else - { - if (stringChar == '"') - { - inString = false; - } - } + HandleStringSequence('"'); } } @@ -762,6 +763,28 @@ void ParseCodeBlock(bool keepClosingBrk) AddToken(TokenTypes.BlockExpr); } + bool LastStoredCharMatches(params char[] chars) + { + if (GetCurrentLexeme().Length < 1) + { + return false; + } + + char chr = currentLexeme[..^1][0]; + return chars.Contains(chr); + } + + bool LastStoredCharMatches(int n = 1, params char[] chars) + { + if (GetCurrentLexeme().Length < n) + { + return false; + } + + char chr = currentLexeme.Substring(currentLexeme.Length - 1 - n, 1)[0]; + return chars.Contains(chr); + } + bool LastStoredCharNotWhitespaceMatches(params char[] chars) { string str = GetCurrentLexeme(); @@ -784,29 +807,96 @@ bool LastStoredCharNotWhitespaceMatches(params char[] chars) */ void ParseUntilHtmlOrClientTransition() { + bool inString = false; + char stringChar = ' '; + bool inMultilineComment = false; int missingBrks = 1; + bool InSpecialSequence() + { + return inString || inMultilineComment; + } + + void HandleStringSequence(char chr) + { + string str = GetCurrentLexeme(); + if (LastStoredCharMatches(2, '\\')) // check that string symbol is not escaped + { + return; + } + + if (!InSpecialSequence()) + { + inString = true; + stringChar = chr; + } + else + { + if (stringChar == chr) + { + inString = false; + } + } + } + while (!IsAtEnd()) { - if (Peek() == '<') + if (!inMultilineComment) { - if (LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) + if (c == '\'') { - AddToken(TokenTypes.BlockExpr); - ParseHtmlTag(); + HandleStringSequence('\''); + } + else if (c == '"') + { + HandleStringSequence('"'); + } + else if (c == '`') + { + HandleStringSequence('`'); } } - else if (Peek() == '{') + + if (!inString) { - missingBrks++; + if (c == '/' && Peek() == '*') + { + if (!inMultilineComment) + { + inMultilineComment = true; + } + } + else if (c == '*' && Peek() == '/') + { + if (inMultilineComment) + { + inMultilineComment = false; + } + } } - else if (Peek() == '}') + + if (!InSpecialSequence()) { - missingBrks--; - if (missingBrks <= 0) + if (Peek() == '<') { - break; + if (LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) + { + AddToken(TokenTypes.BlockExpr); + ParseHtmlTag(); + } + } + else if (Peek() == '{') + { + missingBrks++; } + else if (Peek() == '}') + { + missingBrks--; + if (missingBrks <= 0) + { + break; + } + } } string str2 = GetCurrentLexeme(); diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/12-function-call-html-nested-escape.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/12-function-call-html-nested-escape.html new file mode 100644 index 00000000..80568919 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/12-function-call-html-nested-escape.html @@ -0,0 +1,5 @@ + +
+
hello
+ )/**/' +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/12-function-call-html-nested-escape.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/12-function-call-html-nested-escape.wthtml new file mode 100644 index 00000000..fc662e60 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/12-function-call-html-nested-escape.wthtml @@ -0,0 +1,11 @@ +@{ + say = (what) => { + x = ')/**/\'' + /*comment*/ +
+
@what
+ @x +
+ } +} +@say("hello") \ No newline at end of file From 72c497679877e9a045259c5f9b252962f5f77843 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 18:45:21 +0200 Subject: [PATCH 14/74] Add support for line transitions via @: --- src/WattleScript.Templating/Tokenizer.cs | 55 ++++++++++++++++++- .../IfElseElseif/14-transition-in-line.html | 1 + .../IfElseElseif/14-transition-in-line.wthtml | 3 + .../IfElseElseif/15-transition-in-line-2.html | 1 + .../15-transition-in-line-2.wthtml | 6 ++ ...ction-call-html-nested-escape-comment.html | 6 ++ ...ion-call-html-nested-escape-comment.wthtml | 12 ++++ 7 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/14-transition-in-line.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/14-transition-in-line.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/15-transition-in-line-2.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/15-transition-in-line-2.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/13-function-call-html-nested-escape-comment.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/13-function-call-html-nested-escape-comment.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 55326be9..247c5198 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -499,8 +499,7 @@ bool LookaheadForTransition(bool calledFromClientSide) ParseServerComment(); return true; } - - + AddToken(TokenTypes.ClientText); ParseTransition(); return true; @@ -508,6 +507,52 @@ bool LookaheadForTransition(bool calledFromClientSide) return false; } + + bool LookaheadForTransitionServerSide() + { + if (Peek() == '@') + { + if (Peek(2) == ':') + { + AddToken(TokenTypes.BlockExpr); + Step(); + Step(); + string str = GetCurrentLexeme(); + DiscardCurrentLexeme(); + ParseRestOfLineAsClient(); + return true; + } + } + + return false; + } + + void ParseRestOfLineAsClient() + { + char chr = Step(); + while (!IsAtEnd() && chr != '\n' && chr != '\r') + { + bool shouldContinue = LookaheadForTransition(true); + if (shouldContinue) + { + continue; + } + + chr = Step(); + } + + // if we ended on \r check for \n and consume if matches + if (chr == '\r') + { + chr = Peek(); + if (chr == '\n') + { + Step(); + } + } + + AddToken(TokenTypes.ClientText); + } /* In client mode everything is a literal * until we encouter @ @@ -877,6 +922,12 @@ void HandleStringSequence(char chr) if (!InSpecialSequence()) { + bool cnt = LookaheadForTransitionServerSide(); + if (cnt) + { + continue; + } + if (Peek() == '<') { if (LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/14-transition-in-line.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/14-transition-in-line.html new file mode 100644 index 00000000..8e27be7d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/14-transition-in-line.html @@ -0,0 +1 @@ +text diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/14-transition-in-line.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/14-transition-in-line.wthtml new file mode 100644 index 00000000..3f32aa39 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/14-transition-in-line.wthtml @@ -0,0 +1,3 @@ +@if (3 > 2) { + @:text +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/15-transition-in-line-2.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/15-transition-in-line-2.html new file mode 100644 index 00000000..1adf42e1 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/15-transition-in-line-2.html @@ -0,0 +1 @@ +text 100 diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/15-transition-in-line-2.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/15-transition-in-line-2.wthtml new file mode 100644 index 00000000..595b6fc1 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/15-transition-in-line-2.wthtml @@ -0,0 +1,6 @@ +@{ + x = 100 +} +@if (3 > 2) { + @:text @x +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/13-function-call-html-nested-escape-comment.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/13-function-call-html-nested-escape-comment.html new file mode 100644 index 00000000..acbcfc8f --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/13-function-call-html-nested-escape-comment.html @@ -0,0 +1,6 @@ + +
+
hello
+ )/**/' + +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/13-function-call-html-nested-escape-comment.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/13-function-call-html-nested-escape-comment.wthtml new file mode 100644 index 00000000..47d9566b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/Literal/13-function-call-html-nested-escape-comment.wthtml @@ -0,0 +1,12 @@ +@{ + say = (what) => { + x = ')/**/\'' + /*comment*/ +
+
@what
+ @x + +
+ } +} +@say("hello") \ No newline at end of file From 0bff8bdcf9d1785c2d4c6d0eb09d387e3e7935eb Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 22:06:25 +0200 Subject: [PATCH 15/74] Add support for transition --- src/WattleScript.Templating/Tokenizer.cs | 71 ++++++++++--------- .../IfElseElseif/16-transition-text.html | 1 + .../IfElseElseif/16-transition-text.wthtml | 6 ++ 3 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 247c5198..e170b84c 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -979,12 +979,10 @@ bool Throw(string message) throw new Exception(message); } - bool ParseHtmlTag() + string ParseHtmlTagNameInBuffer() { - char chr = Step(); // has to be < - - // First char in a proper HTML tag (after opening <) can be [_, !, /, Alpha] - if (!(Peek() == '_' || Peek() == '!' || IsAlpha(Peek()))) + // First char in a proper HTML tag (after opening <) can be [_, !, /, Alpha, ?] + if (!(Peek() == '_' || Peek() == '!' || Peek() == '?' || Peek() == '/' || IsAlpha(Peek()))) { Throw("First char after < in an opening HTML tag must be _, ! or alpha"); } @@ -993,7 +991,7 @@ bool ParseHtmlTag() ClearBuffer(); SetStepMode(StepModes.Buffer); - while (!IsAtEnd() && IsAlphaNumeric(Peek())) + while (!IsAtEnd() && (IsAlphaNumeric(Peek()) || Peek() == '/' || Peek() == '!' || Peek() == '?')) { Step(); } @@ -1004,34 +1002,21 @@ bool ParseHtmlTag() } string tagName = GetBuffer(); - /*char nextMeaningful = GetNextCharNotWhitespace(); - - if (nextMeaningful == '>') // self closing without / or end of start tag - { - if (IsSelfClosing(tagName)) - { - - } - } - else if (nextMeaningful == '/') // self closing with / - { - - } - else if (IsAlpha(nextMeaningful)) // start of an attribute - { - - } - else - { - return Throw("Unexpected char"); - }*/ AddBufferToCurrentLexeme(); ClearBuffer(); SetStepMode(StepModes.CurrentLexeme); + return tagName; + } + + bool ParseHtmlTag() + { + char chr = Step(); // has to be < + string tagName = ParseHtmlTagNameInBuffer(); + ParseUntilBalancedChar('<', '>', true, true, true); - string s = GetCurrentLexeme(); + string tagText = GetCurrentLexeme(); bool isSelfClosing = IsSelfClosingHtmlTag(tagName); bool isSelfClosed = CurrentLexemeIsSelfClosedHtmlTag(); @@ -1039,20 +1024,42 @@ bool ParseHtmlTag() if (parseContent) { + if (tagText == "") // "" has a special meaning only when exactly matched. Any modification like "" will be rendered as a normal tag + { + DiscardCurrentLexeme(); + } + ParseHtmlOrPlaintextUntilClosingTag(tagName); - ParseHtmlClosingTag(); + ParseHtmlClosingTag(tagName); } - s = GetCurrentLexeme(); + string s = GetCurrentLexeme(); AddToken(TokenTypes.ClientText); return true; } - void ParseHtmlClosingTag() + // parser has to be positioned at opening < of the closing tag + void ParseHtmlClosingTag(string openingTagName) { - ParseUntilBalancedChar('<', '>', false, true, true); + char chr = Step(); // has to be < + string str = GetCurrentLexeme(); + string closingTagName = ParseHtmlTagNameInBuffer(); + + ParseUntilBalancedChar('<', '>', true, true, true); + str = GetCurrentLexeme(); + + if ($"/{openingTagName}" != closingTagName) + { + // [todo] end tag does not match opening tag + // we will be graceful but a warning can be emmited + } + + if (openingTagName == "text" && closingTagName == "/text" && str.Trim() == "") + { + DiscardCurrentLexeme(); + } } void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.html new file mode 100644 index 00000000..14063c9d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.html @@ -0,0 +1 @@ +Value of a is 10 \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.wthtml new file mode 100644 index 00000000..f9ea9318 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.wthtml @@ -0,0 +1,6 @@ +@{ + a = 10 + + Value of a is @a + +} \ No newline at end of file From a057052213bd88ca46cd03f8fb0ae8ce6b346bc7 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 22:10:37 +0200 Subject: [PATCH 16/74] Added support for while --- src/WattleScript.Templating/Tokenizer.cs | 10 +++++++++- .../ImplicitExpr/AllowedKeyword/While/1-while.html | 1 + .../ImplicitExpr/AllowedKeyword/While/1-while.wthtml | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index e170b84c..faadeb9d 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -29,7 +29,8 @@ public Tokenizer() KeywordsMap = new Dictionary?> { { "if", ParseKeywordIf }, - { "for", ParseKeywordFor } + { "for", ParseKeywordFor }, + { "while", ParseKeywordWhile } }; } @@ -292,6 +293,13 @@ bool ParseKeywordFor() { return ParseGenericBrkKeywordWithBlock("for"); } + + // while (i in a..b) {} + // parser has to be positioned after "while", either at opening ( or at a whitespace preceding it + bool ParseKeywordWhile() + { + return ParseGenericBrkKeywordWithBlock("while"); + } // keyword () {} bool ParseGenericBrkKeywordWithBlock(string keyword) diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.html new file mode 100644 index 00000000..073ba860 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.html @@ -0,0 +1 @@ +
0
1
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.wthtml new file mode 100644 index 00000000..c82e5c04 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.wthtml @@ -0,0 +1,7 @@ +@{ + i = 0 +} +@while (i < 2) { +
@i
+ i++ +} \ No newline at end of file From 0d069160691795ecb6ff2bf18c921a6ea27efae5 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 22:23:48 +0200 Subject: [PATCH 17/74] Added support for do..while --- src/WattleScript.Templating/Tokenizer.cs | 54 ++++++++++++++++++- .../AllowedKeyword/While/2-do-while.html | 1 + .../AllowedKeyword/While/2-do-while.wthtml | 8 +++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index faadeb9d..b1bc7d21 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -30,7 +30,8 @@ public Tokenizer() { { "if", ParseKeywordIf }, { "for", ParseKeywordFor }, - { "while", ParseKeywordWhile } + { "while", ParseKeywordWhile }, + { "do", ParseKeywordDo } }; } @@ -300,6 +301,28 @@ bool ParseKeywordWhile() { return ParseGenericBrkKeywordWithBlock("while"); } + + // do {} while () + // parser has to be positioned after "do", either at opening { or at a whitespace preceding it + bool ParseKeywordDo() + { + bool doParsed = ParseGenericKeywordWithBlock("do"); + bool matchesWhile = NextLiteralSkipEmptyCharsMatches("while"); + + if (matchesWhile) + { + ParseWhitespaceAndNewlines(); + string whileStr = StepN(5); + bool whileParsed = ParseGenericBrkKeywordWithoutBlock("while"); + + if (whileParsed) + { + AddToken(TokenTypes.BlockExpr); + } + } + + return true; + } // keyword () {} bool ParseGenericBrkKeywordWithBlock(string keyword) @@ -321,10 +344,39 @@ bool ParseGenericBrkKeywordWithBlock(string keyword) } ParseCodeBlock(true); + return true; + } + + // keyword {} + bool ParseGenericKeywordWithBlock(string keyword) + { + bool openBrkMatched = MatchNextNonWhiteSpaceChar('{'); + string l = GetCurrentLexeme(); + if (!openBrkMatched) + { + Throw($"Expected {{ after {keyword}"); + } + + ParseCodeBlock(true); return true; } + // keyword () + bool ParseGenericBrkKeywordWithoutBlock(string keyword) + { + bool openBrkMatched = MatchNextNonWhiteSpaceChar('('); + string l = GetCurrentLexeme(); + + if (!openBrkMatched) + { + Throw($"Expected ( after {keyword}"); + } + + bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true, true); + return endExprMatched; + } + // else {} // or possibly else if () {} bool ParseKeywordElseOrElseIf() diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.html new file mode 100644 index 00000000..073ba860 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.html @@ -0,0 +1 @@ +
0
1
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.wthtml new file mode 100644 index 00000000..a3f2e8ff --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.wthtml @@ -0,0 +1,8 @@ +@{ + i = 0 +} +@do { +
@i
+ i++ +} +while (i < 2) \ No newline at end of file From b44781277acef7a42cd9c62b2000574b1bf801e5 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 22:38:46 +0200 Subject: [PATCH 18/74] Added support for function --- src/WattleScript.Templating/Tokenizer.cs | 88 +++++++++++++++++-- .../AllowedKeyword/Function/1-function.html | 3 + .../AllowedKeyword/Function/1-function.wthtml | 5 ++ 3 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/1-function.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/1-function.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index b1bc7d21..a408e460 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -18,6 +18,7 @@ enum ImplicitExpressionTypes private List BannedTransitionKeywords = new List() {"else", "elseif"}; private Dictionary?> KeywordsMap; private StringBuilder Buffer = new StringBuilder(); + private StringBuilder PooledStringBuilder = new StringBuilder(); private Token LastToken => Tokens[^1]; int pos = 0; char c; @@ -31,10 +32,16 @@ public Tokenizer() { "if", ParseKeywordIf }, { "for", ParseKeywordFor }, { "while", ParseKeywordWhile }, - { "do", ParseKeywordDo } + { "do", ParseKeywordDo }, + { "function", ParseKeywordFunction } }; } + void ClearPooledBuilder() + { + PooledStringBuilder.Clear(); + } + bool IsAtEnd() { return pos >= Source.Length; @@ -321,7 +328,39 @@ bool ParseKeywordDo() } } - return true; + return doParsed; + } + + // function foo() {} + // parser has to be positioned after "function", either at first ALPHA char of the function's name or whitespace preceding it + bool ParseKeywordFunction() + { + ParseWhitespaceAndNewlines(); + + char chr = Peek(); + if (chr == '(') + { + return Throw("Missing function's name"); + } + + if (chr == '{') + { + return Throw("Missing function's name and signature"); + } + + if (!IsAlpha(chr)) + { + return Throw("First char in function's name has to be an alpha character"); + } + + string fnName = ParseLiteral(); + + if (string.IsNullOrWhiteSpace(fnName)) + { + return Throw("Missing function's name"); + } + + return ParseGenericBrkKeywordWithBlock("function"); } // keyword () {} @@ -472,6 +511,12 @@ bool NextLiteralSkipEmptyCharsMatches(string literal) bool started = false; while (!IsAtEnd()) { + bool shouldContinue = LookaheadForTransition(true); + if (shouldContinue) + { + continue; + } + if (!started) { if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') @@ -722,25 +767,45 @@ ImplicitExpressionTypes Str2ImplicitExprType(string str) return ImplicitExpressionTypes.Literal; } - void ParseLiteral() + string ParseLiteral() { - while (true) + ClearPooledBuilder(); + + while (!IsAtEnd()) { + bool shouldSkip = LookaheadForTransition(true); + if (shouldSkip) + { + continue; + } + if (!IsAlphaNumeric(Peek())) { break; } - Step(); + char chr = Step(); + PooledStringBuilder.Append(chr); } + + string str = PooledStringBuilder.ToString(); + ClearPooledBuilder(); + return str; } - void ParseLiteralStartsWithAlpha() + string ParseLiteralStartsWithAlpha() { + ClearPooledBuilder(); bool first = true; - while (true) + while (!IsAtEnd()) { + bool shouldSkip = LookaheadForTransition(true); + if (shouldSkip) + { + continue; + } + if (first) { if (!IsAlpha(Peek())) @@ -755,8 +820,13 @@ void ParseLiteralStartsWithAlpha() break; } - Step(); - } + char chr = Step(); + PooledStringBuilder.Append(chr); + } + + string str = PooledStringBuilder.ToString(); + ClearPooledBuilder(); + return str; } void ParseExplicitExpression() diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/1-function.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/1-function.html new file mode 100644 index 00000000..f9c76690 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/1-function.html @@ -0,0 +1,3 @@ + + +
10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/1-function.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/1-function.wthtml new file mode 100644 index 00000000..662aa3e7 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/1-function.wthtml @@ -0,0 +1,5 @@ +@function MyFunc(n) { +
@n
+} + +@MyFunc(10) \ No newline at end of file From 424ae5220051246f018a03b414beb27c495a44fa Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 22:49:37 +0200 Subject: [PATCH 19/74] Keep track of current sides to check for correct possible transitions --- src/WattleScript.Templating/Tokenizer.cs | 77 +++++++++++-------- .../Function/2-function-comment.html | 3 + .../Function/2-function-comment.wthtml | 5 ++ 3 files changed, 52 insertions(+), 33 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/2-function-comment.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/2-function-comment.wthtml diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index a408e460..68f38bbe 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -10,6 +10,12 @@ enum ImplicitExpressionTypes AllowedKeyword, BannedKeyword } + + enum Sides + { + Client, + Server + } private string Source { get; set; } private List Tokens { get; set; } = new List(); @@ -283,7 +289,7 @@ bool ParseKeywordIf() { ParseGenericBrkKeywordWithBlock("if"); - bool matchesElse = NextLiteralSkipEmptyCharsMatches("else") || NextLiteralSkipEmptyCharsMatches("elseif"); // else handles "else if" but we have to check for "elseif" manually + bool matchesElse = NextLiteralSkipEmptyCharsMatches("else", Sides.Server) || NextLiteralSkipEmptyCharsMatches("elseif", Sides.Server); // else handles "else if" but we have to check for "elseif" manually if (matchesElse) { ParseKeywordElseOrElseIf(); @@ -314,11 +320,11 @@ bool ParseKeywordWhile() bool ParseKeywordDo() { bool doParsed = ParseGenericKeywordWithBlock("do"); - bool matchesWhile = NextLiteralSkipEmptyCharsMatches("while"); + bool matchesWhile = NextLiteralSkipEmptyCharsMatches("while", Sides.Server); if (matchesWhile) { - ParseWhitespaceAndNewlines(); + ParseWhitespaceAndNewlines(Sides.Server); string whileStr = StepN(5); bool whileParsed = ParseGenericBrkKeywordWithoutBlock("while"); @@ -335,7 +341,7 @@ bool ParseKeywordDo() // parser has to be positioned after "function", either at first ALPHA char of the function's name or whitespace preceding it bool ParseKeywordFunction() { - ParseWhitespaceAndNewlines(); + ParseWhitespaceAndNewlines(Sides.Server); char chr = Peek(); if (chr == '(') @@ -353,7 +359,7 @@ bool ParseKeywordFunction() return Throw("First char in function's name has to be an alpha character"); } - string fnName = ParseLiteral(); + string fnName = ParseLiteral(Sides.Server); if (string.IsNullOrWhiteSpace(fnName)) { @@ -421,9 +427,9 @@ bool ParseGenericBrkKeywordWithoutBlock(string keyword) bool ParseKeywordElseOrElseIf() { StorePos(); - ParseWhitespaceAndNewlines(); + ParseWhitespaceAndNewlines(Sides.Server); string elseStr = StepN(4); - ParseWhitespaceAndNewlines(); + ParseWhitespaceAndNewlines(Sides.Server); string elseIfStr = StepN(2); RestorePos(); @@ -442,9 +448,9 @@ bool ParseKeywordElseOrElseIf() // else if () {} bool ParseKeywordElseIf() { - ParseWhitespaceAndNewlines(); + ParseWhitespaceAndNewlines(Sides.Server); string elseStr = StepN(4); // eat else - ParseWhitespaceAndNewlines(); + ParseWhitespaceAndNewlines(Sides.Server); string elseIfStr = StepN(2); // ear if return ParseKeywordIf(); @@ -453,7 +459,7 @@ bool ParseKeywordElseIf() // else {} bool ParseKeywordElse() { - ParseWhitespaceAndNewlines(); + ParseWhitespaceAndNewlines(Sides.Server); //DiscardCurrentLexeme(); string elseStr = StepN(4); @@ -483,10 +489,16 @@ string StepN(int steps) return str; } - bool ParseWhitespaceAndNewlines() + bool ParseWhitespaceAndNewlines(Sides currentSide) { while (!IsAtEnd()) { + bool shouldContinue = LookaheadForTransitionClient(currentSide); + if (shouldContinue) + { + continue; + } + if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') { Step(); @@ -499,7 +511,7 @@ bool ParseWhitespaceAndNewlines() return true; } - bool NextLiteralSkipEmptyCharsMatches(string literal) + bool NextLiteralSkipEmptyCharsMatches(string literal, Sides currentSide) { if (IsAtEnd()) { @@ -511,7 +523,7 @@ bool NextLiteralSkipEmptyCharsMatches(string literal) bool started = false; while (!IsAtEnd()) { - bool shouldContinue = LookaheadForTransition(true); + bool shouldContinue = LookaheadForTransitionClient(currentSide); if (shouldContinue) { continue; @@ -579,8 +591,10 @@ void Error(int line, string message) /// /// /// true if we should end current iteration - bool LookaheadForTransition(bool calledFromClientSide) + bool LookaheadForTransitionClient(Sides currentSide) { + TokenTypes transitionType = currentSide == Sides.Client ? TokenTypes.ClientText : TokenTypes.BlockExpr; + if (Peek() == '@') { if (Peek(2) == '@') // @@ -> @ @@ -593,11 +607,8 @@ bool LookaheadForTransition(bool calledFromClientSide) if (Peek(2) == '*') // @* -> comment { - if (calledFromClientSide) - { - AddToken(TokenTypes.ClientText); - } - + AddToken(transitionType); + Step(); Step(); DiscardCurrentLexeme(); @@ -605,8 +616,8 @@ bool LookaheadForTransition(bool calledFromClientSide) return true; } - AddToken(TokenTypes.ClientText); - ParseTransition(); + AddToken(transitionType); + ParseTransition(currentSide); return true; } @@ -637,7 +648,7 @@ void ParseRestOfLineAsClient() char chr = Step(); while (!IsAtEnd() && chr != '\n' && chr != '\r') { - bool shouldContinue = LookaheadForTransition(true); + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); if (shouldContinue) { continue; @@ -668,7 +679,7 @@ void ParseClient() { while (!IsAtEnd()) { - bool shouldContinue = LookaheadForTransition(true); + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); if (shouldContinue) { continue; @@ -708,7 +719,7 @@ void RemoveLastCharFromCurrentLexeme() } } - void ParseTransition() + void ParseTransition(Sides currentSide) { /* Valid transition sequences are * @{ - block @@ -744,7 +755,7 @@ void ParseTransition() } else if (IsAlpha(c)) { - ParseImplicitExpression(); + ParseImplicitExpression(currentSide); } else { @@ -767,13 +778,13 @@ ImplicitExpressionTypes Str2ImplicitExprType(string str) return ImplicitExpressionTypes.Literal; } - string ParseLiteral() + string ParseLiteral(Sides currentSide) { ClearPooledBuilder(); while (!IsAtEnd()) { - bool shouldSkip = LookaheadForTransition(true); + bool shouldSkip = LookaheadForTransitionClient(currentSide); if (shouldSkip) { continue; @@ -793,14 +804,14 @@ string ParseLiteral() return str; } - string ParseLiteralStartsWithAlpha() + string ParseLiteralStartsWithAlpha(Sides currentSide) { ClearPooledBuilder(); bool first = true; while (!IsAtEnd()) { - bool shouldSkip = LookaheadForTransition(true); + bool shouldSkip = LookaheadForTransitionClient(currentSide); if (shouldSkip) { continue; @@ -845,11 +856,11 @@ void ParseExplicitExpression() AddToken(TokenTypes.ExplicitExpr); } - void ParseImplicitExpression() + void ParseImplicitExpression(Sides currentSide) { while (!IsAtEnd()) { - ParseLiteralStartsWithAlpha(); + ParseLiteralStartsWithAlpha(currentSide); string firstPart = GetCurrentLexeme(); ImplicitExpressionTypes firstPartType = Str2ImplicitExprType(firstPart); @@ -876,7 +887,7 @@ void ParseImplicitExpression() } Step(); - ParseLiteralStartsWithAlpha(); + ParseLiteralStartsWithAlpha(currentSide); } bufC = Peek(); @@ -1199,7 +1210,7 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) string s = GetCurrentLexeme(); while (!IsAtEnd()) { - bool shouldContinue = LookaheadForTransition(true); + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); if (shouldContinue) { continue; diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/2-function-comment.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/2-function-comment.html new file mode 100644 index 00000000..f9c76690 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/2-function-comment.html @@ -0,0 +1,3 @@ + + +
10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/2-function-comment.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/2-function-comment.wthtml new file mode 100644 index 00000000..fc705ea5 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/2-function-comment.wthtml @@ -0,0 +1,5 @@ +@function @* server comment *@ MyFunc(n) { +
@n
+} + +@MyFunc(10) \ No newline at end of file From fa400d0ab5150330315eda3e27d36bc88799b1b2 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 23:25:12 +0200 Subject: [PATCH 20/74] Add support for custom directives --- src/WattleScript.Templating/Class1.cs | 5 -- src/WattleScript.Templating/Template.cs | 53 +++++++------ src/WattleScript.Templating/Tokenizer.cs | 76 ++++++++++++++++--- .../WattleScript.Templating.csproj | 4 + .../Templating/TemplatingTestsRunner.cs | 7 +- .../AllowedKeyword/Directive/1-directive.html | 0 .../Directive/1-directive.wthtml | 1 + .../Directive/2-directive-combined.html | 2 + .../Directive/2-directive-combined.wthtml | 5 ++ 9 files changed, 106 insertions(+), 47 deletions(-) delete mode 100644 src/WattleScript.Templating/Class1.cs create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/1-directive.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/1-directive.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/2-directive-combined.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/2-directive-combined.wthtml diff --git a/src/WattleScript.Templating/Class1.cs b/src/WattleScript.Templating/Class1.cs deleted file mode 100644 index 7b3d6c47..00000000 --- a/src/WattleScript.Templating/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace WattleScript.Templating; - -public class Class1 -{ -} \ No newline at end of file diff --git a/src/WattleScript.Templating/Template.cs b/src/WattleScript.Templating/Template.cs index dc563e6a..697d0025 100644 --- a/src/WattleScript.Templating/Template.cs +++ b/src/WattleScript.Templating/Template.cs @@ -1,10 +1,11 @@ using System.Text; +using WattleScript.Interpreter; namespace WattleScript.Templating; public class Template { - public string EncodeJsString(string s) + string EncodeJsString(string s) { StringBuilder sb = new StringBuilder(); sb.Append("\""); @@ -51,7 +52,7 @@ public string EncodeJsString(string s) return sb.ToString(); } - public List Optimise(List? tokens) + List Optimise(List? tokens) { if (tokens == null) // if we have no tokens or only one we can't merge { @@ -90,9 +91,9 @@ public List Optimise(List? tokens) return tokens; } - public string Render(string code, bool optimise) + public string Render(Script script, string code, bool optimise) { - Tokenizer tk = new Tokenizer(); + Tokenizer tk = new Tokenizer(script); List tokens = tk.Tokenize(code); StringBuilder sb = new StringBuilder(); @@ -105,32 +106,30 @@ public string Render(string code, bool optimise) foreach (Token tkn in tokens) { - if (tkn.Type == TokenTypes.ClientText) + switch (tkn.Type) { - string lexeme = tkn.Lexeme; - if (firstClientPending) + case TokenTypes.ClientText: { - lexeme = lexeme.TrimStart(); - firstClientPending = false; - } + string lexeme = tkn.Lexeme; + if (firstClientPending) + { + lexeme = lexeme.TrimStart(); + firstClientPending = false; + } - sb.AppendLine($"stdout({EncodeJsString(lexeme)})"); - } - else if (tkn.Type == TokenTypes.BlockExpr) - { - sb.AppendLine(tkn.Lexeme); - } - else if (tkn.Type == TokenTypes.ImplicitExpr) - { - sb.AppendLine($"stdout({tkn.Lexeme})"); - } - else if (tkn.Type == TokenTypes.ExplicitExpr) - { - sb.AppendLine($"stdout({tkn.Lexeme})"); - } - else if (tkn.Type == TokenTypes.ServerComment) - { - sb.AppendLine($"/*{tkn.Lexeme}*/"); + sb.AppendLine($"stdout({EncodeJsString(lexeme)})"); + break; + } + case TokenTypes.BlockExpr: + sb.AppendLine(tkn.Lexeme); + break; + case TokenTypes.ImplicitExpr: + case TokenTypes.ExplicitExpr: + sb.AppendLine($"stdout({tkn.Lexeme})"); + break; + case TokenTypes.ServerComment: + sb.AppendLine($"/*{tkn.Lexeme}*/"); + break; } } diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Tokenizer.cs index 68f38bbe..38628dc3 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Tokenizer.cs @@ -1,4 +1,5 @@ using System.Text; +using WattleScript.Interpreter; namespace WattleScript.Templating; @@ -16,7 +17,7 @@ enum Sides Client, Server } - + private string Source { get; set; } private List Tokens { get; set; } = new List(); private List Messages { get; set; } = new List(); @@ -25,14 +26,20 @@ enum Sides private Dictionary?> KeywordsMap; private StringBuilder Buffer = new StringBuilder(); private StringBuilder PooledStringBuilder = new StringBuilder(); - private Token LastToken => Tokens[^1]; - int pos = 0; - char c; - string currentLexeme = ""; - int line = 1; + private int pos; + private char c; + private string currentLexeme = ""; + private int line = 1; + private Script? script; - public Tokenizer() + /// + /// + /// + /// can be null but templating engine won't resolve custom directives + public Tokenizer(Script? script) { + this.script = script; + KeywordsMap = new Dictionary?> { { "if", ParseKeywordIf }, @@ -60,17 +67,17 @@ string GetCurrentLexeme() bool IsAlphaNumeric(char ch) { - return !IsAtEnd() && (IsDigit(ch) || IsAlpha(ch)); + return IsDigit(ch) || IsAlpha(ch); } bool IsAlpha(char ch) { - return !IsAtEnd() && (char.IsLetter(ch) || ch is '_'); + return char.IsLetter(ch) || ch is '_'; } bool IsDigit(char ch) { - return !IsAtEnd() && (ch is >= '0' and <= '9'); + return ch is >= '0' and <= '9'; } char Step(int i = 1) @@ -369,6 +376,38 @@ bool ParseKeywordFunction() return ParseGenericBrkKeywordWithBlock("function"); } + // directive [a.b.c]? + // parser has to be positioned after "directive", before optional right hand + bool ParseDirective() + { + ParseWhitespaceAndNewlines(Sides.Server); + + // no right hand or invalid right hand + if (!IsAlpha(Peek())) + { + AddToken(TokenTypes.BlockExpr); + return true; + } + + while (!IsAtEnd()) + { + ParseLiteral(Sides.Server); + + if (Peek() == '.') + { + Step(); + } + + if (!IsAlpha(Peek())) + { + break; + } + } + + AddToken(TokenTypes.BlockExpr); + return true; + } + // keyword () {} bool ParseGenericBrkKeywordWithBlock(string keyword) { @@ -488,7 +527,7 @@ string StepN(int steps) return str; } - + bool ParseWhitespaceAndNewlines(Sides currentSide) { while (!IsAtEnd()) @@ -864,11 +903,20 @@ void ParseImplicitExpression(Sides currentSide) string firstPart = GetCurrentLexeme(); ImplicitExpressionTypes firstPartType = Str2ImplicitExprType(firstPart); + // 1. we check for known keywords if (firstPartType is ImplicitExpressionTypes.AllowedKeyword or ImplicitExpressionTypes.BannedKeyword) { ParseKeyword(); return; } + + // 2. scan custom directives, if the feature is available + // all directives are parsed as a sequence of oscillating [ALPHA, .] tokens + if (script?.Options.Directives.TryGetValue(firstPart, out _) ?? false) + { + ParseDirective(); + return; + } char bufC = Peek(); if (bufC == '[') @@ -900,14 +948,18 @@ void ParseImplicitExpression(Sides currentSide) AddToken(TokenTypes.ImplicitExpr); } - void ParseKeyword() + bool ParseKeyword() { string keyword = GetCurrentLexeme(); + // if keyword is from a know list of keywords we invoke a handler method of that keyword if (KeywordsMap.TryGetValue(keyword, out Func? resolver)) { resolver?.Invoke(); + return true; } + + return false; } /* Here the goal is to find the matching end of transition expression diff --git a/src/WattleScript.Templating/WattleScript.Templating.csproj b/src/WattleScript.Templating/WattleScript.Templating.csproj index eb2460e9..77ce6acd 100644 --- a/src/WattleScript.Templating/WattleScript.Templating.csproj +++ b/src/WattleScript.Templating/WattleScript.Templating.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index bc6996dc..978a1da6 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -38,14 +38,15 @@ public async Task RunCore(string path, bool reportErrors = false) string output = await File.ReadAllTextAsync(outputPath); StringBuilder stdOut = new StringBuilder(); - Template tmp = new Template(); - string transpiled = tmp.Render(code, true); - Script script = new Script(CoreModules.Preset_HardSandbox); script.Options.DebugPrint = s => stdOut.AppendLine(s); script.Options.IndexTablesFrom = 0; script.Options.AnnotationPolicy = new CustomPolicy(AnnotationValueParsingPolicy.ForceTable); script.Options.Syntax = ScriptSyntax.WattleScript; + script.Options.Directives.Add("using"); + + Template tmp = new Template(); + string transpiled = tmp.Render(script, code, true); void PrintLine(Script script, CallbackArguments args) { diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/1-directive.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/1-directive.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/1-directive.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/1-directive.wthtml new file mode 100644 index 00000000..cfd51051 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/1-directive.wthtml @@ -0,0 +1 @@ +@using myLib.somethingElse \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/2-directive-combined.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/2-directive-combined.html new file mode 100644 index 00000000..5f6e84cc --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/2-directive-combined.html @@ -0,0 +1,2 @@ + +100 \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/2-directive-combined.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/2-directive-combined.wthtml new file mode 100644 index 00000000..bc0f97fe --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Directive/2-directive-combined.wthtml @@ -0,0 +1,5 @@ +@using myLib.somethingElse +@{ + x = 100 +} +@x \ No newline at end of file From 907bafd7b855280b20c23d5691e39c963a3619a9 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 19 Apr 2022 23:55:21 +0200 Subject: [PATCH 21/74] Cleanup, added public method debug --- src/WattleScript.Templating/Extensions.cs | 12 + .../{Tokenizer.cs => Parser/Parser.cs} | 477 +----------------- .../Parser/ParserKeywords.cs | 229 +++++++++ .../Parser/ParserUtils.cs | 185 +++++++ .../{Template.cs => TemplatingEngine.cs} | 40 +- src/WattleScript.Templating/Token.cs | 34 +- .../Templating/TemplatingTestsRunner.cs | 27 +- 7 files changed, 519 insertions(+), 485 deletions(-) create mode 100644 src/WattleScript.Templating/Extensions.cs rename src/WattleScript.Templating/{Tokenizer.cs => Parser/Parser.cs} (71%) create mode 100644 src/WattleScript.Templating/Parser/ParserKeywords.cs create mode 100644 src/WattleScript.Templating/Parser/ParserUtils.cs rename src/WattleScript.Templating/{Template.cs => TemplatingEngine.cs} (76%) diff --git a/src/WattleScript.Templating/Extensions.cs b/src/WattleScript.Templating/Extensions.cs new file mode 100644 index 00000000..067bce96 --- /dev/null +++ b/src/WattleScript.Templating/Extensions.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; + +namespace WattleScript.Templating; + +internal static class Extensions +{ + public static string ToDescriptionString(this Enum val) + { + DescriptionAttribute[] attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString())?.GetCustomAttributes(typeof(DescriptionAttribute), false)!; + return attributes.Length > 0 ? attributes[0].Description : ""; + } +} \ No newline at end of file diff --git a/src/WattleScript.Templating/Tokenizer.cs b/src/WattleScript.Templating/Parser/Parser.cs similarity index 71% rename from src/WattleScript.Templating/Tokenizer.cs rename to src/WattleScript.Templating/Parser/Parser.cs index 38628dc3..dbac5114 100644 --- a/src/WattleScript.Templating/Tokenizer.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -3,7 +3,7 @@ namespace WattleScript.Templating; -public class Tokenizer +internal partial class Parser { enum ImplicitExpressionTypes { @@ -17,8 +17,14 @@ enum Sides Client, Server } + + enum StepModes + { + CurrentLexeme, + Buffer + } - private string Source { get; set; } + private string? source; private List Tokens { get; set; } = new List(); private List Messages { get; set; } = new List(); private List AllowedTransitionKeywords = new List() {"if", "for", "do", "while", "require", "function"}; @@ -27,19 +33,22 @@ enum Sides private StringBuilder Buffer = new StringBuilder(); private StringBuilder PooledStringBuilder = new StringBuilder(); private int pos; + private int lastCommitedPos; + private int lastCommitedLine; private char c; private string currentLexeme = ""; private int line = 1; private Script? script; + private StepModes stepMode = StepModes.CurrentLexeme; /// /// /// /// can be null but templating engine won't resolve custom directives - public Tokenizer(Script? script) + public Parser(Script? script) { this.script = script; - + KeywordsMap = new Dictionary?> { { "if", ParseKeywordIf }, @@ -49,96 +58,20 @@ public Tokenizer(Script? script) { "function", ParseKeywordFunction } }; } - - void ClearPooledBuilder() - { - PooledStringBuilder.Clear(); - } - bool IsAtEnd() - { - return pos >= Source.Length; - } - - string GetCurrentLexeme() - { - return currentLexeme; - } - - bool IsAlphaNumeric(char ch) - { - return IsDigit(ch) || IsAlpha(ch); - } - - bool IsAlpha(char ch) - { - return char.IsLetter(ch) || ch is '_'; - } - - bool IsDigit(char ch) - { - return ch is >= '0' and <= '9'; - } - - char Step(int i = 1) - { - char cc = Source[pos]; - - if (stepMode == StepModes.CurrentLexeme) - { - currentLexeme += cc; - } - else - { - Buffer.Append(cc); - } - - pos += i; - c = cc; - return cc; - } - - private int storedPos; - void StorePos() + public List Parse(string templateSource, bool includeNewlines = false) { - storedPos = pos; - } - - void RestorePos() - { - pos = storedPos; - if (pos >= Source.Length) - { - pos = Source.Length - 1; - } - - storedPos = 0; - c = Source[pos]; - DiscardCurrentLexeme(); - } + source = templateSource; + ParseClient(); - char Peek(int i = 1) - { if (IsAtEnd()) { - return '\n'; - } - - int peekedPos = pos + i - 1; - - if (peekedPos < 0) - { - pos = 0; - } - - if (Source.Length <= peekedPos) - { - return Source[^1]; + AddToken(TokenTypes.Eof); } - return Source[peekedPos]; + return Tokens; } - + bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced, bool handleStrings, bool handleServerComments) { bool inString = false; @@ -230,22 +163,6 @@ void HandleStringSequence(char chr) return false; } - bool Match(char expected) - { - if (IsAtEnd()) - { - return false; - } - - if (Source[pos] != expected) - { - return false; - } - - Step(); - return true; - } - bool MatchNextNonWhiteSpaceChar(char ch) { while (!IsAtEnd()) @@ -289,245 +206,7 @@ bool MatchNextNonWhiteSpaceNonNewlineChar(char ch) return false; } - - // if (expr) {} - // parser has to be positioned after if, either at opening ( or at whitespace before it - bool ParseKeywordIf() - { - ParseGenericBrkKeywordWithBlock("if"); - - bool matchesElse = NextLiteralSkipEmptyCharsMatches("else", Sides.Server) || NextLiteralSkipEmptyCharsMatches("elseif", Sides.Server); // else handles "else if" but we have to check for "elseif" manually - if (matchesElse) - { - ParseKeywordElseOrElseIf(); - } - - return false; - } - - // for (i in a..b) - // for (i = 0; i < x; i++) - // for (;;) - // we always have () around expr/s - // parser has to be positioned after "for", either at opening ( or at a whitespace preceding it - bool ParseKeywordFor() - { - return ParseGenericBrkKeywordWithBlock("for"); - } - - // while (i in a..b) {} - // parser has to be positioned after "while", either at opening ( or at a whitespace preceding it - bool ParseKeywordWhile() - { - return ParseGenericBrkKeywordWithBlock("while"); - } - - // do {} while () - // parser has to be positioned after "do", either at opening { or at a whitespace preceding it - bool ParseKeywordDo() - { - bool doParsed = ParseGenericKeywordWithBlock("do"); - bool matchesWhile = NextLiteralSkipEmptyCharsMatches("while", Sides.Server); - - if (matchesWhile) - { - ParseWhitespaceAndNewlines(Sides.Server); - string whileStr = StepN(5); - bool whileParsed = ParseGenericBrkKeywordWithoutBlock("while"); - - if (whileParsed) - { - AddToken(TokenTypes.BlockExpr); - } - } - - return doParsed; - } - - // function foo() {} - // parser has to be positioned after "function", either at first ALPHA char of the function's name or whitespace preceding it - bool ParseKeywordFunction() - { - ParseWhitespaceAndNewlines(Sides.Server); - - char chr = Peek(); - if (chr == '(') - { - return Throw("Missing function's name"); - } - - if (chr == '{') - { - return Throw("Missing function's name and signature"); - } - - if (!IsAlpha(chr)) - { - return Throw("First char in function's name has to be an alpha character"); - } - - string fnName = ParseLiteral(Sides.Server); - - if (string.IsNullOrWhiteSpace(fnName)) - { - return Throw("Missing function's name"); - } - - return ParseGenericBrkKeywordWithBlock("function"); - } - - // directive [a.b.c]? - // parser has to be positioned after "directive", before optional right hand - bool ParseDirective() - { - ParseWhitespaceAndNewlines(Sides.Server); - - // no right hand or invalid right hand - if (!IsAlpha(Peek())) - { - AddToken(TokenTypes.BlockExpr); - return true; - } - - while (!IsAtEnd()) - { - ParseLiteral(Sides.Server); - - if (Peek() == '.') - { - Step(); - } - - if (!IsAlpha(Peek())) - { - break; - } - } - - AddToken(TokenTypes.BlockExpr); - return true; - } - - // keyword () {} - bool ParseGenericBrkKeywordWithBlock(string keyword) - { - bool openBrkMatched = MatchNextNonWhiteSpaceChar('('); - string l = GetCurrentLexeme(); - - if (!openBrkMatched) - { - Throw($"Expected ( after {keyword}"); - } - - bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true, true); - l = GetCurrentLexeme(); - - if (!endExprMatched) - { - return false; - } - - ParseCodeBlock(true); - return true; - } - - // keyword {} - bool ParseGenericKeywordWithBlock(string keyword) - { - bool openBrkMatched = MatchNextNonWhiteSpaceChar('{'); - string l = GetCurrentLexeme(); - - if (!openBrkMatched) - { - Throw($"Expected {{ after {keyword}"); - } - - ParseCodeBlock(true); - return true; - } - - // keyword () - bool ParseGenericBrkKeywordWithoutBlock(string keyword) - { - bool openBrkMatched = MatchNextNonWhiteSpaceChar('('); - string l = GetCurrentLexeme(); - - if (!openBrkMatched) - { - Throw($"Expected ( after {keyword}"); - } - - bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true, true); - return endExprMatched; - } - - // else {} - // or possibly else if () {} - bool ParseKeywordElseOrElseIf() - { - StorePos(); - ParseWhitespaceAndNewlines(Sides.Server); - string elseStr = StepN(4); - ParseWhitespaceAndNewlines(Sides.Server); - string elseIfStr = StepN(2); - RestorePos(); - - if (elseStr == "else" && elseIfStr == "if") - { - ParseKeywordElseIf(); - } - else if (elseStr == "else") - { - ParseKeywordElse(); - } - - return true; - } - - // else if () {} - bool ParseKeywordElseIf() - { - ParseWhitespaceAndNewlines(Sides.Server); - string elseStr = StepN(4); // eat else - ParseWhitespaceAndNewlines(Sides.Server); - string elseIfStr = StepN(2); // ear if - - return ParseKeywordIf(); - } - - // else {} - bool ParseKeywordElse() - { - ParseWhitespaceAndNewlines(Sides.Server); - //DiscardCurrentLexeme(); - - string elseStr = StepN(4); - - if (elseStr != "else") - { - return false; - } - - string str = GetCurrentLexeme(); - - ParseCodeBlock(true); - - return false; - } - - string StepN(int steps) - { - string str = ""; - while (!IsAtEnd() && steps > 0) - { - char chr = Step(); - str += chr; - steps--; - } - return str; - } - bool ParseWhitespaceAndNewlines(Sides currentSide) { while (!IsAtEnd()) @@ -598,41 +277,13 @@ bool NextLiteralSkipEmptyCharsMatches(string literal, Sides currentSide) return match; } - public List Tokenize(string source, bool includeNewlines = false) - { - bool tokenize = true; - bool anyErrors = false; - Source = source; - - ParseClient(); - - if (IsAtEnd()) - { - tokenize = false; - AddToken(TokenTypes.Eof); - } - - if (anyErrors) - { - tokenize = false; - } - - void Error(int line, string message) - { - anyErrors = true; - Messages.Add($"Error at line {line} - {message}"); - } - - return Tokens; - } - /// /// /// /// true if we should end current iteration bool LookaheadForTransitionClient(Sides currentSide) { - TokenTypes transitionType = currentSide == Sides.Client ? TokenTypes.ClientText : TokenTypes.BlockExpr; + TokenTypes transitionType = currentSide == Sides.Client ? TokenTypes.Text : TokenTypes.BlockExpr; if (Peek() == '@') { @@ -706,7 +357,7 @@ void ParseRestOfLineAsClient() } } - AddToken(TokenTypes.ClientText); + AddToken(TokenTypes.Text); } /* In client mode everything is a literal @@ -727,7 +378,7 @@ void ParseClient() Step(); } - AddToken(TokenTypes.ClientText); + AddToken(TokenTypes.Text); } // @* *@ @@ -742,7 +393,7 @@ void ParseServerComment() Step(); RemoveLastCharFromCurrentLexeme(); RemoveLastCharFromCurrentLexeme(); // eat and discard closing *@ - AddToken(TokenTypes.ServerComment); + AddToken(TokenTypes.Comment); break; } @@ -1150,28 +801,6 @@ void HandleStringSequence(char chr) string str = GetCurrentLexeme(); } - void ClearBuffer() - { - Buffer.Clear(); - } - - private StepModes stepMode = StepModes.CurrentLexeme; - enum StepModes - { - CurrentLexeme, - Buffer - } - - void SetStepMode(StepModes mode) - { - stepMode = mode; - } - - bool Throw(string message) - { - throw new Exception(message); - } - string ParseHtmlTagNameInBuffer() { // First char in a proper HTML tag (after opening <) can be [_, !, /, Alpha, ?] @@ -1227,7 +856,7 @@ bool ParseHtmlTag() } string s = GetCurrentLexeme(); - AddToken(TokenTypes.ClientText); + AddToken(TokenTypes.Text); return true; } @@ -1297,62 +926,4 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) s = GetCurrentLexeme(); } - - char GetNextCharNotWhitespace() - { - StorePos(); - char ch = '\0'; - - while (!IsAtEnd()) - { - ch = Step(); - if (Peek() != ' ') - { - break; - } - } - RestorePos(); - - return ch; - } - - string GetBuffer() - { - return Buffer.ToString(); - } - - bool CurrentLexemeIsSelfClosedHtmlTag() - { - return GetCurrentLexeme().EndsWith("/>"); - } - - bool IsSelfClosingHtmlTag(string htmlTag) - { - return IsSelfClosing(htmlTag.ToLowerInvariant()); - } - - void AddToken(TokenTypes type) - { - if (currentLexeme.Length > 0) - { - Token token = new Token(type, currentLexeme, null, line); - Tokens.Add(token); - DiscardCurrentLexeme(); - } - } - - void DiscardCurrentLexeme() - { - currentLexeme = ""; - } - - void AddBufferToCurrentLexeme() - { - currentLexeme += Buffer.ToString(); - } - - bool IsSelfClosing(string tagName) - { - return tagName == "area" || tagName == "base" || tagName == "br" || tagName == "col" || tagName == "embed" || tagName == "hr" || tagName == "img" || tagName == "input" || tagName == "keygen" || tagName == "link" || tagName == "menuitem" || tagName == "meta" || tagName == "param" || tagName == "source" || tagName == "track" || tagName == "wbr"; - } } \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/ParserKeywords.cs b/src/WattleScript.Templating/Parser/ParserKeywords.cs new file mode 100644 index 00000000..ee10e6cf --- /dev/null +++ b/src/WattleScript.Templating/Parser/ParserKeywords.cs @@ -0,0 +1,229 @@ +namespace WattleScript.Templating; + +internal partial class Parser +{ + // if (expr) {} + // parser has to be positioned after if, either at opening ( or at whitespace before it + bool ParseKeywordIf() + { + ParseGenericBrkKeywordWithBlock("if"); + + bool matchesElse = NextLiteralSkipEmptyCharsMatches("else", Sides.Server) || NextLiteralSkipEmptyCharsMatches("elseif", Sides.Server); // else handles "else if" but we have to check for "elseif" manually + if (matchesElse) + { + ParseKeywordElseOrElseIf(); + } + + return false; + } + + // for (i in a..b) + // for (i = 0; i < x; i++) + // for (;;) + // we always have () around expr/s + // parser has to be positioned after "for", either at opening ( or at a whitespace preceding it + bool ParseKeywordFor() + { + return ParseGenericBrkKeywordWithBlock("for"); + } + + // while (i in a..b) {} + // parser has to be positioned after "while", either at opening ( or at a whitespace preceding it + bool ParseKeywordWhile() + { + return ParseGenericBrkKeywordWithBlock("while"); + } + + // do {} while () + // parser has to be positioned after "do", either at opening { or at a whitespace preceding it + bool ParseKeywordDo() + { + bool doParsed = ParseGenericKeywordWithBlock("do"); + bool matchesWhile = NextLiteralSkipEmptyCharsMatches("while", Sides.Server); + + if (matchesWhile) + { + ParseWhitespaceAndNewlines(Sides.Server); + string whileStr = StepN(5); + bool whileParsed = ParseGenericBrkKeywordWithoutBlock("while"); + + if (whileParsed) + { + AddToken(TokenTypes.BlockExpr); + } + } + + return doParsed; + } + + // function foo() {} + // parser has to be positioned after "function", either at first ALPHA char of the function's name or whitespace preceding it + bool ParseKeywordFunction() + { + ParseWhitespaceAndNewlines(Sides.Server); + + char chr = Peek(); + if (chr == '(') + { + return Throw("Missing function's name"); + } + + if (chr == '{') + { + return Throw("Missing function's name and signature"); + } + + if (!IsAlpha(chr)) + { + return Throw("First char in function's name has to be an alpha character"); + } + + string fnName = ParseLiteral(Sides.Server); + + if (string.IsNullOrWhiteSpace(fnName)) + { + return Throw("Missing function's name"); + } + + return ParseGenericBrkKeywordWithBlock("function"); + } + + // directive [a.b.c]? + // parser has to be positioned after "directive", before optional right hand + bool ParseDirective() + { + ParseWhitespaceAndNewlines(Sides.Server); + + // no right hand or invalid right hand + if (!IsAlpha(Peek())) + { + AddToken(TokenTypes.BlockExpr); + return true; + } + + while (!IsAtEnd()) + { + ParseLiteral(Sides.Server); + + if (Peek() == '.') + { + Step(); + } + + if (!IsAlpha(Peek())) + { + break; + } + } + + AddToken(TokenTypes.BlockExpr); + return true; + } + + // keyword () {} + bool ParseGenericBrkKeywordWithBlock(string keyword) + { + bool openBrkMatched = MatchNextNonWhiteSpaceChar('('); + string l = GetCurrentLexeme(); + + if (!openBrkMatched) + { + Throw($"Expected ( after {keyword}"); + } + + bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true, true); + l = GetCurrentLexeme(); + + if (!endExprMatched) + { + return false; + } + + ParseCodeBlock(true); + return true; + } + + // keyword {} + bool ParseGenericKeywordWithBlock(string keyword) + { + bool openBrkMatched = MatchNextNonWhiteSpaceChar('{'); + string l = GetCurrentLexeme(); + + if (!openBrkMatched) + { + Throw($"Expected {{ after {keyword}"); + } + + ParseCodeBlock(true); + return true; + } + + // keyword () + bool ParseGenericBrkKeywordWithoutBlock(string keyword) + { + bool openBrkMatched = MatchNextNonWhiteSpaceChar('('); + string l = GetCurrentLexeme(); + + if (!openBrkMatched) + { + Throw($"Expected ( after {keyword}"); + } + + bool endExprMatched = ParseUntilBalancedChar('(', ')', true, true, true); + return endExprMatched; + } + + // else {} + // or possibly else if () {} + bool ParseKeywordElseOrElseIf() + { + StorePos(); + ParseWhitespaceAndNewlines(Sides.Server); + string elseStr = StepN(4); + ParseWhitespaceAndNewlines(Sides.Server); + string elseIfStr = StepN(2); + RestorePos(); + + if (elseStr == "else" && elseIfStr == "if") + { + ParseKeywordElseIf(); + } + else if (elseStr == "else") + { + ParseKeywordElse(); + } + + return true; + } + + // else if () {} + bool ParseKeywordElseIf() + { + ParseWhitespaceAndNewlines(Sides.Server); + string elseStr = StepN(4); // eat else + ParseWhitespaceAndNewlines(Sides.Server); + string elseIfStr = StepN(2); // ear if + + return ParseKeywordIf(); + } + + // else {} + bool ParseKeywordElse() + { + ParseWhitespaceAndNewlines(Sides.Server); + //DiscardCurrentLexeme(); + + string elseStr = StepN(4); + + if (elseStr != "else") + { + return false; + } + + string str = GetCurrentLexeme(); + + ParseCodeBlock(true); + + return false; + } +} \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs new file mode 100644 index 00000000..0a8df277 --- /dev/null +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -0,0 +1,185 @@ +namespace WattleScript.Templating; + +internal partial class Parser +{ + void ClearPooledBuilder() + { + PooledStringBuilder.Clear(); + } + + bool IsAtEnd() + { + return pos >= source.Length; + } + + string GetCurrentLexeme() + { + return currentLexeme; + } + + bool IsAlphaNumeric(char ch) + { + return IsDigit(ch) || IsAlpha(ch); + } + + bool IsAlpha(char ch) + { + return char.IsLetter(ch) || ch is '_'; + } + + bool IsDigit(char ch) + { + return ch is >= '0' and <= '9'; + } + + char Step(int i = 1) + { + char cc = source[pos]; + + if (stepMode == StepModes.CurrentLexeme) + { + currentLexeme += cc; + } + else + { + Buffer.Append(cc); + } + + pos += i; + c = cc; + return cc; + } + + private int storedPos; + void StorePos() + { + storedPos = pos; + } + + void RestorePos() + { + pos = storedPos; + if (pos >= source.Length) + { + pos = source.Length - 1; + } + + storedPos = 0; + c = source[pos]; + DiscardCurrentLexeme(); + } + + char Peek(int i = 1) + { + if (IsAtEnd()) + { + return '\n'; + } + + int peekedPos = pos + i - 1; + + if (peekedPos < 0) + { + pos = 0; + } + + if (source.Length <= peekedPos) + { + return source[^1]; + } + + return source[peekedPos]; + } + + string StepN(int steps) + { + string str = ""; + while (!IsAtEnd() && steps > 0) + { + char chr = Step(); + str += chr; + steps--; + } + + return str; + } + + char GetNextCharNotWhitespace() + { + StorePos(); + char ch = '\0'; + + while (!IsAtEnd()) + { + ch = Step(); + if (Peek() != ' ') + { + break; + } + } + RestorePos(); + + return ch; + } + + string GetBuffer() + { + return Buffer.ToString(); + } + + bool CurrentLexemeIsSelfClosedHtmlTag() + { + return GetCurrentLexeme().EndsWith("/>"); + } + + bool IsSelfClosingHtmlTag(string htmlTag) + { + return IsSelfClosing(htmlTag.ToLowerInvariant()); + } + + bool AddToken(TokenTypes type) + { + if (currentLexeme.Length == 0) + { + return false; + } + + Token token = new Token(type, currentLexeme, lastCommitedLine + 1, line + 1, lastCommitedPos + 1, pos + 1); + Tokens.Add(token); + DiscardCurrentLexeme(); + + lastCommitedPos = pos; + lastCommitedLine = line; + return true; + } + + void DiscardCurrentLexeme() + { + currentLexeme = ""; + } + + void AddBufferToCurrentLexeme() + { + currentLexeme += Buffer.ToString(); + } + + bool IsSelfClosing(string tagName) + { + return tagName == "area" || tagName == "base" || tagName == "br" || tagName == "col" || tagName == "embed" || tagName == "hr" || tagName == "img" || tagName == "input" || tagName == "keygen" || tagName == "link" || tagName == "menuitem" || tagName == "meta" || tagName == "param" || tagName == "source" || tagName == "track" || tagName == "wbr"; + } + + void ClearBuffer() + { + Buffer.Clear(); + } + + void SetStepMode(StepModes mode) + { + stepMode = mode; + } + + bool Throw(string message) + { + throw new Exception(message); + } +} \ No newline at end of file diff --git a/src/WattleScript.Templating/Template.cs b/src/WattleScript.Templating/TemplatingEngine.cs similarity index 76% rename from src/WattleScript.Templating/Template.cs rename to src/WattleScript.Templating/TemplatingEngine.cs index 697d0025..beaa2cd9 100644 --- a/src/WattleScript.Templating/Template.cs +++ b/src/WattleScript.Templating/TemplatingEngine.cs @@ -3,12 +3,12 @@ namespace WattleScript.Templating; -public class Template +public class TemplatingEngine { string EncodeJsString(string s) { StringBuilder sb = new StringBuilder(); - sb.Append("\""); + sb.Append('"'); foreach (char c in s) { switch (c) @@ -47,8 +47,7 @@ string EncodeJsString(string s) break; } } - sb.Append("\""); - + sb.Append('"'); return sb.ToString(); } @@ -79,6 +78,11 @@ List Optimise(List? tokens) if (token.Type == nextToken.Type) { token.Lexeme += nextToken.Lexeme; + token.FromLine = Math.Min(token.FromLine, nextToken.FromLine); + token.ToLine = Math.Max(token.ToLine, nextToken.ToLine); + token.StartCol = Math.Min(token.StartCol, nextToken.StartCol); + token.EndCol = Math.Max(token.EndCol, nextToken.EndCol); + tokens.RemoveAt(i); i--; continue; @@ -90,11 +94,31 @@ List Optimise(List? tokens) return tokens; } + + public string Debug(Script script, string code, bool optimise) + { + Parser parser = new Parser(script); + List tokens = parser.Parse(code); + StringBuilder sb = new StringBuilder(); + + if (optimise) + { + tokens = Optimise(tokens); + } + + foreach (Token tkn in tokens) + { + sb.AppendLine(tkn.ToString()); + } + + string finalText = sb.ToString(); + return finalText; + } public string Render(Script script, string code, bool optimise) { - Tokenizer tk = new Tokenizer(script); - List tokens = tk.Tokenize(code); + Parser parser = new Parser(script); + List tokens = parser.Parse(code); StringBuilder sb = new StringBuilder(); bool firstClientPending = true; @@ -108,7 +132,7 @@ public string Render(Script script, string code, bool optimise) { switch (tkn.Type) { - case TokenTypes.ClientText: + case TokenTypes.Text: { string lexeme = tkn.Lexeme; if (firstClientPending) @@ -127,7 +151,7 @@ public string Render(Script script, string code, bool optimise) case TokenTypes.ExplicitExpr: sb.AppendLine($"stdout({tkn.Lexeme})"); break; - case TokenTypes.ServerComment: + case TokenTypes.Comment: sb.AppendLine($"/*{tkn.Lexeme}*/"); break; } diff --git a/src/WattleScript.Templating/Token.cs b/src/WattleScript.Templating/Token.cs index 204995ef..788f65b9 100644 --- a/src/WattleScript.Templating/Token.cs +++ b/src/WattleScript.Templating/Token.cs @@ -1,34 +1,44 @@ -namespace WattleScript.Templating; +using System.ComponentModel; +namespace WattleScript.Templating; - -public enum TokenTypes +internal enum TokenTypes { + [Description("BLOCK")] BlockExpr, + [Description("EXPLICIT")] ExplicitExpr, + [Description("IMPLICIT")] ImplicitExpr, - ClientText, - ServerComment, + [Description("TEXT")] + Text, + [Description("COMMENT")] + Comment, + [Description("EOF")] Eof, Length } -public class Token +internal class Token { public TokenTypes Type { get; set; } public string Lexeme { get; set; } - public object Literal { get; set; } - public int Line { get; set; } + public int FromLine { get; set; } + public int ToLine { get; set; } + public int StartCol { get; set; } + public int EndCol { get; set; } - public Token(TokenTypes type, string lexeme, object literal, int line) + public Token(TokenTypes type, string lexeme, int fromLine, int toLine, int startCol, int endCol) { Type = type; Lexeme = lexeme; - Literal = literal; - Line = line; + FromLine = fromLine; + ToLine = toLine; + StartCol = startCol; + EndCol = endCol; } public override string ToString() { - return $"{Type} - {Lexeme} - {Literal}"; + return $"{Type.ToDescriptionString()} [ln {FromLine}-{ToLine}, col {StartCol}-{EndCol}] - {Lexeme}"; } } \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index 978a1da6..61129788 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -26,6 +26,18 @@ public async Task RunThrowErros(string path) public async Task RunCore(string path, bool reportErrors = false) { + StringBuilder stdOut = new StringBuilder(); + + void PrintLine(Script script, CallbackArguments args) + { + stdOut.AppendLine(args[0].CastToString()); + } + + void Print(Script script, CallbackArguments args) + { + stdOut.Append(args[0].CastToString()); + } + string outputPath = path.Replace(".wthtml", ".html"); if (!File.Exists(outputPath)) @@ -36,7 +48,6 @@ public async Task RunCore(string path, bool reportErrors = false) string code = await File.ReadAllTextAsync(path); string output = await File.ReadAllTextAsync(outputPath); - StringBuilder stdOut = new StringBuilder(); Script script = new Script(CoreModules.Preset_HardSandbox); script.Options.DebugPrint = s => stdOut.AppendLine(s); @@ -45,19 +56,11 @@ public async Task RunCore(string path, bool reportErrors = false) script.Options.Syntax = ScriptSyntax.WattleScript; script.Options.Directives.Add("using"); - Template tmp = new Template(); + TemplatingEngine tmp = new TemplatingEngine(); string transpiled = tmp.Render(script, code, true); - void PrintLine(Script script, CallbackArguments args) - { - stdOut.AppendLine(args[0].CastToString()); - } - - void Print(Script script, CallbackArguments args) - { - stdOut.Append(args[0].CastToString()); - } - + string debugStr = tmp.Debug(script, code, true); + script.Globals["stdout_line"] = PrintLine; script.Globals["stdout"] = Print; From 686a7fe52862f8437d4681c06669fce2723b4471 Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 20 Apr 2022 00:27:47 +0200 Subject: [PATCH 22/74] Implemented missing @: in transition --- src/WattleScript.Templating/Parser/Parser.cs | 18 ++++++++++++++---- .../Function/3-function-annotation.html | 3 +++ .../Function/3-function-annotation.wthtml | 8 ++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/3-function-annotation.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/3-function-annotation.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index dbac5114..01ed54f0 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -409,7 +409,7 @@ void RemoveLastCharFromCurrentLexeme() } } - void ParseTransition(Sides currentSide) + bool ParseTransition(Sides currentSide) { /* Valid transition sequences are * @{ - block @@ -438,10 +438,10 @@ void ParseTransition(Sides currentSide) DiscardCurrentLexeme(); ParseExplicitExpression(); } - else if (c == ':') + else if (c == ':' && currentSide == Sides.Client) { DiscardCurrentLexeme(); - + ParseRestOfLineAsClient(); } else if (IsAlpha(c)) { @@ -449,8 +449,18 @@ void ParseTransition(Sides currentSide) } else { - // [todo] report err, synchronise + // [todo] either an invalid transition or an annotation + if (currentSide == Sides.Server) + { + // we don't know enough to decide so we treat it as an annotation in server mode for now + currentLexeme = $"@{GetCurrentLexeme()}"; + return true; + } + + return Throw("Invalid character after @"); } + + return false; } ImplicitExpressionTypes Str2ImplicitExprType(string str) diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/3-function-annotation.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/3-function-annotation.html new file mode 100644 index 00000000..f9c76690 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/3-function-annotation.html @@ -0,0 +1,3 @@ + + +
10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/3-function-annotation.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/3-function-annotation.wthtml new file mode 100644 index 00000000..77a7f577 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Function/3-function-annotation.wthtml @@ -0,0 +1,8 @@ +@{ + @bind('myText') + function MyFunc(n) { +
@n
+ } +} + +@MyFunc(10) \ No newline at end of file From 25506d15c842f51e886094607d8c546ba9315ff9 Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 20 Apr 2022 00:33:48 +0200 Subject: [PATCH 23/74] Avoid allocating in EncodeJsString --- .../TemplatingEngine.cs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/WattleScript.Templating/TemplatingEngine.cs b/src/WattleScript.Templating/TemplatingEngine.cs index beaa2cd9..d2c8de99 100644 --- a/src/WattleScript.Templating/TemplatingEngine.cs +++ b/src/WattleScript.Templating/TemplatingEngine.cs @@ -5,50 +5,52 @@ namespace WattleScript.Templating; public class TemplatingEngine { + private readonly StringBuilder pooledSb = new StringBuilder(); + string EncodeJsString(string s) { - StringBuilder sb = new StringBuilder(); - sb.Append('"'); + pooledSb.Clear(); + pooledSb.Append('"'); foreach (char c in s) { switch (c) { case '\"': - sb.Append("\\\""); + pooledSb.Append("\\\""); break; case '\\': - sb.Append("\\\\"); + pooledSb.Append("\\\\"); break; case '\b': - sb.Append("\\b"); + pooledSb.Append("\\b"); break; case '\f': - sb.Append("\\f"); + pooledSb.Append("\\f"); break; case '\n': - sb.Append("\\n"); + pooledSb.Append("\\n"); break; case '\r': - sb.Append("\\r"); + pooledSb.Append("\\r"); break; case '\t': - sb.Append("\\t"); + pooledSb.Append("\\t"); break; default: - int i = (int)c; - if (i < 32 || i > 127) + int i = c; + if (i is < 32 or > 127) { - sb.AppendFormat("\\u{0:X04}", i); + pooledSb.Append($"\\u{i:X04}"); } else { - sb.Append(c); + pooledSb.Append(c); } break; } } - sb.Append('"'); - return sb.ToString(); + pooledSb.Append('"'); + return pooledSb.ToString(); } List Optimise(List? tokens) @@ -99,8 +101,8 @@ public string Debug(Script script, string code, bool optimise) { Parser parser = new Parser(script); List tokens = parser.Parse(code); - StringBuilder sb = new StringBuilder(); - + pooledSb.Clear(); + if (optimise) { tokens = Optimise(tokens); @@ -108,10 +110,10 @@ public string Debug(Script script, string code, bool optimise) foreach (Token tkn in tokens) { - sb.AppendLine(tkn.ToString()); + pooledSb.AppendLine(tkn.ToString()); } - string finalText = sb.ToString(); + string finalText = pooledSb.ToString(); return finalText; } From 9f3ae298d1df42e063eac7350767cdf0e7500ba1 Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 20 Apr 2022 01:55:42 +0200 Subject: [PATCH 24/74] Added support for @! --- src/WattleScript.Templating/Parser/Parser.cs | 62 +++++++++++++++++-- .../Parser/ParserKeywords.cs | 6 +- .../Parser/ParserUtils.cs | 10 +++ .../Tests/ExplicitExpr/7-explicit-escape.html | 5 ++ .../ExplicitExpr/7-explicit-escape.wthtml | 7 +++ 5 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/7-explicit-escape.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ExplicitExpr/7-explicit-escape.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 01ed54f0..edd18db7 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -40,6 +40,7 @@ enum StepModes private int line = 1; private Script? script; private StepModes stepMode = StepModes.CurrentLexeme; + private bool parsingTransitionCharactersEnabled = true; /// /// @@ -206,6 +207,32 @@ bool MatchNextNonWhiteSpaceNonNewlineChar(char ch) return false; } + + bool NextNonWhiteSpaceCharMatches(char ch) + { + StorePos(); + while (!IsAtEnd()) + { + if (Peek() == ' ') + { + Step(); + } + else if (Peek() == ch) + { + Step(); + RestorePos(); + return true; + } + else + { + RestorePos(); + return false; + } + } + + RestorePos(); + return false; + } bool ParseWhitespaceAndNewlines(Sides currentSide) { @@ -283,6 +310,11 @@ bool NextLiteralSkipEmptyCharsMatches(string literal, Sides currentSide) /// true if we should end current iteration bool LookaheadForTransitionClient(Sides currentSide) { + if (!ParsingControlChars()) + { + return false; + } + TokenTypes transitionType = currentSide == Sides.Client ? TokenTypes.Text : TokenTypes.BlockExpr; if (Peek() == '@') @@ -316,6 +348,11 @@ bool LookaheadForTransitionClient(Sides currentSide) bool LookaheadForTransitionServerSide() { + if (!ParsingControlChars()) + { + return false; + } + if (Peek() == '@') { if (Peek(2) == ':') @@ -415,6 +452,7 @@ bool ParseTransition(Sides currentSide) * @{ - block * @( - explicit expression * @: - line transition (back to client) + * @! - explicit escape expression * @TKeyword - if, for, while, do... * @TBannedKeyword - else, elseif * @ContrainedLiteral - eg. @myVar. First char has to be either alpha or underscore (eg. @8 is invalid) @@ -430,8 +468,7 @@ bool ParseTransition(Sides currentSide) if (c == '{') { - DiscardCurrentLexeme(); - ParseCodeBlock(false); + ParseCodeBlock(false, false); } else if (c == '(') { @@ -443,6 +480,13 @@ bool ParseTransition(Sides currentSide) DiscardCurrentLexeme(); ParseRestOfLineAsClient(); } + else if (c == '!' && currentSide == Sides.Client && NextNonWhiteSpaceCharMatches('{')) + { + DiscardCurrentLexeme(); + SetParsingControlChars(false); + ParseCodeBlock(false, false); + SetParsingControlChars(true); + } else if (IsAlpha(c)) { ParseImplicitExpression(currentSide); @@ -633,10 +677,20 @@ bool ParseKeyword() * - looking for a } in case we've entered from a code block * - looking for a ) when entered from an explicit expression */ - void ParseCodeBlock(bool keepClosingBrk) + void ParseCodeBlock(bool keepOpeningBrk, bool keepClosingBrk) { bool matchedOpenBrk = MatchNextNonWhiteSpaceChar('{'); string l = GetCurrentLexeme(); + if (l == "{") + { + matchedOpenBrk = true; + } + + if (matchedOpenBrk && !keepOpeningBrk) + { + RemoveLastCharFromCurrentLexeme(); + } + AddToken(TokenTypes.BlockExpr); while (!IsAtEnd()) @@ -781,7 +835,7 @@ void HandleStringSequence(char chr) { continue; } - + if (Peek() == '<') { if (LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) diff --git a/src/WattleScript.Templating/Parser/ParserKeywords.cs b/src/WattleScript.Templating/Parser/ParserKeywords.cs index ee10e6cf..2285c055 100644 --- a/src/WattleScript.Templating/Parser/ParserKeywords.cs +++ b/src/WattleScript.Templating/Parser/ParserKeywords.cs @@ -139,7 +139,7 @@ bool ParseGenericBrkKeywordWithBlock(string keyword) return false; } - ParseCodeBlock(true); + ParseCodeBlock(true, true); return true; } @@ -154,7 +154,7 @@ bool ParseGenericKeywordWithBlock(string keyword) Throw($"Expected {{ after {keyword}"); } - ParseCodeBlock(true); + ParseCodeBlock(true, true); return true; } @@ -222,7 +222,7 @@ bool ParseKeywordElse() string str = GetCurrentLexeme(); - ParseCodeBlock(true); + ParseCodeBlock(true, true); return false; } diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index 0a8df277..cad8272b 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -182,4 +182,14 @@ bool Throw(string message) { throw new Exception(message); } + + void SetParsingControlChars(bool enabled) + { + parsingTransitionCharactersEnabled = enabled; + } + + bool ParsingControlChars() + { + return parsingTransitionCharactersEnabled; + } } \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/7-explicit-escape.html b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/7-explicit-escape.html new file mode 100644 index 00000000..d0be2593 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/7-explicit-escape.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/7-explicit-escape.wthtml b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/7-explicit-escape.wthtml new file mode 100644 index 00000000..109b394a --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ExplicitExpr/7-explicit-escape.wthtml @@ -0,0 +1,7 @@ +@! { + +} \ No newline at end of file From 6e7ae68736bc4646510203463ab57187f625844f Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 20 Apr 2022 17:57:10 +0200 Subject: [PATCH 25/74] Added support for @switch implicit transition --- src/WattleScript.Templating/Parser/Parser.cs | 21 ++++++++++++++----- .../Parser/ParserKeywords.cs | 7 +++++++ .../AllowedKeyword/Switch/1-switch.html | 2 ++ .../AllowedKeyword/Switch/1-switch.wthtml | 11 ++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Switch/1-switch.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Switch/1-switch.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index edd18db7..44314e22 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -27,7 +27,7 @@ enum StepModes private string? source; private List Tokens { get; set; } = new List(); private List Messages { get; set; } = new List(); - private List AllowedTransitionKeywords = new List() {"if", "for", "do", "while", "require", "function"}; + private List AllowedTransitionKeywords = new List() {"if", "for", "do", "while", "require", "function", "switch"}; private List BannedTransitionKeywords = new List() {"else", "elseif"}; private Dictionary?> KeywordsMap; private StringBuilder Buffer = new StringBuilder(); @@ -56,7 +56,8 @@ public Parser(Script? script) { "for", ParseKeywordFor }, { "while", ParseKeywordWhile }, { "do", ParseKeywordDo }, - { "function", ParseKeywordFunction } + { "function", ParseKeywordFunction }, + { "switch", ParseKeywordSwitch }, }; } @@ -208,9 +209,15 @@ bool MatchNextNonWhiteSpaceNonNewlineChar(char ch) return false; } - bool NextNonWhiteSpaceCharMatches(char ch) + bool NextNonWhiteSpaceCharMatches(char ch, int skip = 0) { StorePos(); + + for (int i = 0; i < skip; i++) + { + Step(); + } + while (!IsAtEnd()) { if (Peek() == ' ') @@ -464,24 +471,28 @@ bool ParseTransition(Sides currentSide) Step(); // @ DiscardCurrentLexeme(); - Step(); + c = Peek(); if (c == '{') { + Step(); ParseCodeBlock(false, false); } else if (c == '(') { + Step(); DiscardCurrentLexeme(); ParseExplicitExpression(); } else if (c == ':' && currentSide == Sides.Client) { + Step(); DiscardCurrentLexeme(); ParseRestOfLineAsClient(); } - else if (c == '!' && currentSide == Sides.Client && NextNonWhiteSpaceCharMatches('{')) + else if (c == '!' && currentSide == Sides.Client && NextNonWhiteSpaceCharMatches('{', 1)) { + Step(); DiscardCurrentLexeme(); SetParsingControlChars(false); ParseCodeBlock(false, false); diff --git a/src/WattleScript.Templating/Parser/ParserKeywords.cs b/src/WattleScript.Templating/Parser/ParserKeywords.cs index 2285c055..1b786847 100644 --- a/src/WattleScript.Templating/Parser/ParserKeywords.cs +++ b/src/WattleScript.Templating/Parser/ParserKeywords.cs @@ -34,6 +34,13 @@ bool ParseKeywordWhile() return ParseGenericBrkKeywordWithBlock("while"); } + // switch (expr) {} + // parser has to be positioned after "switch", either at opening ( or at a whitespace preceding it + bool ParseKeywordSwitch() + { + return ParseGenericBrkKeywordWithBlock("switch"); + } + // do {} while () // parser has to be positioned after "do", either at opening { or at a whitespace preceding it bool ParseKeywordDo() diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Switch/1-switch.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Switch/1-switch.html new file mode 100644 index 00000000..e0c2609d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Switch/1-switch.html @@ -0,0 +1,2 @@ + +'8 \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Switch/1-switch.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Switch/1-switch.wthtml new file mode 100644 index 00000000..595ea56f --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Switch/1-switch.wthtml @@ -0,0 +1,11 @@ +@{ + x = 10 + x2 = 0 +} +@switch (x) { + case 1: + x2 = 5 + case 10: + x2 = 8 +} +'@x2 \ No newline at end of file From deeb5db36c626bd73671cfa3a2102d91ddc06266 Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 20 Apr 2022 22:05:22 +0200 Subject: [PATCH 26/74] Render transpiled code in error messages --- src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index 61129788..a1bb1f12 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -98,7 +98,7 @@ void Print(Script script, CallbackArguments args) { if (e is AssertionException ae) { - Assert.Fail($"Test {path} did not pass.\nMessage: {ae.Message}\n{ae.StackTrace}"); + Assert.Fail($"Test {path} did not pass.\nMessage: {ae.Message}\n{ae.StackTrace}\nParsed template:\n{transpiled}"); return; } From ed16e20d7c04f6719b744dcdd897dcb0bcc7b37d Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 20 Apr 2022 22:12:02 +0200 Subject: [PATCH 27/74] Test cd on win --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index e1ce507e..d7ed5933 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: windows-latest steps: - uses: actions/checkout@v3 From 528ec6092b91ac3a79695399fadf8e204e2f9fcf Mon Sep 17 00:00:00 2001 From: lofcz Date: Thu, 21 Apr 2022 05:07:17 +0200 Subject: [PATCH 28/74] Fix newline handling --- .github/workflows/dotnet.yml | 2 +- src/WattleScript.Templating/Parser/Parser.cs | 37 +++++++------------ .../Parser/ParserUtils.cs | 27 +++++++++++++- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index d7ed5933..584a5cc2 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: windows-latest + runs-on: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v3 diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 44314e22..39acf399 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -286,22 +286,20 @@ bool NextLiteralSkipEmptyCharsMatches(string literal, Sides currentSide) if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') { Step(); + continue; } - else - { - string str = GetCurrentLexeme(); - DiscardCurrentLexeme(); - started = true; - } - } - else - { - if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') - { - break; - } + + string str = GetCurrentLexeme(); + DiscardCurrentLexeme(); + started = true; + continue; } + if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') + { + break; + } + Step(); } @@ -390,17 +388,7 @@ void ParseRestOfLineAsClient() chr = Step(); } - - // if we ended on \r check for \n and consume if matches - if (chr == '\r') - { - chr = Peek(); - if (chr == '\n') - { - Step(); - } - } - + AddToken(TokenTypes.Text); } @@ -851,6 +839,7 @@ void HandleStringSequence(char chr) { if (LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) { + StepEol(); AddToken(TokenTypes.BlockExpr); ParseHtmlTag(); } diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index cad8272b..77824788 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -1,4 +1,6 @@ -namespace WattleScript.Templating; +using System.Runtime.InteropServices; + +namespace WattleScript.Templating; internal partial class Parser { @@ -47,6 +49,15 @@ char Step(int i = 1) pos += i; c = cc; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (cc == '\r' && Peek() == '\n') + { + return Step(); + } + } + return cc; } @@ -192,4 +203,18 @@ bool ParsingControlChars() { return parsingTransitionCharactersEnabled; } + + bool StepEol() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (Peek() == '\n' && LastStoredCharMatches('\r')) + { + Step(); + return false; + } + } + + return false; + } } \ No newline at end of file From 0825469adf2d861df7733d9842f9f4726869d6a8 Mon Sep 17 00:00:00 2001 From: lofcz Date: Thu, 21 Apr 2022 05:11:43 +0200 Subject: [PATCH 29/74] Update dotnet.yml --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 584a5cc2..e1ce507e 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: [ubuntu-latest, windows-latest] + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 72167ac870eca07e220d34569d70a41cf361b808 Mon Sep 17 00:00:00 2001 From: lofcz Date: Thu, 21 Apr 2022 06:16:46 +0200 Subject: [PATCH 30/74] Shape public api --- .../TemplatingEngine.cs | 60 +++++++++++++++++-- .../TemplatingEngineOptions.cs | 12 ++++ .../Templating/TemplatingTestsRunner.cs | 35 +++-------- 3 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 src/WattleScript.Templating/TemplatingEngineOptions.cs diff --git a/src/WattleScript.Templating/TemplatingEngine.cs b/src/WattleScript.Templating/TemplatingEngine.cs index d2c8de99..337b119b 100644 --- a/src/WattleScript.Templating/TemplatingEngine.cs +++ b/src/WattleScript.Templating/TemplatingEngine.cs @@ -6,6 +6,19 @@ namespace WattleScript.Templating; public class TemplatingEngine { private readonly StringBuilder pooledSb = new StringBuilder(); + private readonly TemplatingEngineOptions options; + private readonly Script script; + private StringBuilder stdOut = new StringBuilder(); + + public TemplatingEngine(Script script, TemplatingEngineOptions? options = null) + { + options ??= TemplatingEngineOptions.Default; + this.options = options; + this.script = script ?? throw new ArgumentNullException(nameof(script)); + + script.Globals["stdout_line"] = PrintLine; + script.Globals["stdout"] = Print; + } string EncodeJsString(string s) { @@ -97,13 +110,18 @@ List Optimise(List? tokens) return tokens; } - public string Debug(Script script, string code, bool optimise) + public string Debug(string code) { + if (string.IsNullOrWhiteSpace(code)) + { + return ""; + } + Parser parser = new Parser(script); List tokens = parser.Parse(code); pooledSb.Clear(); - if (optimise) + if (options.Optimise) { tokens = Optimise(tokens); } @@ -117,15 +135,20 @@ public string Debug(Script script, string code, bool optimise) return finalText; } - public string Render(Script script, string code, bool optimise) + public string Transpile(string code) { + if (string.IsNullOrWhiteSpace(code)) + { + return ""; + } + Parser parser = new Parser(script); List tokens = parser.Parse(code); StringBuilder sb = new StringBuilder(); bool firstClientPending = true; - if (optimise) + if (options.Optimise) { tokens = Optimise(tokens); } @@ -162,4 +185,33 @@ public string Render(Script script, string code, bool optimise) string finalText = sb.ToString(); return finalText; } + + public async Task Render(string code, Table? globalContext = null, string? friendlyCodeName = null) + { + stdOut.Clear(); + + string transpiledTemplate = Transpile(code); + await script.DoStringAsync(transpiledTemplate, globalContext, friendlyCodeName); + string htmlText = stdOut.ToString(); + + + + return new RenderResult() {Output = htmlText, Transpiled = transpiledTemplate}; + } + + private void PrintLine(Script script, CallbackArguments args) + { + stdOut.AppendLine(args[0].CastToString()); + } + + private void Print(Script script, CallbackArguments args) + { + stdOut.Append(args[0].CastToString()); + } + + public class RenderResult + { + public string Output { get; init; } + public string Transpiled { get; init; } + } } \ No newline at end of file diff --git a/src/WattleScript.Templating/TemplatingEngineOptions.cs b/src/WattleScript.Templating/TemplatingEngineOptions.cs new file mode 100644 index 00000000..91c71e59 --- /dev/null +++ b/src/WattleScript.Templating/TemplatingEngineOptions.cs @@ -0,0 +1,12 @@ +namespace WattleScript.Templating; + +public class TemplatingEngineOptions +{ + public static readonly TemplatingEngineOptions Default = new TemplatingEngineOptions() {Optimise = true}; + + /// + /// True = a slightly longer parsing, a slightly slower execution + /// False = a slightly faster parsing, a slightly faster execution + /// + public bool Optimise { get; set; } +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index a1bb1f12..78813d9e 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -26,18 +26,6 @@ public async Task RunThrowErros(string path) public async Task RunCore(string path, bool reportErrors = false) { - StringBuilder stdOut = new StringBuilder(); - - void PrintLine(Script script, CallbackArguments args) - { - stdOut.AppendLine(args[0].CastToString()); - } - - void Print(Script script, CallbackArguments args) - { - stdOut.Append(args[0].CastToString()); - } - string outputPath = path.Replace(".wthtml", ".html"); if (!File.Exists(outputPath)) @@ -50,20 +38,15 @@ void Print(Script script, CallbackArguments args) string output = await File.ReadAllTextAsync(outputPath); Script script = new Script(CoreModules.Preset_HardSandbox); - script.Options.DebugPrint = s => stdOut.AppendLine(s); script.Options.IndexTablesFrom = 0; script.Options.AnnotationPolicy = new CustomPolicy(AnnotationValueParsingPolicy.ForceTable); script.Options.Syntax = ScriptSyntax.WattleScript; script.Options.Directives.Add("using"); - - TemplatingEngine tmp = new TemplatingEngine(); - string transpiled = tmp.Render(script, code, true); - - string debugStr = tmp.Debug(script, code, true); - - script.Globals["stdout_line"] = PrintLine; - script.Globals["stdout"] = Print; + TemplatingEngine tmp = new TemplatingEngine(script); + string debugStr = tmp.Debug(code); + TemplatingEngine.RenderResult rr = null; + if (path.Contains("flaky")) { Assert.Inconclusive($"Test {path} marked as flaky"); @@ -78,16 +61,14 @@ void Print(Script script, CallbackArguments args) if (reportErrors) { script.Options.ParserErrorMode = ScriptOptions.ParserErrorModes.Report; - await script.DoStringAsync(transpiled); + rr = await tmp.Render(code); return; } try { - DynValue dv = script.LoadString(transpiled); - await script.CallAsync(dv); - - Assert.AreEqual(output, stdOut.ToString(), $"Test {path} did not pass."); + rr = await tmp.Render(code); + Assert.AreEqual(output, rr.Output, $"Test {path} did not pass."); if (path.Contains("invalid")) { @@ -98,7 +79,7 @@ void Print(Script script, CallbackArguments args) { if (e is AssertionException ae) { - Assert.Fail($"Test {path} did not pass.\nMessage: {ae.Message}\n{ae.StackTrace}\nParsed template:\n{transpiled}"); + Assert.Fail($"Test {path} did not pass.\nMessage: {ae.Message}\n{ae.StackTrace}\nParsed template:\n{rr?.Transpiled ?? ""}"); return; } From 72a001645ce0b02b8d9360e88aa0d953a23caa0a Mon Sep 17 00:00:00 2001 From: lofcz Date: Sun, 24 Apr 2022 23:00:43 +0200 Subject: [PATCH 31/74] Refactor --- src/WattleScript.Templating/Parser/Node.cs | 91 ++ src/WattleScript.Templating/Parser/Parser.cs | 478 ++++-- .../Parser/ParserUtils.cs | 70 +- .../Parser/TagHelper.cs | 12 + .../TemplatingEngine.cs | 10 +- .../Templating/Tests/Html/1-w3s.html | 1324 ++++++++++++++++ .../Templating/Tests/Html/1-w3s.wthtml | 1324 ++++++++++++++++ .../Tests/Html/2-w3s-block-flaky.html | 1323 ++++++++++++++++ .../Tests/Html/2-w3s-block-flaky.wthtml | 1325 +++++++++++++++++ .../Templating/Tests/Html/3-checker.html | 131 ++ .../Templating/Tests/Html/3-checker.wthtml | 131 ++ .../Tests/Html/4-checker-block.html | 130 ++ .../Tests/Html/4-checker-block.wthtml | 132 ++ .../Templating/Tests/Html/5-min.html | 7 + .../Templating/Tests/Html/5-min.wthtml | 9 + .../Templating/Tests/Html/6-min-2.html | 16 + .../Templating/Tests/Html/6-min-2.wthtml | 19 + .../Templating/Tests/Html/7-root.html | 1 + .../Templating/Tests/Html/7-root.wthtml | 3 + .../Templating/Tests/Nodes/1-simple.html | 9 + .../Templating/Tests/Nodes/1-simple.wthtml | 9 + 21 files changed, 6457 insertions(+), 97 deletions(-) create mode 100644 src/WattleScript.Templating/Parser/Node.cs create mode 100644 src/WattleScript.Templating/Parser/TagHelper.cs create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/1-w3s.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/1-w3s.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/3-checker.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/3-checker.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/4-checker-block.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/4-checker-block.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/5-min.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/5-min.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/6-min-2.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/6-min-2.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/7-root.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/7-root.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Nodes/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Nodes/1-simple.wthtml diff --git a/src/WattleScript.Templating/Parser/Node.cs b/src/WattleScript.Templating/Parser/Node.cs new file mode 100644 index 00000000..8266278f --- /dev/null +++ b/src/WattleScript.Templating/Parser/Node.cs @@ -0,0 +1,91 @@ +namespace WattleScript.Templating; + +internal class Document +{ + public List Nodes { get; set; } + public INodeWithChildren CurrentNode { get; set; } + DocumentNode DocNode = new DocumentNode(); + + public Document() + { + Nodes = new List(); + Nodes.Add(DocNode); + CurrentNode = DocNode; + } + + public void AddChild(NodeBase node) + { + DocNode.AddChild(node); + } +} + +internal interface INodeWithChildren +{ + public List Children { get; set; } + public void AddChild(NodeBase child); +} + +internal class NodeBase +{ + +} + +internal class ServerNode : NodeBase +{ + +} + +internal class TextNode : NodeBase +{ + public string Text { get; set; } + + public TextNode(string text) + { + Text = text; + } +} + +internal class DocumentNode : NodeBase, INodeWithChildren +{ + public List Children { get; set; } = new List(); + public void AddChild(NodeBase child) + { + Children.Add(child); + } +} + +internal class HtmlElement : NodeBase, INodeWithChildren +{ + internal enum ClosingType + { + SelfClosing, + ImplicitSelfClosing, + EndTag + } + + public HtmlElement Parent { get; set; } + public List Attributes { get; set; } + public string Name { get; set; } + public ClosingType Closing { get; set; } + public bool ForceNativeTag { get; set; } + public List Children { get; set; } = new List(); + + public void AddChild(NodeBase child) + { + Children.Add(child); + } +} + +internal class HtmlAttribute +{ + internal enum HtmlAttributeQuoteType + { + None, + Single, + Double + } + + public string Name { get; set; } + public string Value { get; set; } + public HtmlAttributeQuoteType QuoteType { get; set; } +} \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 39acf399..51a1d43e 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -36,19 +36,19 @@ enum StepModes private int lastCommitedPos; private int lastCommitedLine; private char c; - private string currentLexeme = ""; + private StringBuilder currentLexeme = new StringBuilder(); private int line = 1; private Script? script; private StepModes stepMode = StepModes.CurrentLexeme; private bool parsingTransitionCharactersEnabled = true; + private Document document; + private List? tagHelpers; - /// - /// - /// - /// can be null but templating engine won't resolve custom directives - public Parser(Script? script) + public Parser(Script? script, List? tagHelpers) { this.script = script; + this.tagHelpers = tagHelpers; + document = new Document(); KeywordsMap = new Dictionary?> { @@ -61,10 +61,11 @@ public Parser(Script? script) }; } - public List Parse(string templateSource, bool includeNewlines = false) + public List Parse(string templateSource) { source = templateSource; ParseClient(); + //Lookahead(); if (IsAtEnd()) { @@ -112,6 +113,7 @@ void HandleStringSequence(char chr) while (!IsAtEnd()) { Step(); + if (handleStrings && !inMultilineComment) { if (c == '\'') @@ -251,7 +253,7 @@ bool ParseWhitespaceAndNewlines(Sides currentSide) continue; } - if (Peek() == ' ' || Peek() == '\n' || Peek() == '\r') + if (IsWhitespaceOrNewline(Peek())) { Step(); continue; @@ -407,12 +409,29 @@ void ParseClient() continue; } - Step(); + shouldContinue = LookaheadForHtmlComment(Sides.Client); + if (shouldContinue) + { + continue; + } + + if (Peek() == '<' && IsHtmlTagOpeningChar(Peek(2))) + { + ParseHtmlTag(null); + } + + char c = Step(); + string str = GetCurrentLexeme(); } - + AddToken(TokenTypes.Text); } + bool IsHtmlTagOpeningChar(char chr) + { + return IsAlpha(chr) || chr == '!'; + } + // @* *@ // parser must be positioned directly after opening @* void ParseServerComment() @@ -437,7 +456,9 @@ void RemoveLastCharFromCurrentLexeme() { if (GetCurrentLexeme().Length > 0) { - currentLexeme = currentLexeme[..^1]; + string str = currentLexeme.ToString()[..^1]; + currentLexeme.Clear(); + currentLexeme.Append(str); } } @@ -496,7 +517,7 @@ bool ParseTransition(Sides currentSide) if (currentSide == Sides.Server) { // we don't know enough to decide so we treat it as an annotation in server mode for now - currentLexeme = $"@{GetCurrentLexeme()}"; + currentLexeme.Insert(0, "@"); return true; } @@ -595,7 +616,8 @@ void ParseExplicitExpression() str = str[..^1]; } - currentLexeme = str; + currentLexeme.Clear(); + currentLexeme.Append(str); AddToken(TokenTypes.ExplicitExpr); } @@ -708,13 +730,13 @@ void ParseCodeBlock(bool keepOpeningBrk, bool keepClosingBrk) } l = GetCurrentLexeme(); - if (!keepClosingBrk) + if (!keepClosingBrk && currentLexeme.Length > 0) { - currentLexeme = currentLexeme.Substring(0, currentLexeme.Length - 1); + RemoveLastCharFromCurrentLexeme(); } AddToken(TokenTypes.BlockExpr); } - + bool LastStoredCharMatches(params char[] chars) { if (GetCurrentLexeme().Length < 1) @@ -722,7 +744,7 @@ bool LastStoredCharMatches(params char[] chars) return false; } - char chr = currentLexeme[..^1][0]; + char chr = currentLexeme.ToString()[..^1][0]; return chars.Contains(chr); } @@ -733,7 +755,7 @@ bool LastStoredCharMatches(int n = 1, params char[] chars) return false; } - char chr = currentLexeme.Substring(currentLexeme.Length - 1 - n, 1)[0]; + char chr = currentLexeme.ToString().Substring(currentLexeme.Length - 1 - n, 1)[0]; return chars.Contains(chr); } @@ -771,6 +793,7 @@ bool InSpecialSequence() void HandleStringSequence(char chr) { + Step(); string str = GetCurrentLexeme(); if (LastStoredCharMatches(2, '\\')) // check that string symbol is not escaped { @@ -790,20 +813,21 @@ void HandleStringSequence(char chr) } } } - + + bool allowHtml = false; while (!IsAtEnd()) { if (!inMultilineComment) { - if (c == '\'') + if (Peek() == '\'') { HandleStringSequence('\''); } - else if (c == '"') + else if (Peek() == '"') { HandleStringSequence('"'); } - else if (c == '`') + else if (Peek() == '`') { HandleStringSequence('`'); } @@ -811,14 +835,14 @@ void HandleStringSequence(char chr) if (!inString) { - if (c == '/' && Peek() == '*') + if (Peek() == '/' && Peek() == '*') { if (!inMultilineComment) { inMultilineComment = true; } } - else if (c == '*' && Peek() == '/') + else if (Peek() == '*' && Peek() == '/') { if (inMultilineComment) { @@ -835,13 +859,22 @@ void HandleStringSequence(char chr) continue; } - if (Peek() == '<') + cnt = LookaheadForHtmlComment(Sides.Server); + if (cnt) + { + continue; + } + + if (Peek() == '<' && IsHtmlTagOpeningChar(Peek(2))) { - if (LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) + if (allowHtml || LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) { StepEol(); AddToken(TokenTypes.BlockExpr); - ParseHtmlTag(); + ParseHtmlTag(null); + + allowHtml = true; + continue; } } else if (Peek() == '{') @@ -860,98 +893,329 @@ void HandleStringSequence(char chr) string str2 = GetCurrentLexeme(); char chr = Step(); + allowHtml = false; } string str = GetCurrentLexeme(); } - string ParseHtmlTagNameInBuffer() + string ParseHtmlTagName(bool inBuffer) { + StringBuilder sb = new StringBuilder(); + + if (inBuffer) + { + ClearBuffer(); + SetStepMode(StepModes.Buffer); + } + + // Tag name can be provided via a server transition so we have to check for that + LookaheadForTransitionClient(Sides.Client); + // First char in a proper HTML tag (after opening <) can be [_, !, /, Alpha, ?] if (!(Peek() == '_' || Peek() == '!' || Peek() == '?' || Peek() == '/' || IsAlpha(Peek()))) { + char chr = Peek(); + string lexeme = GetCurrentLexeme(); + Throw("First char after < in an opening HTML tag must be _, ! or alpha"); } - + else + { + char chr = Step(); + sb.Append(chr); + } + // The next few chars represent element's name - ClearBuffer(); - SetStepMode(StepModes.Buffer); - - while (!IsAtEnd() && (IsAlphaNumeric(Peek()) || Peek() == '/' || Peek() == '!' || Peek() == '?')) + while (!IsAtEnd() && (IsAlphaNumeric(Peek()))) { - Step(); + bool shouldContinue = LookaheadForTransitionServerSide(); + if (shouldContinue) + { + continue; + } + + char chr = Step(); + sb.Append(chr); } if (IsAtEnd()) { - Throw("Unclosed HTML tag at the end of file"); + //Throw("Unclosed HTML tag at the end of file"); } - string tagName = GetBuffer(); + if (inBuffer) + { + string tagName = GetBuffer(); - AddBufferToCurrentLexeme(); - ClearBuffer(); - SetStepMode(StepModes.CurrentLexeme); + AddBufferToCurrentLexeme(); + ClearBuffer(); + SetStepMode(StepModes.CurrentLexeme); - return tagName; + return tagName; + } + + return sb.ToString(); } - bool ParseHtmlTag() + bool LookaheadForClosingTag() { + return Peek() == '>' || (Peek() == '/' && Peek(2) == '>'); + } + + bool ParseHtmlTag(string? parentTagName) + { + ParseWhitespaceAndNewlines(Sides.Client); + + if (Peek() != '<') + { + return false; + } + char chr = Step(); // has to be < - string tagName = ParseHtmlTagNameInBuffer(); + string tagName = ParseHtmlTagName(false); - ParseUntilBalancedChar('<', '>', true, true, true); - string tagText = GetCurrentLexeme(); + ParseWhitespaceAndNewlines(Sides.Client); - bool isSelfClosing = IsSelfClosingHtmlTag(tagName); - bool isSelfClosed = CurrentLexemeIsSelfClosedHtmlTag(); - bool parseContent = !isSelfClosed && !isSelfClosing; + while (!IsAtEnd()) + { + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); + if (shouldContinue) + { + continue; + } + + bool shouldEnd = LookaheadForAttributeOrClose(tagName, false); + if (shouldEnd) + { + break; + } + } - if (parseContent) + return true; + } + + bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag) + { + if (LookaheadForClosingTag()) { - if (tagText == "") // "" has a special meaning only when exactly matched. Any modification like "" will be rendered as a normal tag + CloseTag(tagName, startsFromClosingTag); + return true; + } + + return ParseAttribute(tagName, startsFromClosingTag); + } + + string ParseAttributeName() + { + StringBuilder sb = new StringBuilder(); + while (!IsAtEnd()) + { + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); + if (shouldContinue) + { + continue; + } + + if (Peek() == '=' || LookaheadForClosingTag()) { - DiscardCurrentLexeme(); + return sb.ToString(); + } + + sb.Append(Step()); + } + + return sb.ToString(); + } + + string ParseAttributeValue() + { + HtmlAttrEnclosingModes closeMode = HtmlAttrEnclosingModes.None; + StringBuilder sb = new StringBuilder(); + + if (Peek() == '\'') + { + closeMode = HtmlAttrEnclosingModes.SingleQuote; + Step(); + } + else if (Peek() == '\"') + { + closeMode = HtmlAttrEnclosingModes.DoubleQuote; + Step(); + } + + while (!IsAtEnd()) + { + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); + if (shouldContinue) + { + continue; + } + + if (IsWhitespaceOrNewline(Peek()) && closeMode == HtmlAttrEnclosingModes.None) + { + return sb.ToString(); + } + + if (Peek() == '\'' && closeMode == HtmlAttrEnclosingModes.SingleQuote) + { + Step(); + return sb.ToString(); } - ParseHtmlOrPlaintextUntilClosingTag(tagName); - ParseHtmlClosingTag(tagName); + if (Peek() == '"' && closeMode == HtmlAttrEnclosingModes.DoubleQuote) + { + Step(); + return sb.ToString(); + } + + sb.Append(Step()); + } + + return sb.ToString(); + } + + + bool ParseAttribute(string tagName, bool startsFromClosingTag) + { + string name = ParseAttributeName(); + + if (Peek() == '=') + { + Step(); + string val = ParseAttributeValue(); + } + + if (LookaheadForClosingTag()) + { + CloseTag(tagName, startsFromClosingTag); + return true; } + return false; + } + + bool CloseTag(string tagName, bool startsFromClosingTag) + { + if (tagName == "html") + { + string g = ""; + } + + if (Peek() == '/' && Peek(2) == '>') + { + Step(); + Step(); + } + else if (Peek() == '>') + { + Step(); + } + + bool parseContent = false; + if (!startsFromClosingTag) + { + string tagText = GetCurrentLexeme(); + bool isSelfClosing = IsSelfClosingHtmlTag(tagName); + bool isSelfClosed = CurrentLexemeIsSelfClosedHtmlTag(); + parseContent = !isSelfClosed && !isSelfClosing; + + if (parseContent) + { + if (tagText == "") // "" has a special meaning only when exactly matched. Any modification like "" will be rendered as a normal tag + { + DiscardCurrentLexeme(); + ParseHtmlOrPlaintextUntilClosingTag(tagName); + } + else if (tagName is "script" or "style") // raw text elements + { + ParsePlaintextUntilClosingTag(tagName); + ParseHtmlClosingTag(tagName); + } + else + { + ParseHtmlOrPlaintextUntilClosingTag(tagName); + } + } + } + string s = GetCurrentLexeme(); - AddToken(TokenTypes.Text); - return true; + if (s.Contains("")) + { + string h = ""; + } + + if (startsFromClosingTag && tagName == "/text" && s.Trim() == "") + { + DiscardCurrentLexeme(); + return parseContent; + } + + AddToken(TokenTypes.Text); + return parseContent; } - // parser has to be positioned at opening < of the closing tag - void ParseHtmlClosingTag(string openingTagName) + // parser has to be positioned at opening < of the closing tag + string ParseHtmlClosingTag(string openingTagName) { - char chr = Step(); // has to be < - - string str = GetCurrentLexeme(); - string closingTagName = ParseHtmlTagNameInBuffer(); + ParseWhitespaceAndNewlines(Sides.Client); + if (Peek() != '<') + { + return ""; + } - ParseUntilBalancedChar('<', '>', true, true, true); - str = GetCurrentLexeme(); + char chr = Step(); // has to be < + string closingTagName = ParseHtmlTagName(false); + //ParseUntilBalancedChar('<', '>', true, true, true); + + while (!IsAtEnd()) + { + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); + if (shouldContinue) + { + continue; + } + + bool shouldEnd = LookaheadForAttributeOrClose(closingTagName, true); + if (shouldEnd) + { + break; + } + } + if ($"/{openingTagName}" != closingTagName) { // [todo] end tag does not match opening tag // we will be graceful but a warning can be emmited } - - if (openingTagName == "text" && closingTagName == "/text" && str.Trim() == "") + + return closingTagName; + } + + void ParsePlaintextUntilClosingTag(string openingTagName) + { + while (!IsAtEnd()) { - DiscardCurrentLexeme(); + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); + if (shouldContinue) + { + continue; + } + + if (Peek() == '<' && Peek(2) == '/' && PeekRange(3, openingTagName.Length) == openingTagName && IsWhitespaceOrNewline(Peek(4 + openingTagName.Length))) + { + break; + } + + Step(); } } - + void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) { - int missingBrks = 1; - + AddToken(TokenTypes.Text); string s = GetCurrentLexeme(); while (!IsAtEnd()) { @@ -960,28 +1224,29 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) { continue; } + + shouldContinue = LookaheadForHtmlComment(Sides.Client); + if (shouldContinue) + { + continue; + } if (Peek() == '<') { - if (IsAlpha(Peek(2))) // we enter new tag - { - ParseHtmlTag(); - } - else if (Peek(2) == '/' && IsAlpha(Peek(3))) + if (Peek(2) == '/' && IsHtmlTagOpeningChar(Peek(3))) { - missingBrks--; - } - - if (missingBrks <= 0) - { - break; + string closingName = ParseHtmlClosingTag(openingTagName); + if (string.Equals($"/{openingTagName}", closingName, StringComparison.InvariantCultureIgnoreCase)) + { + AddToken(TokenTypes.Text); + return; + } } - } - else if (Peek() == '>') - { - if (IsAlpha(Peek(2))) + + if (IsHtmlTagOpeningChar(Peek(2))) { - missingBrks++; + ParseHtmlTag(openingTagName); + continue; } } @@ -990,4 +1255,49 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) s = GetCurrentLexeme(); } + + bool LookaheadForHtmlComment(Sides currentSide) + { + if (Peek() == '<' && Peek(2) == '!') + { + if (Peek(3) == '-' && Peek(4) == '-') + { + ParseHtmlComment(HtmlCommentModes.DoubleHyphen, currentSide); + return true; + } + + if (Peek(3) == '[') + { + ParseHtmlComment(HtmlCommentModes.Cdata, currentSide); + return true; + } + } + + return false; + } + + void ParseHtmlComment(HtmlCommentModes openCommentMode, Sides currentSide) + { + if (currentSide == Sides.Server) + { + AddToken(TokenTypes.BlockExpr); + } + + if (openCommentMode == HtmlCommentModes.DoubleHyphen) + { + StepN(4); // + { + StepN(3); + return; + } + + Step(); + } } \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index 77824788..789687e3 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -4,6 +4,19 @@ namespace WattleScript.Templating; internal partial class Parser { + internal enum HtmlAttrEnclosingModes + { + None, + SingleQuote, + DoubleQuote + } + + internal enum HtmlCommentModes + { + DoubleHyphen, // + +
+ + +
+ + + + +
+
+
+ + + HTML + CSS + JAVASCRIPT + SQL + PYTHON + PHP + BOOTSTRAP + HOW TO + W3.CSS + JAVA + JQUERY + C + C++ + C# + R + React + + + + + + + + +
+ +
+ +
+ +
+ + + + + + + + +
+
+ + + + + + +
+
+
+ +

HTML Tutorial

+ HTML HOME + HTML Introduction + HTML Editors + HTML Basic + HTML Elements + HTML Attributes + HTML Headings + HTML Paragraphs + HTML Styles + HTML Formatting + HTML Quotations + HTML Comments + HTML Colors +
+ Colors + RGB + HEX + HSL +
+ HTML CSS + HTML Links + + HTML Images + + HTML Favicon + HTML Tables + + + + + HTML Lists + + HTML Block & Inline + HTML Classes + HTML Id + HTML Iframes + HTML JavaScript + HTML File Paths + HTML Head + HTML Layout + HTML Responsive + HTML Computercode + HTML Semantics + HTML Style Guide + HTML Entities + HTML Symbols + HTML Emojis + HTML Charset + HTML URL Encode + HTML vs. XHTML +
+

HTML Forms

+ HTML Forms + HTML Form Attributes + HTML Form Elements + HTML Input Types + HTML Input Attributes + HTML Input Form Attributes +
+

HTML Graphics

+ HTML Canvas + HTML SVG +
+

HTML Media

+ HTML Media + HTML Video + HTML Audio + HTML Plug-ins + HTML YouTube +
+

HTML APIs

+ HTML Geolocation + HTML Drag/Drop + HTML Web Storage + HTML Web Workers + HTML SSE +
+

HTML Examples

+ HTML Examples + HTML Quiz + HTML Exercises + HTML Certificate + HTML Summary + HTML Accessibility +
+

HTML References

+ HTML Tag List + HTML Attributes + HTML Global Attributes + HTML Browser Support + HTML Events + HTML Colors + HTML Canvas + HTML Audio/Video + HTML Doctypes + HTML Character Sets + HTML URL Encode + HTML Lang Codes + HTTP Messages + HTTP Methods + PX to EM Converter + Keyboard Shortcuts +

+
+
+
+
+
+
+
+ + + +
+ + +
+

HTML Basic Examples

+ +
+

In this chapter we will show some basic HTML examples.

+

Don't worry if we use tags you have not learned about yet.

+
+ +

HTML Documents

+

All HTML documents must start with a document type declaration: <!DOCTYPE html>.

+

The HTML document itself begins with <html> and ends with </html>.

+

The visible part of the HTML document is between <body> and </body>.

+
+

Example

+
+ <!DOCTYPE html>
<html>
<body>

<h1>My First Heading</h1>
<p>My first paragraph.</p>

</body>
</html>
+ Try it Yourself » +
+
+ +

The <!DOCTYPE> Declaration

+

The <!DOCTYPE> declaration represents the document type, and helps browsers to display web pages correctly.

+

It must only appear once, at the top of the page (before any HTML tags).

+

The <!DOCTYPE> declaration is not case sensitive.

+

The <!DOCTYPE> declaration for HTML5 is:

+
+
+ <!DOCTYPE html>
+
+
+ +

HTML Headings

+

HTML headings are defined with the <h1> to <h6> tags.

+

<h1> defines the most important heading. <h6> defines the least important + heading: 

+
+

Example

+
+ <h1>This is heading 1</h1>
+ <h2>This is heading 2</h2>
+ <h3>This is heading 3</h3> +
+ Try it Yourself » +
+ +
+
+ + + +
+ +
+
+ +

HTML Paragraphs

+

HTML paragraphs are defined with the <p> tag:

+
+

Example

+
+ <p>This is a paragraph.</p>
+ <p>This is another paragraph.</p> +
+ Try it Yourself » +
+
+ +

HTML Links

+

HTML links are defined with the <a> tag:

+
+

Example

+
+ <a href="https://www.w3schools.com">This is a link</a> +
+ Try it Yourself » +
+

The link's destination is specified in the href attribute. 

+

Attributes are used to provide additional information about HTML elements.

+

You will learn more about attributes in a later chapter.

+
+ +

HTML Images

+

HTML images are defined with the <img> tag.

+

The source file (src), alternative text (alt), + width, and height are provided as attributes:

+
+

Example

+
+ <img src="w3schools.jpg" alt="W3Schools.com" width="104" height="142"> +
+ Try it Yourself » +
+
+ +

How to View HTML Source?

+

Have you ever seen a Web page and wondered "Hey! How did they do that?"

+

View HTML Source Code:

+

Right-click in an HTML page and select "View Page Source" (in + Chrome) or "View Source" (in Edge), or similar in other browsers. This will open a window + containing the HTML source code of the page.

+

Inspect an HTML Element:

+

Right-click on an element (or a blank area), and choose "Inspect" or + "Inspect Element" to see what elements are made up of (you will see both + the HTML and the CSS). You can also edit the HTML or CSS on-the-fly in the + Elements or Styles panel that opens.

+ +
+ +
+
+ +
+ +
+ + +
+ + + + + + + diff --git a/src/WattleScript.Tests/Templating/Tests/Html/1-w3s.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/1-w3s.wthtml new file mode 100644 index 00000000..702a878d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/1-w3s.wthtml @@ -0,0 +1,1324 @@ + + + + HTML Basic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Tutorials + References + Exercises + Videos + Pro NEW + + Menu + +
+
+ + +
+ + +
+ + + + +
+
+
+ + + HTML + CSS + JAVASCRIPT + SQL + PYTHON + PHP + BOOTSTRAP + HOW TO + W3.CSS + JAVA + JQUERY + C + C++ + C# + R + React + + + + + + + + +
+ +
+ +
+ +
+ + + + + + + + +
+
+ + + + + + +
+
+
+ +

HTML Tutorial

+ HTML HOME + HTML Introduction + HTML Editors + HTML Basic + HTML Elements + HTML Attributes + HTML Headings + HTML Paragraphs + HTML Styles + HTML Formatting + HTML Quotations + HTML Comments + HTML Colors +
+ Colors + RGB + HEX + HSL +
+ HTML CSS + HTML Links + + HTML Images + + HTML Favicon + HTML Tables + + + + + HTML Lists + + HTML Block & Inline + HTML Classes + HTML Id + HTML Iframes + HTML JavaScript + HTML File Paths + HTML Head + HTML Layout + HTML Responsive + HTML Computercode + HTML Semantics + HTML Style Guide + HTML Entities + HTML Symbols + HTML Emojis + HTML Charset + HTML URL Encode + HTML vs. XHTML +
+

HTML Forms

+ HTML Forms + HTML Form Attributes + HTML Form Elements + HTML Input Types + HTML Input Attributes + HTML Input Form Attributes +
+

HTML Graphics

+ HTML Canvas + HTML SVG +
+

HTML Media

+ HTML Media + HTML Video + HTML Audio + HTML Plug-ins + HTML YouTube +
+

HTML APIs

+ HTML Geolocation + HTML Drag/Drop + HTML Web Storage + HTML Web Workers + HTML SSE +
+

HTML Examples

+ HTML Examples + HTML Quiz + HTML Exercises + HTML Certificate + HTML Summary + HTML Accessibility +
+

HTML References

+ HTML Tag List + HTML Attributes + HTML Global Attributes + HTML Browser Support + HTML Events + HTML Colors + HTML Canvas + HTML Audio/Video + HTML Doctypes + HTML Character Sets + HTML URL Encode + HTML Lang Codes + HTTP Messages + HTTP Methods + PX to EM Converter + Keyboard Shortcuts +

+
+
+
+
+
+
+
+ + + +
+ + +
+

HTML Basic Examples

+ +
+

In this chapter we will show some basic HTML examples.

+

Don't worry if we use tags you have not learned about yet.

+
+ +

HTML Documents

+

All HTML documents must start with a document type declaration: <!DOCTYPE html>.

+

The HTML document itself begins with <html> and ends with </html>.

+

The visible part of the HTML document is between <body> and </body>.

+
+

Example

+
+ <!DOCTYPE html>
<html>
<body>

<h1>My First Heading</h1>
<p>My first paragraph.</p>

</body>
</html>
+ Try it Yourself » +
+
+ +

The <!DOCTYPE> Declaration

+

The <!DOCTYPE> declaration represents the document type, and helps browsers to display web pages correctly.

+

It must only appear once, at the top of the page (before any HTML tags).

+

The <!DOCTYPE> declaration is not case sensitive.

+

The <!DOCTYPE> declaration for HTML5 is:

+
+
+ <!DOCTYPE html>
+
+
+ +

HTML Headings

+

HTML headings are defined with the <h1> to <h6> tags.

+

<h1> defines the most important heading. <h6> defines the least important + heading: 

+
+

Example

+
+ <h1>This is heading 1</h1>
+ <h2>This is heading 2</h2>
+ <h3>This is heading 3</h3> +
+ Try it Yourself » +
+ +
+
+ + + +
+ +
+
+ +

HTML Paragraphs

+

HTML paragraphs are defined with the <p> tag:

+
+

Example

+
+ <p>This is a paragraph.</p>
+ <p>This is another paragraph.</p> +
+ Try it Yourself » +
+
+ +

HTML Links

+

HTML links are defined with the <a> tag:

+
+

Example

+
+ <a href="https://www.w3schools.com">This is a link</a> +
+ Try it Yourself » +
+

The link's destination is specified in the href attribute. 

+

Attributes are used to provide additional information about HTML elements.

+

You will learn more about attributes in a later chapter.

+
+ +

HTML Images

+

HTML images are defined with the <img> tag.

+

The source file (src), alternative text (alt), + width, and height are provided as attributes:

+
+

Example

+
+ <img src="w3schools.jpg" alt="W3Schools.com" width="104" height="142"> +
+ Try it Yourself » +
+
+ +

How to View HTML Source?

+

Have you ever seen a Web page and wondered "Hey! How did they do that?"

+

View HTML Source Code:

+

Right-click in an HTML page and select "View Page Source" (in + Chrome) or "View Source" (in Edge), or similar in other browsers. This will open a window + containing the HTML source code of the page.

+

Inspect an HTML Element:

+

Right-click on an element (or a blank area), and choose "Inspect" or + "Inspect Element" to see what elements are made up of (you will see both + the HTML and the CSS). You can also edit the HTML or CSS on-the-fly in the + Elements or Styles panel that opens.

+ +
+ +
+
+ +
+ +
+ + +
+ + + + + + + diff --git a/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.html b/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.html new file mode 100644 index 00000000..672a8a56 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.html @@ -0,0 +1,1323 @@ + + + HTML Basic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Tutorials + References + Exercises + Videos + Pro NEW + + Menu + +
+
+ + +
+ + +
+ + + + +
+
+
+ + + HTML + CSS + JAVASCRIPT + SQL + PYTHON + PHP + BOOTSTRAP + HOW TO + W3.CSS + JAVA + JQUERY + C + C++ + C# + R + React + + + + + + + + +
+ +
+ +
+ +
+ + + + + + + + +
+
+ + + + + + +
+
+
+ +

HTML Tutorial

+ HTML HOME + HTML Introduction + HTML Editors + HTML Basic + HTML Elements + HTML Attributes + HTML Headings + HTML Paragraphs + HTML Styles + HTML Formatting + HTML Quotations + HTML Comments + HTML Colors +
+ Colors + RGB + HEX + HSL +
+ HTML CSS + HTML Links + + HTML Images + + HTML Favicon + HTML Tables + + + + + HTML Lists + + HTML Block & Inline + HTML Classes + HTML Id + HTML Iframes + HTML JavaScript + HTML File Paths + HTML Head + HTML Layout + HTML Responsive + HTML Computercode + HTML Semantics + HTML Style Guide + HTML Entities + HTML Symbols + HTML Emojis + HTML Charset + HTML URL Encode + HTML vs. XHTML +
+

HTML Forms

+ HTML Forms + HTML Form Attributes + HTML Form Elements + HTML Input Types + HTML Input Attributes + HTML Input Form Attributes +
+

HTML Graphics

+ HTML Canvas + HTML SVG +
+

HTML Media

+ HTML Media + HTML Video + HTML Audio + HTML Plug-ins + HTML YouTube +
+

HTML APIs

+ HTML Geolocation + HTML Drag/Drop + HTML Web Storage + HTML Web Workers + HTML SSE +
+

HTML Examples

+ HTML Examples + HTML Quiz + HTML Exercises + HTML Certificate + HTML Summary + HTML Accessibility +
+

HTML References

+ HTML Tag List + HTML Attributes + HTML Global Attributes + HTML Browser Support + HTML Events + HTML Colors + HTML Canvas + HTML Audio/Video + HTML Doctypes + HTML Character Sets + HTML URL Encode + HTML Lang Codes + HTTP Messages + HTTP Methods + PX to EM Converter + Keyboard Shortcuts +

+
+
+
+
+
+
+
+ + + +
+ + +
+

HTML Basic Examples

+ +
+

In this chapter we will show some basic HTML examples.

+

Don't worry if we use tags you have not learned about yet.

+
+ +

HTML Documents

+

All HTML documents must start with a document type declaration: <!DOCTYPE html>.

+

The HTML document itself begins with <html> and ends with </html>.

+

The visible part of the HTML document is between <body> and </body>.

+
+

Example

+
+ <!DOCTYPE html>
<html>
<body>

<h1>My First Heading</h1>
<p>My first paragraph.</p>

</body>
</html>
+ Try it Yourself » +
+
+ +

The <!DOCTYPE> Declaration

+

The <!DOCTYPE> declaration represents the document type, and helps browsers to display web pages correctly.

+

It must only appear once, at the top of the page (before any HTML tags).

+

The <!DOCTYPE> declaration is not case sensitive.

+

The <!DOCTYPE> declaration for HTML5 is:

+
+
+ <!DOCTYPE html>
+
+
+ +

HTML Headings

+

HTML headings are defined with the <h1> to <h6> tags.

+

<h1> defines the most important heading. <h6> defines the least important + heading: 

+
+

Example

+
+ <h1>This is heading 1</h1>
+ <h2>This is heading 2</h2>
+ <h3>This is heading 3</h3> +
+ Try it Yourself » +
+ +
+
+ + + +
+ +
+
+ +

HTML Paragraphs

+

HTML paragraphs are defined with the <p> tag:

+
+

Example

+
+ <p>This is a paragraph.</p>
+ <p>This is another paragraph.</p> +
+ Try it Yourself » +
+
+ +

HTML Links

+

HTML links are defined with the <a> tag:

+
+

Example

+
+ <a href="https://www.w3schools.com">This is a link</a> +
+ Try it Yourself » +
+

The link's destination is specified in the href attribute. 

+

Attributes are used to provide additional information about HTML elements.

+

You will learn more about attributes in a later chapter.

+
+ +

HTML Images

+

HTML images are defined with the <img> tag.

+

The source file (src), alternative text (alt), + width, and height are provided as attributes:

+
+

Example

+
+ <img src="w3schools.jpg" alt="W3Schools.com" width="104" height="142"> +
+ Try it Yourself » +
+
+ +

How to View HTML Source?

+

Have you ever seen a Web page and wondered "Hey! How did they do that?"

+

View HTML Source Code:

+

Right-click in an HTML page and select "View Page Source" (in + Chrome) or "View Source" (in Edge), or similar in other browsers. This will open a window + containing the HTML source code of the page.

+

Inspect an HTML Element:

+

Right-click on an element (or a blank area), and choose "Inspect" or + "Inspect Element" to see what elements are made up of (you will see both + the HTML and the CSS). You can also edit the HTML or CSS on-the-fly in the + Elements or Styles panel that opens.

+ +
+ +
+
+ +
+ +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.wthtml new file mode 100644 index 00000000..1823c90e --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.wthtml @@ -0,0 +1,1325 @@ +@{ + + + HTML Basic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + Tutorials + References + Exercises + Videos + Pro NEW + + Menu + +
+
+ + +
+ + +
+ + + + +
+
+
+ + + HTML + CSS + JAVASCRIPT + SQL + PYTHON + PHP + BOOTSTRAP + HOW TO + W3.CSS + JAVA + JQUERY + C + C++ + C# + R + React + + + + + + + + +
+ +
+ +
+ +
+ + + + + + + + +
+
+ + + + + + +
+
+
+ +

HTML Tutorial

+ HTML HOME + HTML Introduction + HTML Editors + HTML Basic + HTML Elements + HTML Attributes + HTML Headings + HTML Paragraphs + HTML Styles + HTML Formatting + HTML Quotations + HTML Comments + HTML Colors +
+ Colors + RGB + HEX + HSL +
+ HTML CSS + HTML Links + + HTML Images + + HTML Favicon + HTML Tables + + + + + HTML Lists + + HTML Block & Inline + HTML Classes + HTML Id + HTML Iframes + HTML JavaScript + HTML File Paths + HTML Head + HTML Layout + HTML Responsive + HTML Computercode + HTML Semantics + HTML Style Guide + HTML Entities + HTML Symbols + HTML Emojis + HTML Charset + HTML URL Encode + HTML vs. XHTML +
+

HTML Forms

+ HTML Forms + HTML Form Attributes + HTML Form Elements + HTML Input Types + HTML Input Attributes + HTML Input Form Attributes +
+

HTML Graphics

+ HTML Canvas + HTML SVG +
+

HTML Media

+ HTML Media + HTML Video + HTML Audio + HTML Plug-ins + HTML YouTube +
+

HTML APIs

+ HTML Geolocation + HTML Drag/Drop + HTML Web Storage + HTML Web Workers + HTML SSE +
+

HTML Examples

+ HTML Examples + HTML Quiz + HTML Exercises + HTML Certificate + HTML Summary + HTML Accessibility +
+

HTML References

+ HTML Tag List + HTML Attributes + HTML Global Attributes + HTML Browser Support + HTML Events + HTML Colors + HTML Canvas + HTML Audio/Video + HTML Doctypes + HTML Character Sets + HTML URL Encode + HTML Lang Codes + HTTP Messages + HTTP Methods + PX to EM Converter + Keyboard Shortcuts +

+
+
+
+
+
+
+
+ + + +
+ + +
+

HTML Basic Examples

+ +
+

In this chapter we will show some basic HTML examples.

+

Don't worry if we use tags you have not learned about yet.

+
+ +

HTML Documents

+

All HTML documents must start with a document type declaration: <!DOCTYPE html>.

+

The HTML document itself begins with <html> and ends with </html>.

+

The visible part of the HTML document is between <body> and </body>.

+
+

Example

+
+ <!DOCTYPE html>
<html>
<body>

<h1>My First Heading</h1>
<p>My first paragraph.</p>

</body>
</html>
+ Try it Yourself » +
+
+ +

The <!DOCTYPE> Declaration

+

The <!DOCTYPE> declaration represents the document type, and helps browsers to display web pages correctly.

+

It must only appear once, at the top of the page (before any HTML tags).

+

The <!DOCTYPE> declaration is not case sensitive.

+

The <!DOCTYPE> declaration for HTML5 is:

+
+
+ <!DOCTYPE html>
+
+
+ +

HTML Headings

+

HTML headings are defined with the <h1> to <h6> tags.

+

<h1> defines the most important heading. <h6> defines the least important + heading: 

+
+

Example

+
+ <h1>This is heading 1</h1>
+ <h2>This is heading 2</h2>
+ <h3>This is heading 3</h3> +
+ Try it Yourself » +
+ +
+
+ + + +
+ +
+
+ +

HTML Paragraphs

+

HTML paragraphs are defined with the <p> tag:

+
+

Example

+
+ <p>This is a paragraph.</p>
+ <p>This is another paragraph.</p> +
+ Try it Yourself » +
+
+ +

HTML Links

+

HTML links are defined with the <a> tag:

+
+

Example

+
+ <a href="https://www.w3schools.com">This is a link</a> +
+ Try it Yourself » +
+

The link's destination is specified in the href attribute. 

+

Attributes are used to provide additional information about HTML elements.

+

You will learn more about attributes in a later chapter.

+
+ +

HTML Images

+

HTML images are defined with the <img> tag.

+

The source file (src), alternative text (alt), + width, and height are provided as attributes:

+
+

Example

+
+ <img src="w3schools.jpg" alt="W3Schools.com" width="104" height="142"> +
+ Try it Yourself » +
+
+ +

How to View HTML Source?

+

Have you ever seen a Web page and wondered "Hey! How did they do that?"

+

View HTML Source Code:

+

Right-click in an HTML page and select "View Page Source" (in + Chrome) or "View Source" (in Edge), or similar in other browsers. This will open a window + containing the HTML source code of the page.

+

Inspect an HTML Element:

+

Right-click on an element (or a blank area), and choose "Inspect" or + "Inspect Element" to see what elements are made up of (you will see both + the HTML and the CSS). You can also edit the HTML or CSS on-the-fly in the + Elements or Styles panel that opens.

+ +
+ +
+
+ +
+ +
+ + +
+ + + + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/3-checker.html b/src/WattleScript.Tests/Templating/Tests/Html/3-checker.html new file mode 100644 index 00000000..f5992aa5 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/3-checker.html @@ -0,0 +1,131 @@ + + + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez + + + + + + + +
+

Closing Tag Checker for HTML5

+ +
+
+

In HTML5, under certain circumstances, some closing tags are optional. If you leave those closing tags out, the W3C validator will not point them out, since they are technically not an error. Some people, though, may want to close all their tags anyway.

+

If you experience any problems, or have any suggestions, let me know.

+

Before you start

+

Make sure your code is valid, or you could get unexpected results. The script assumes you have valid HTML5 code, but would like to make sure you didn't leave any tags, unintentionally, unclosed.

+

Paste your code here:

+

+ + +

+ +
+ + + +
+
+
© 2016
+ + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/3-checker.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/3-checker.wthtml new file mode 100644 index 00000000..f5992aa5 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/3-checker.wthtml @@ -0,0 +1,131 @@ + + + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez + + + + + + + +
+

Closing Tag Checker for HTML5

+ +
+
+

In HTML5, under certain circumstances, some closing tags are optional. If you leave those closing tags out, the W3C validator will not point them out, since they are technically not an error. Some people, though, may want to close all their tags anyway.

+

If you experience any problems, or have any suggestions, let me know.

+

Before you start

+

Make sure your code is valid, or you could get unexpected results. The script assumes you have valid HTML5 code, but would like to make sure you didn't leave any tags, unintentionally, unclosed.

+

Paste your code here:

+

+ + +

+ +
+ + + +
+
+
© 2016
+ + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/4-checker-block.html b/src/WattleScript.Tests/Templating/Tests/Html/4-checker-block.html new file mode 100644 index 00000000..9ab90f85 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/4-checker-block.html @@ -0,0 +1,130 @@ + + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez + + + + + + + +
+

Closing Tag Checker for HTML5

+ +
+
+

In HTML5, under certain circumstances, some closing tags are optional. If you leave those closing tags out, the W3C validator will not point them out, since they are technically not an error. Some people, though, may want to close all their tags anyway.

+

If you experience any problems, or have any suggestions, let me know.

+

Before you start

+

Make sure your code is valid, or you could get unexpected results. The script assumes you have valid HTML5 code, but would like to make sure you didn't leave any tags, unintentionally, unclosed.

+

Paste your code here:

+

+ + +

+ +
+ + + +
+
+
© 2016
+ + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/4-checker-block.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/4-checker-block.wthtml new file mode 100644 index 00000000..3c8743c8 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/4-checker-block.wthtml @@ -0,0 +1,132 @@ +@{ + + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez + + + + + + + +
+

Closing Tag Checker for HTML5

+ +
+
+

In HTML5, under certain circumstances, some closing tags are optional. If you leave those closing tags out, the W3C validator will not point them out, since they are technically not an error. Some people, though, may want to close all their tags anyway.

+

If you experience any problems, or have any suggestions, let me know.

+

Before you start

+

Make sure your code is valid, or you could get unexpected results. The script assumes you have valid HTML5 code, but would like to make sure you didn't leave any tags, unintentionally, unclosed.

+

Paste your code here:

+

+ + +

+ +
+ + + +
+
+
© 2016
+ + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/5-min.html b/src/WattleScript.Tests/Templating/Tests/Html/5-min.html new file mode 100644 index 00000000..d2f60334 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/5-min.html @@ -0,0 +1,7 @@ + + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/5-min.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/5-min.wthtml new file mode 100644 index 00000000..ee32feca --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/5-min.wthtml @@ -0,0 +1,9 @@ +@{ + + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.html b/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.html new file mode 100644 index 00000000..e51bd1f6 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.html @@ -0,0 +1,16 @@ + + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez + + + + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.wthtml new file mode 100644 index 00000000..7bb5bea3 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.wthtml @@ -0,0 +1,19 @@ +@{ + + + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez + + + + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/7-root.html b/src/WattleScript.Tests/Templating/Tests/Html/7-root.html new file mode 100644 index 00000000..075f7fa6 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/7-root.html @@ -0,0 +1 @@ +

t

\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/7-root.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/7-root.wthtml new file mode 100644 index 00000000..a0652282 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/7-root.wthtml @@ -0,0 +1,3 @@ +@{ +

t

+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Nodes/1-simple.html b/src/WattleScript.Tests/Templating/Tests/Nodes/1-simple.html new file mode 100644 index 00000000..6d580eca --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Nodes/1-simple.html @@ -0,0 +1,9 @@ + + + + +

My First Heading

+

My first paragraph.

+ + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Nodes/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/Nodes/1-simple.wthtml new file mode 100644 index 00000000..6d580eca --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Nodes/1-simple.wthtml @@ -0,0 +1,9 @@ + + + + +

My First Heading

+

My first paragraph.

+ + + \ No newline at end of file From d1483a7d872817ad5a1b2727d05612fc7d53ef6e Mon Sep 17 00:00:00 2001 From: lofcz Date: Sun, 24 Apr 2022 23:04:21 +0200 Subject: [PATCH 32/74] Add support for mathml/svg cdata --- src/WattleScript.Templating/Parser/Parser.cs | 6 ++++++ src/WattleScript.Templating/Parser/ParserUtils.cs | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 51a1d43e..68e395aa 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -1298,6 +1298,12 @@ void ParseHtmlComment(HtmlCommentModes openCommentMode, Sides currentSide) return; } + if (openCommentMode == HtmlCommentModes.Cdata && Peek() == ']' && Peek(2) == ']' && Peek(3) == '>') // ]]> + { + StepN(3); + return; + } + Step(); } } \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index 789687e3..d32d4621 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -13,8 +13,8 @@ internal enum HtmlAttrEnclosingModes internal enum HtmlCommentModes { - DoubleHyphen, // + Cdata // or --> } void ClearPooledBuilder() From 109dd56c7f4eb19b61a1ae75200172c096ef558d Mon Sep 17 00:00:00 2001 From: CallumDev Date: Tue, 26 Apr 2022 00:38:18 +0930 Subject: [PATCH 33/74] Don't emit no-op CLEAN ops --- .../Execution/VM/FunctionBuilder.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs b/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs index 921c6b3c..d43f7bf0 100755 --- a/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs +++ b/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs @@ -276,28 +276,31 @@ public void Emit_Debug(string str) //AppendInstruction(new Instruction() { OpCode = OpCode.Debug, String = str.Substring(0, Math.Min(32, str.Length)) }); } - public int Emit_Enter(RuntimeScopeBlock runtimeScopeBlock) + //TODO: We should be handling clean ops better. This micro-optimisation should not be needed + void PossibleCleanOp(int from, int to) { - return AppendInstruction(new Instruction(OpCode.Clean, runtimeScopeBlock.From, - runtimeScopeBlock.ToInclusive)); + if (to >= from) + AppendInstruction(new Instruction(OpCode.Clean, from, to)); } - public int Emit_Leave(RuntimeScopeBlock runtimeScopeBlock) + public void Emit_Enter(RuntimeScopeBlock runtimeScopeBlock) { - return AppendInstruction(new Instruction(OpCode.Clean, runtimeScopeBlock.From, - runtimeScopeBlock.To)); + PossibleCleanOp(runtimeScopeBlock.From, runtimeScopeBlock.ToInclusive); } - public int Emit_Exit(RuntimeScopeBlock runtimeScopeBlock) + public void Emit_Leave(RuntimeScopeBlock runtimeScopeBlock) { - return AppendInstruction(new Instruction(OpCode.Clean, runtimeScopeBlock.From, - runtimeScopeBlock.ToInclusive)); + PossibleCleanOp(runtimeScopeBlock.From, runtimeScopeBlock.To); } - public int Emit_Clean(RuntimeScopeBlock runtimeScopeBlock) + public void Emit_Exit(RuntimeScopeBlock runtimeScopeBlock) { - return AppendInstruction(new Instruction(OpCode.Clean, runtimeScopeBlock.To + 1, - runtimeScopeBlock.ToInclusive)); + PossibleCleanOp(runtimeScopeBlock.From, runtimeScopeBlock.ToInclusive); + } + + public void Emit_Clean(RuntimeScopeBlock runtimeScopeBlock) + { + PossibleCleanOp(runtimeScopeBlock.To + 1, runtimeScopeBlock.ToInclusive); } public int Emit_CloseUp(SymbolRef sym) From 02b26c7975043a9e7e2cdcd77ca1b881b0092465 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 26 Apr 2022 18:05:52 +0200 Subject: [PATCH 34/74] Improve handling of transition --- src/WattleScript.Templating/Parser/Node.cs | 2 +- src/WattleScript.Templating/Parser/Parser.cs | 36 ++++++++++--------- ...-w3s-block-flaky.html => 2-w3s-block.html} | 0 ...-block-flaky.wthtml => 2-w3s-block.wthtml} | 0 .../Tests/Html/8-w3s-block-min.html | 1 + .../Tests/Html/8-w3s-block-min.wthtml | 1 + 6 files changed, 22 insertions(+), 18 deletions(-) rename src/WattleScript.Tests/Templating/Tests/Html/{2-w3s-block-flaky.html => 2-w3s-block.html} (100%) rename src/WattleScript.Tests/Templating/Tests/Html/{2-w3s-block-flaky.wthtml => 2-w3s-block.wthtml} (100%) create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/8-w3s-block-min.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/8-w3s-block-min.wthtml diff --git a/src/WattleScript.Templating/Parser/Node.cs b/src/WattleScript.Templating/Parser/Node.cs index 8266278f..5b531378 100644 --- a/src/WattleScript.Templating/Parser/Node.cs +++ b/src/WattleScript.Templating/Parser/Node.cs @@ -27,7 +27,7 @@ internal interface INodeWithChildren internal class NodeBase { - + public int CharFrom { get; set; } } internal class ServerNode : NodeBase diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 68e395aa..85c8a01a 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -972,11 +972,12 @@ bool ParseHtmlTag(string? parentTagName) return false; } + HtmlElement el = new HtmlElement() {CharFrom = pos}; char chr = Step(); // has to be < string tagName = ParseHtmlTagName(false); ParseWhitespaceAndNewlines(Sides.Client); - + while (!IsAtEnd()) { bool shouldContinue = LookaheadForTransitionClient(Sides.Client); @@ -985,7 +986,7 @@ bool ParseHtmlTag(string? parentTagName) continue; } - bool shouldEnd = LookaheadForAttributeOrClose(tagName, false); + bool shouldEnd = LookaheadForAttributeOrClose(tagName, false, el); if (shouldEnd) { break; @@ -995,15 +996,15 @@ bool ParseHtmlTag(string? parentTagName) return true; } - bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag) + bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag, HtmlElement el) { if (LookaheadForClosingTag()) { - CloseTag(tagName, startsFromClosingTag); + CloseTag(tagName, startsFromClosingTag, el); return true; } - return ParseAttribute(tagName, startsFromClosingTag); + return ParseAttribute(tagName, startsFromClosingTag, el); } string ParseAttributeName() @@ -1076,7 +1077,7 @@ string ParseAttributeValue() } - bool ParseAttribute(string tagName, bool startsFromClosingTag) + bool ParseAttribute(string tagName, bool startsFromClosingTag, HtmlElement el) { string name = ParseAttributeName(); @@ -1088,14 +1089,14 @@ bool ParseAttribute(string tagName, bool startsFromClosingTag) if (LookaheadForClosingTag()) { - CloseTag(tagName, startsFromClosingTag); + CloseTag(tagName, startsFromClosingTag, el); return true; } return false; } - bool CloseTag(string tagName, bool startsFromClosingTag) + bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement el) { if (tagName == "html") { @@ -1113,9 +1114,10 @@ bool CloseTag(string tagName, bool startsFromClosingTag) } bool parseContent = false; + string tagText = GetCurrentLexeme(); + if (!startsFromClosingTag) { - string tagText = GetCurrentLexeme(); bool isSelfClosing = IsSelfClosingHtmlTag(tagName); bool isSelfClosed = CurrentLexemeIsSelfClosedHtmlTag(); parseContent = !isSelfClosed && !isSelfClosing; @@ -1125,16 +1127,16 @@ bool CloseTag(string tagName, bool startsFromClosingTag) if (tagText == "") // "" has a special meaning only when exactly matched. Any modification like "" will be rendered as a normal tag { DiscardCurrentLexeme(); - ParseHtmlOrPlaintextUntilClosingTag(tagName); + ParseHtmlOrPlaintextUntilClosingTag(tagName, el); } else if (tagName is "script" or "style") // raw text elements { ParsePlaintextUntilClosingTag(tagName); - ParseHtmlClosingTag(tagName); + ParseHtmlClosingTag(tagName, el); } else { - ParseHtmlOrPlaintextUntilClosingTag(tagName); + ParseHtmlOrPlaintextUntilClosingTag(tagName, el); } } } @@ -1146,7 +1148,7 @@ bool CloseTag(string tagName, bool startsFromClosingTag) string h = ""; } - if (startsFromClosingTag && tagName == "/text" && s.Trim() == "") + if (startsFromClosingTag && tagName == "/text" && s.Trim() == "" && source?.Substring(el.CharFrom, 6) == "") // { DiscardCurrentLexeme(); return parseContent; @@ -1157,7 +1159,7 @@ bool CloseTag(string tagName, bool startsFromClosingTag) } // parser has to be positioned at opening < of the closing tag - string ParseHtmlClosingTag(string openingTagName) + string ParseHtmlClosingTag(string openingTagName, HtmlElement el) { ParseWhitespaceAndNewlines(Sides.Client); if (Peek() != '<') @@ -1178,7 +1180,7 @@ string ParseHtmlClosingTag(string openingTagName) continue; } - bool shouldEnd = LookaheadForAttributeOrClose(closingTagName, true); + bool shouldEnd = LookaheadForAttributeOrClose(closingTagName, true, el); if (shouldEnd) { break; @@ -1213,7 +1215,7 @@ void ParsePlaintextUntilClosingTag(string openingTagName) } } - void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) + void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName, HtmlElement el) { AddToken(TokenTypes.Text); string s = GetCurrentLexeme(); @@ -1235,7 +1237,7 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName) { if (Peek(2) == '/' && IsHtmlTagOpeningChar(Peek(3))) { - string closingName = ParseHtmlClosingTag(openingTagName); + string closingName = ParseHtmlClosingTag(openingTagName, el); if (string.Equals($"/{openingTagName}", closingName, StringComparison.InvariantCultureIgnoreCase)) { AddToken(TokenTypes.Text); diff --git a/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.html b/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block.html similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.html rename to src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block.html diff --git a/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block.wthtml similarity index 100% rename from src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block-flaky.wthtml rename to src/WattleScript.Tests/Templating/Tests/Html/2-w3s-block.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/Html/8-w3s-block-min.html b/src/WattleScript.Tests/Templating/Tests/Html/8-w3s-block-min.html new file mode 100644 index 00000000..17f353b2 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/8-w3s-block-min.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/8-w3s-block-min.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/8-w3s-block-min.wthtml new file mode 100644 index 00000000..17f353b2 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/8-w3s-block-min.wthtml @@ -0,0 +1 @@ + \ No newline at end of file From 1bec0ac26913c119473731e148bc881dd71c1521 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 26 Apr 2022 18:21:22 +0200 Subject: [PATCH 35/74] Fix - closing tags don't have to have a whitespace at the end --- src/WattleScript.Templating/Parser/Parser.cs | 2 +- .../Templating/Tests/Html/9-w3s-block-svg.html | 1 + .../Templating/Tests/Html/9-w3s-block-svg.wthtml | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/9-w3s-block-svg.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/9-w3s-block-svg.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 85c8a01a..d3376ab9 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -1206,7 +1206,7 @@ void ParsePlaintextUntilClosingTag(string openingTagName) continue; } - if (Peek() == '<' && Peek(2) == '/' && PeekRange(3, openingTagName.Length) == openingTagName && IsWhitespaceOrNewline(Peek(4 + openingTagName.Length))) + if (Peek() == '<' && Peek(2) == '/' && PeekRange(3, openingTagName.Length) == openingTagName) // && IsWhitespaceOrNewline(Peek(4 + openingTagName.Length)) { break; } diff --git a/src/WattleScript.Tests/Templating/Tests/Html/9-w3s-block-svg.html b/src/WattleScript.Tests/Templating/Tests/Html/9-w3s-block-svg.html new file mode 100644 index 00000000..91f85f7f --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/9-w3s-block-svg.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/9-w3s-block-svg.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/9-w3s-block-svg.wthtml new file mode 100644 index 00000000..5af75f47 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/9-w3s-block-svg.wthtml @@ -0,0 +1,3 @@ +@{ + +} \ No newline at end of file From 1b3e0821d3cacc13d3189c00d9e320d0452cf500 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 26 Apr 2022 21:59:58 +0200 Subject: [PATCH 36/74] Update FunctionBuilder.cs --- .../Execution/VM/FunctionBuilder.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs b/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs index d43f7bf0..ce2e7bea 100755 --- a/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs +++ b/src/WattleScript.Interpreter/Execution/VM/FunctionBuilder.cs @@ -276,11 +276,14 @@ public void Emit_Debug(string str) //AppendInstruction(new Instruction() { OpCode = OpCode.Debug, String = str.Substring(0, Math.Min(32, str.Length)) }); } - //TODO: We should be handling clean ops better. This micro-optimisation should not be needed + // Skips emitting clean ops that would be interpreted as a nop by the VM + // Saves us a little bit of space void PossibleCleanOp(int from, int to) { if (to >= from) + { AppendInstruction(new Instruction(OpCode.Clean, from, to)); + } } public void Emit_Enter(RuntimeScopeBlock runtimeScopeBlock) @@ -300,7 +303,8 @@ public void Emit_Exit(RuntimeScopeBlock runtimeScopeBlock) public void Emit_Clean(RuntimeScopeBlock runtimeScopeBlock) { - PossibleCleanOp(runtimeScopeBlock.To + 1, runtimeScopeBlock.ToInclusive); + //This one needs to be a possible nop for Label statements to work correctly + AppendInstruction(new Instruction(OpCode.Clean, runtimeScopeBlock.To + 1, runtimeScopeBlock.ToInclusive)); } public int Emit_CloseUp(SymbolRef sym) @@ -442,4 +446,4 @@ public int Emit_JLclInit(SymbolRef sym, int target) return AppendInstruction(new Instruction(OpCode.JLclInit, target, sym.Index)); } } -} +} \ No newline at end of file From c2a3ec6436db97e7f37eeea46933635f0451d619 Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 27 Apr 2022 00:04:01 +0200 Subject: [PATCH 37/74] Split errors in recoverable and fatal --- src/WattleScript.Templating/Parser/Node.cs | 3 + src/WattleScript.Templating/Parser/Parser.cs | 97 ++++++++++++++++--- .../Parser/ParserUtils.cs | 23 ++++- .../Templating/TemplatingTestsRunner.cs | 15 +-- .../Tests/Invalid/1-unclosed-end.html | 1 + .../Tests/Invalid/1-unclosed-end.wthtml | 3 + .../Templating/Tests/Recovery/1-simple.html | 4 + .../Templating/Tests/Recovery/1-simple.wthtml | 6 ++ 8 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/Invalid/1-unclosed-end.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Invalid/1-unclosed-end.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/1-simple.wthtml diff --git a/src/WattleScript.Templating/Parser/Node.cs b/src/WattleScript.Templating/Parser/Node.cs index 5b531378..d32615fb 100644 --- a/src/WattleScript.Templating/Parser/Node.cs +++ b/src/WattleScript.Templating/Parser/Node.cs @@ -28,6 +28,9 @@ internal interface INodeWithChildren internal class NodeBase { public int CharFrom { get; set; } + public bool Recovery { get; set; } + public int Line { get; set; } + public int Col { get; set; } } internal class ServerNode : NodeBase diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index d3376ab9..dd0b6d3f 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -36,6 +36,7 @@ enum StepModes private int lastCommitedPos; private int lastCommitedLine; private char c; + private int col; private StringBuilder currentLexeme = new StringBuilder(); private int line = 1; private Script? script; @@ -43,7 +44,11 @@ enum StepModes private bool parsingTransitionCharactersEnabled = true; private Document document; private List? tagHelpers; - + private Stack openElements = new Stack(); + private bool parsingBlock = false; + private List fatalExceptions = new List(); + private List recoveredExceptions = new List(); + public Parser(Script? script, List? tagHelpers) { this.script = script; @@ -72,9 +77,18 @@ public List Parse(string templateSource) AddToken(TokenTypes.Eof); } + HandleUnrecoverableErrors(); return Tokens; } + void HandleUnrecoverableErrors() + { + if (fatalExceptions.Count > 0) + { + throw fatalExceptions[0]; + } + } + bool ParseUntilBalancedChar(char startBr, char endBr, bool startsInbalanced, bool handleStrings, bool handleServerComments) { bool inString = false; @@ -700,6 +714,7 @@ bool ParseKeyword() */ void ParseCodeBlock(bool keepOpeningBrk, bool keepClosingBrk) { + parsingBlock = true; bool matchedOpenBrk = MatchNextNonWhiteSpaceChar('{'); string l = GetCurrentLexeme(); if (l == "{") @@ -734,7 +749,9 @@ void ParseCodeBlock(bool keepOpeningBrk, bool keepClosingBrk) { RemoveLastCharFromCurrentLexeme(); } + AddToken(TokenTypes.BlockExpr); + parsingBlock = false; } bool LastStoredCharMatches(params char[] chars) @@ -972,9 +989,12 @@ bool ParseHtmlTag(string? parentTagName) return false; } - HtmlElement el = new HtmlElement() {CharFrom = pos}; + HtmlElement el = new HtmlElement() {CharFrom = pos, Line = line, Col = col}; char chr = Step(); // has to be < string tagName = ParseHtmlTagName(false); + el.Name = tagName; + openElements.Push(el); + ParseWhitespaceAndNewlines(Sides.Client); @@ -1139,6 +1159,14 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement el) ParseHtmlOrPlaintextUntilClosingTag(tagName, el); } } + else + { + HtmlElement ell = openElements.Peek(); + if (ell == el) + { + openElements.Pop(); + } + } } string s = GetCurrentLexeme(); @@ -1206,9 +1234,12 @@ void ParsePlaintextUntilClosingTag(string openingTagName) continue; } - if (Peek() == '<' && Peek(2) == '/' && PeekRange(3, openingTagName.Length) == openingTagName) // && IsWhitespaceOrNewline(Peek(4 + openingTagName.Length)) + if (Peek() == '<' && Peek(2) == '/') { - break; + if (PeekRange(3, openingTagName.Length) == openingTagName) + { + break; + } } Step(); @@ -1232,14 +1263,36 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName, HtmlElement el) { continue; } + + if (el.Recovery) + { + AddToken(TokenTypes.Text); + return; + } if (Peek() == '<') { if (Peek(2) == '/' && IsHtmlTagOpeningChar(Peek(3))) { string closingName = ParseHtmlClosingTag(openingTagName, el); + HtmlElement openEl = openElements.Peek(); + if (string.Equals($"/{openingTagName}", closingName, StringComparison.InvariantCultureIgnoreCase)) { + AddToken(TokenTypes.Text); + openElements.Pop(); + return; + } + + if (closingName.StartsWith('/')) + { + // recovery: close entire stack + while (openElements.Count > 0) + { + HtmlElement rel = openElements.Pop(); + rel.Recovery = true; + } + AddToken(TokenTypes.Text); return; } @@ -1255,6 +1308,11 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName, HtmlElement el) Step(); } + if (parsingBlock) + { + FatalIfInBlock($"Unclosed element {el.Name} at line {el.Line}, {el.Col}. Parser could not recover from this error."); + } + s = GetCurrentLexeme(); } @@ -1280,6 +1338,9 @@ bool LookaheadForHtmlComment(Sides currentSide) void ParseHtmlComment(HtmlCommentModes openCommentMode, Sides currentSide) { + int startLine = line; + int startCol = col; + if (currentSide == Sides.Server) { AddToken(TokenTypes.BlockExpr); @@ -1294,18 +1355,26 @@ void ParseHtmlComment(HtmlCommentModes openCommentMode, Sides currentSide) StepN(3); } - if (Peek() == '-' && Peek(2) == '-' && Peek(3) == '>') // --> + while (!IsAtEnd()) { - StepN(3); - return; - } + if (Peek() == '-' && Peek(2) == '-' && Peek(3) == '>') // --> + { + StepN(3); + AddToken(TokenTypes.Text); + return; + } - if (openCommentMode == HtmlCommentModes.Cdata && Peek() == ']' && Peek(2) == ']' && Peek(3) == '>') // ]]> - { - StepN(3); - return; - } + if (openCommentMode == HtmlCommentModes.Cdata && Peek() == ']' && Peek(2) == ']' && Peek(3) == '>') // ]]> + { + StepN(3); + AddToken(TokenTypes.Text); + return; + } - Step(); + Step(); + } + + // [todo] error, unclosed html comment + FatalIfInBlock($"Unclosed HTML comment at line {startLine}, {startCol}. Parser could not recover from this error."); } } \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index d32d4621..f4b6b6d0 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -69,7 +69,8 @@ char Step(int i = 1) { Buffer.Append(cc); } - + + col += i; pos += i; c = cc; @@ -77,6 +78,12 @@ char Step(int i = 1) { pos = source.Length; } + + if (cc == '\n') + { + col = 1; + line++; + } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !IsAtEnd()) { @@ -212,6 +219,20 @@ void AddBufferToCurrentLexeme() currentLexeme.Append(Buffer); } + void FatalIfInBlock(string message) + { + Exception e = new Exception(message); + + if (parsingBlock) + { + fatalExceptions.Add(e); + } + else + { + recoveredExceptions.Add(e); + } + } + bool IsSelfClosing(string tagName) { return tagName is "area" diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index 78813d9e..939e99c6 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -44,7 +44,6 @@ public async Task RunCore(string path, bool reportErrors = false) script.Options.Directives.Add("using"); TemplatingEngine tmp = new TemplatingEngine(script); - string debugStr = tmp.Debug(code); TemplatingEngine.RenderResult rr = null; if (path.Contains("flaky")) @@ -70,22 +69,24 @@ public async Task RunCore(string path, bool reportErrors = false) rr = await tmp.Render(code); Assert.AreEqual(output, rr.Output, $"Test {path} did not pass."); - if (path.Contains("invalid")) + if (path.ToLowerInvariant().Contains("invalid")) { Assert.Fail("Expected to crash but 'passed'"); } + + string debugStr = tmp.Debug(code); } catch (Exception e) { - if (e is AssertionException ae) + if (path.ToLowerInvariant().Contains("invalid")) { - Assert.Fail($"Test {path} did not pass.\nMessage: {ae.Message}\n{ae.StackTrace}\nParsed template:\n{rr?.Transpiled ?? ""}"); + Assert.Pass($"Crashed as expected: {e.Message}"); return; } - - if (path.Contains("invalid")) + + if (e is AssertionException ae) { - Assert.Pass($"Crashed as expected: {e.Message}"); + Assert.Fail($"Test {path} did not pass.\nMessage: {ae.Message}\n{ae.StackTrace}\nParsed template:\n{rr?.Transpiled ?? ""}"); return; } diff --git a/src/WattleScript.Tests/Templating/Tests/Invalid/1-unclosed-end.html b/src/WattleScript.Tests/Templating/Tests/Invalid/1-unclosed-end.html new file mode 100644 index 00000000..1c8a701b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Invalid/1-unclosed-end.html @@ -0,0 +1 @@ +

text \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Invalid/1-unclosed-end.wthtml b/src/WattleScript.Tests/Templating/Tests/Invalid/1-unclosed-end.wthtml new file mode 100644 index 00000000..de2a3503 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Invalid/1-unclosed-end.wthtml @@ -0,0 +1,3 @@ +@{ +

text +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Recovery/1-simple.html b/src/WattleScript.Tests/Templating/Tests/Recovery/1-simple.html new file mode 100644 index 00000000..d21be282 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Recovery/1-simple.html @@ -0,0 +1,4 @@ +

\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Recovery/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/Recovery/1-simple.wthtml new file mode 100644 index 00000000..2b1387c9 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Recovery/1-simple.wthtml @@ -0,0 +1,6 @@ +@{ + +} \ No newline at end of file From b84e76fb297d7f49f3c9c63e9a97cb588aec795d Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 27 Apr 2022 00:35:48 +0200 Subject: [PATCH 38/74] Relax unicode --- .../Tree/Lexer/LexerUtils.cs | 46 +++++++++++++++---- .../Templating/Tests/Recovery/2-table.html | 12 +++++ .../Templating/Tests/Recovery/2-table.wthtml | 14 ++++++ 3 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/2-table.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/2-table.wthtml diff --git a/src/WattleScript.Interpreter/Tree/Lexer/LexerUtils.cs b/src/WattleScript.Interpreter/Tree/Lexer/LexerUtils.cs index af35e7d0..580dbbca 100755 --- a/src/WattleScript.Interpreter/Tree/Lexer/LexerUtils.cs +++ b/src/WattleScript.Interpreter/Tree/Lexer/LexerUtils.cs @@ -148,6 +148,7 @@ public static string UnescapeLuaString(Token token, string str) string hexprefix = ""; string val = ""; bool zmode = false; + bool smart_unicode = false; foreach (char c in str) { @@ -228,26 +229,53 @@ public static string UnescapeLuaString(Token token, string str) } else { + void EndSequence() + { + try + { + int i = int.Parse(val, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + sb.Append(ConvertUtf32ToChar(i)); + unicode_state = 0; + val = string.Empty; + escape = false; + smart_unicode = false; + } + catch (Exception e) + { + + } + } + if (unicode_state == 1) { if (c != '{') - throw new SyntaxErrorException(token, "'{' expected near '\\u'"); - + { + //throw new SyntaxErrorException(token, "'{' expected near '\\u'"); + smart_unicode = true; + val += c; + } + unicode_state = 2; } else if (unicode_state == 2) { - if (c == '}') + if (!smart_unicode && c == '}') { - int i = int.Parse(val, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - sb.Append(ConvertUtf32ToChar(i)); - unicode_state = 0; - val = string.Empty; - escape = false; + EndSequence(); } else if (val.Length >= 8) { - throw new SyntaxErrorException(token, "'}' missing, or unicode code point too large after '\\u'"); + if (smart_unicode) + { + throw new SyntaxErrorException(token, "Unicode code point too large after '\\u' (max 8 chars)"); + } + + throw new SyntaxErrorException(token, "'}' missing, or unicode code point too large after '\\u' (max 8 chars)"); + } + else if (smart_unicode && !CharIsHexDigit(c)) + { + EndSequence(); + goto redo; } else { diff --git a/src/WattleScript.Tests/Templating/Tests/Recovery/2-table.html b/src/WattleScript.Tests/Templating/Tests/Recovery/2-table.html new file mode 100644 index 00000000..7f887225 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Recovery/2-table.html @@ -0,0 +1,12 @@ + + + + +
37547 TEE Electric Powered Rail Car Train Functions (Abbreviated) +
Function Control Unit Central Station +
Headlights ✔ +
Interior Lights ✔ +
Electric locomotive operating sounds ✔ +
Engineer’s cab lighting ✔ +
Station Announcements - Swiss ✔ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Recovery/2-table.wthtml b/src/WattleScript.Tests/Templating/Tests/Recovery/2-table.wthtml new file mode 100644 index 00000000..d5119dfa --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Recovery/2-table.wthtml @@ -0,0 +1,14 @@ +@{ + + + + +
37547 TEE Electric Powered Rail Car Train Functions (Abbreviated) +
Function Control Unit Central Station +
Headlights ✔ +
Interior Lights ✔ +
Electric locomotive operating sounds ✔ +
Engineer’s cab lighting ✔ +
Station Announcements - Swiss ✔ +
+} \ No newline at end of file From 5c6f30a4fd7345f806cfeeecb048b03898047ea3 Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 27 Apr 2022 23:34:42 +0200 Subject: [PATCH 39/74] Improve recovery support more cases of invalid elements --- src/WattleScript.Templating/Extensions.cs | 22 + src/WattleScript.Templating/Parser/Parser.cs | 111 +- .../Parser/ParserUtils.cs | 72 +- .../Templating/Tests/Html/10-github.html | 1796 ++++++++++++++++ .../Templating/Tests/Html/10-github.wthtml | 1798 +++++++++++++++++ .../Templating/Tests/Html/11-github-min.html | 200 ++ .../Tests/Html/11-github-min.wthtml | 202 ++ .../Templating/Tests/Html/6-min-2.html | 3 +- .../AllowedKeyword/For/2-for-ul.html | 5 +- .../IfElseElseif/10-if-elseif-multiple.html | 3 +- .../11-if-elseif-no-whitespace.html | 3 +- .../IfElseElseif/12-self-closing.html | 5 +- .../IfElseElseif/16-transition-text.wthtml | 4 +- .../IfElseElseif/2-if-else.html | 3 +- .../IfElseElseif/6-if-html-code-block.html | 1 + .../IfElseElseif/7-if-block.html | 3 +- .../8-if-html-code-block-explicit.html | 1 + .../IfElseElseif/9-if-elseif.html | 3 +- .../AllowedKeyword/While/1-while.html | 4 +- .../AllowedKeyword/While/2-do-while.html | 4 +- 20 files changed, 4191 insertions(+), 52 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/10-github.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/10-github.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/11-github-min.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/11-github-min.wthtml diff --git a/src/WattleScript.Templating/Extensions.cs b/src/WattleScript.Templating/Extensions.cs index 067bce96..258c5f2d 100644 --- a/src/WattleScript.Templating/Extensions.cs +++ b/src/WattleScript.Templating/Extensions.cs @@ -9,4 +9,26 @@ public static string ToDescriptionString(this Enum val) DescriptionAttribute[] attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString())?.GetCustomAttributes(typeof(DescriptionAttribute), false)!; return attributes.Length > 0 ? attributes[0].Description : ""; } + + public static T? Peek(this List list) + { + return list.Count > 0 ? list[^1] : default; + } + + public static T? Pop(this List list) + { + if (list.Count > 0) + { + T? itm = list[^1]; + list.RemoveAt(list.Count - 1); + return itm; + } + + return default; + } + + public static void Push(this List list, T? itm) + { + list.Add(itm); + } } \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index dd0b6d3f..65c2b99d 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -37,6 +37,9 @@ enum StepModes private int lastCommitedLine; private char c; private int col; + private int storedLine; + private int storedCol; + private char storedC; private StringBuilder currentLexeme = new StringBuilder(); private int line = 1; private Script? script; @@ -44,7 +47,7 @@ enum StepModes private bool parsingTransitionCharactersEnabled = true; private Document document; private List? tagHelpers; - private Stack openElements = new Stack(); + private List openElements = new List(); private bool parsingBlock = false; private List fatalExceptions = new List(); private List recoveredExceptions = new List(); @@ -826,7 +829,8 @@ void HandleStringSequence(char chr) { if (stringChar == chr) { - inString = false; + inString = false; + AddToken(TokenTypes.BlockExpr); } } } @@ -882,15 +886,25 @@ void HandleStringSequence(char chr) continue; } - if (Peek() == '<' && IsHtmlTagOpeningChar(Peek(2))) + if (Peek() == '<') { - if (allowHtml || LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) + if (IsHtmlTagOpeningChar(Peek(2))) { - StepEol(); - AddToken(TokenTypes.BlockExpr); - ParseHtmlTag(null); + if (allowHtml || LastStoredCharNotWhitespaceMatches('\n', '\r', ';')) + { + StepEol(); + string ss = GetCurrentLexeme(); + AddTokenSplitRightTrim(TokenTypes.BlockExpr, TokenTypes.Text); + ParseHtmlTag(null); - allowHtml = true; + allowHtml = true; + continue; + } + } + else if (Peek(2) == '/') + { + // closing tag without opening is fatal + ParseHtmlClosingTag("", null, false); continue; } } @@ -916,16 +930,21 @@ void HandleStringSequence(char chr) string str = GetCurrentLexeme(); } - string ParseHtmlTagName(bool inBuffer) + string ParseHtmlTagName(bool inBuffer, int offset = 1) { StringBuilder sb = new StringBuilder(); if (inBuffer) { - ClearBuffer(); + ClearBuffer(true); SetStepMode(StepModes.Buffer); } + if (offset > 1 && inBuffer) + { + pos += offset - 1; + } + // Tag name can be provided via a server transition so we have to check for that LookaheadForTransitionClient(Sides.Client); @@ -935,7 +954,7 @@ string ParseHtmlTagName(bool inBuffer) char chr = Peek(); string lexeme = GetCurrentLexeme(); - Throw("First char after < in an opening HTML tag must be _, ! or alpha"); + Throw("First char after < in an HTML tag must be _, !, / or alpha"); } else { @@ -944,7 +963,7 @@ string ParseHtmlTagName(bool inBuffer) } // The next few chars represent element's name - while (!IsAtEnd() && (IsAlphaNumeric(Peek()))) + while (!IsAtEnd() && IsHtmlTagChar(Peek())) { bool shouldContinue = LookaheadForTransitionServerSide(); if (shouldContinue) @@ -966,7 +985,7 @@ string ParseHtmlTagName(bool inBuffer) string tagName = GetBuffer(); AddBufferToCurrentLexeme(); - ClearBuffer(); + ClearBuffer(false); SetStepMode(StepModes.CurrentLexeme); return tagName; @@ -1016,7 +1035,7 @@ bool ParseHtmlTag(string? parentTagName) return true; } - bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag, HtmlElement el) + bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag, HtmlElement? el) { if (LookaheadForClosingTag()) { @@ -1116,7 +1135,7 @@ bool ParseAttribute(string tagName, bool startsFromClosingTag, HtmlElement el) return false; } - bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement el) + bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el) { if (tagName == "html") { @@ -1135,13 +1154,15 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement el) bool parseContent = false; string tagText = GetCurrentLexeme(); - + if (!startsFromClosingTag) { bool isSelfClosing = IsSelfClosingHtmlTag(tagName); bool isSelfClosed = CurrentLexemeIsSelfClosedHtmlTag(); - parseContent = !isSelfClosed && !isSelfClosing; - + bool startsWithSlash = tagName.StartsWith('/'); + + parseContent = !isSelfClosed && !isSelfClosing && !startsWithSlash; + if (parseContent) { if (tagText == "") // "" has a special meaning only when exactly matched. Any modification like "" will be rendered as a normal tag @@ -1152,7 +1173,7 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement el) else if (tagName is "script" or "style") // raw text elements { ParsePlaintextUntilClosingTag(tagName); - ParseHtmlClosingTag(tagName, el); + ParseHtmlClosingTag(tagName, el, false); } else { @@ -1161,7 +1182,7 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement el) } else { - HtmlElement ell = openElements.Peek(); + HtmlElement? ell = openElements.Peek(); if (ell == el) { openElements.Pop(); @@ -1176,7 +1197,7 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement el) string h = ""; } - if (startsFromClosingTag && tagName == "/text" && s.Trim() == "" && source?.Substring(el.CharFrom, 6) == "") // + if (startsFromClosingTag && tagName == "/text" && s.Trim() == "" && el != null && source?.Substring(el.CharFrom, 6) == "") // { DiscardCurrentLexeme(); return parseContent; @@ -1187,7 +1208,7 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement el) } // parser has to be positioned at opening < of the closing tag - string ParseHtmlClosingTag(string openingTagName, HtmlElement el) + string ParseHtmlClosingTag(string openingTagName, HtmlElement? el, bool inBuffer) { ParseWhitespaceAndNewlines(Sides.Client); if (Peek() != '<') @@ -1196,7 +1217,7 @@ string ParseHtmlClosingTag(string openingTagName, HtmlElement el) } char chr = Step(); // has to be < - string closingTagName = ParseHtmlTagName(false); + string closingTagName = ParseHtmlTagName(inBuffer); //ParseUntilBalancedChar('<', '>', true, true, true); @@ -1274,28 +1295,48 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName, HtmlElement el) { if (Peek(2) == '/' && IsHtmlTagOpeningChar(Peek(3))) { - string closingName = ParseHtmlClosingTag(openingTagName, el); - HtmlElement openEl = openElements.Peek(); + string str = GetCurrentLexeme(); + AddToken(TokenTypes.Text); - if (string.Equals($"/{openingTagName}", closingName, StringComparison.InvariantCultureIgnoreCase)) + string closingNameLookahead = ParseHtmlTagName(true, 3); // skip 0) { - // recovery: close entire stack - while (openElements.Count > 0) + // [todo] + // here two actions can take place based on the context + // 1) peeked element is superfluous and doesn't have an opening element ->
+ // 2) peeked element encloses another element that is already in opened elements ->
+ if (openElements.FirstOrDefault(x => string.Equals(x?.Name, closingNameLookahead, StringComparison.InvariantCultureIgnoreCase)) == null) { - HtmlElement rel = openElements.Pop(); - rel.Recovery = true; + ParseHtmlTag(openingTagName); + } + else + { + HtmlElement? rel = openElements.Pop(); + if (rel != null) + { + rel.Recovery = true; + } } - - AddToken(TokenTypes.Text); - return; } + + string lex = GetCurrentLexeme(); + AddToken(TokenTypes.Text); + return; } if (IsHtmlTagOpeningChar(Peek(2))) diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index f4b6b6d0..f4c82c9c 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -37,6 +37,11 @@ bool IsAlphaNumeric(char ch) return IsDigit(ch) || IsAlpha(ch); } + bool IsHtmlTagChar(char ch) + { + return IsAlphaNumeric(ch) || ch == ':' || ch == '-'; + } + bool IsAlpha(char ch) { return char.IsLetter(ch) || ch is '_'; @@ -69,7 +74,7 @@ char Step(int i = 1) { Buffer.Append(cc); } - + col += i; pos += i; c = cc; @@ -100,18 +105,26 @@ char Step(int i = 1) void StorePos() { storedPos = pos; + storedCol = col; + storedLine = line; } void RestorePos() { pos = storedPos; - if (pos >= source.Length) + if (source != null && pos >= source.Length) { pos = source.Length - 1; } storedPos = 0; - c = source[pos]; + if (source != null) + { + c = source[pos]; + } + + col = storedCol; + line = storedLine; DiscardCurrentLexeme(); } @@ -188,6 +201,37 @@ bool IsSelfClosingHtmlTag(string htmlTag) return IsSelfClosing(htmlTag.ToLowerInvariant().Replace("!", "")); } + bool AddTokenSplitRightTrim(TokenTypes lhsType, TokenTypes rhsType) + { + string str = GetCurrentLexeme(); + string lhs = str.TrimEnd(); + + int dif = str.Length - lhs.Length; + bool any = false; + + if (lhs.Length > 0) + { + currentLexeme.Clear(); + currentLexeme.Append(lhs); + any = AddToken(lhsType); + } + + if (dif > 0) + { + string rhs = str.Substring(str.Length - dif); + currentLexeme.Clear(); + currentLexeme.Append(rhs); + bool any2 = AddToken(rhsType); + + if (!any) + { + any = any2; + } + } + + return any; + } + bool AddToken(TokenTypes type) { if (currentLexeme.Length == 0) @@ -254,9 +298,24 @@ bool IsSelfClosing(string tagName) or "doctype"; } - void ClearBuffer() + void ClearBuffer(bool start) { Buffer.Clear(); + + if (start) + { + storedCol = col; + storedLine = line; + storedC = c; + storedPos = pos; + } + else + { + col = storedCol; + line = storedLine; + c = storedC; + pos = storedPos; + } } void SetStepMode(StepModes mode) @@ -292,4 +351,9 @@ bool StepEol() return false; } + + void DiscardTokensAfter(int n) + { + Tokens = Tokens.GetRange(0, n); + } } \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/10-github.html b/src/WattleScript.Tests/Templating/Tests/Html/10-github.html new file mode 100644 index 00000000..009db364 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/10-github.html @@ -0,0 +1,1796 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GitHub - WattleScript/wattlescript: WattleScript C# scripting engine. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + + + +
+ +
+ + + + + + + +
+ + + +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + +
+ + +
+ +
+ + + + + +
+
+ +
+ +
+
+ + + main + + + + +
+
+
+ Switch branches/tags + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + +
+ + +
+
+
+
+ +
+ +
+ + + + +
+ + + + + + + + +
+ Code + +
+ +
+
+ +
+ + +
+
+ + + + +
+
+

Latest commit

+
+ +
+
 
+
+

Git stats

+ +
+
+
+

Files

+ + + + + Permalink + +
+ + + Failed to load latest commit information. + + + +
+
+
+
Type
+
Name
+
Latest commit message
+
Commit time
+
+ +
+
+ +
+ + + +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ +
+ src +
+ +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ + + +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ + + +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ +
+ AUTHORS +
+ +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ +
+ LICENSE +
+ +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ +
+ README.md +
+ +
+
 
+
+ +
+
 
+
+ +
+
+ +
+ +
+ + +
+ + + + +
+ +
+
+ + +

+ + README.md + + +

+
+
+ + + +
+

WattleScript

+

https://wattlescript.github.io/

+
+

WattleScript is a scripting engine written in C# for runtimes supporting .NET Standard 2.0 and newer. (.NET 4.7.2+, .NET Core 3.1+). It is a dual-language environment, providing support for Lua 5.2 code as well as its own language, Wattle.

+

Using WattleScript is as easy as:

+
var script = new Script();
+script.DoString("print('Hello World!')");
+

WattleScript is based off the tried and tested MoonSharp project, inheriting its VM design and test suite. The design focuses on easy and fast interop with .NET objects and functions.

+

Features

+
    +
  • Wattle scripting language.
  • +
  • Lua mode 99% compatible with Lua 5.2, differences documented here.
  • +
  • Easily configured sandbox for safe execution of untrusted scripts.
  • +
  • Minimal garbage generation at runtime
  • +
  • No external dependencies
  • +
  • Easy and performant interop with CLR objects, with runtime code generation where supported
  • +
  • Source Generator for AOT interop.
  • +
  • Support for awaiting on returned values
  • +
  • Supports dumping/loading bytecode
  • +
  • Support for the complete Lua standard library with very few exceptions (mostly located in the debug module).
  • +
  • json module for loading json into tables safely at runtime.
  • +
+

License

+

WattleScript is licensed under the 3-clause BSD License. See LICENSE for details.

+
+
+
+ +
+ + +
+
+ +
+
+
+

About

+ +

+ WattleScript C# scripting engine. +

+
+ + + wattlescript.github.io + +
+ + +

Resources

+ + +

License

+ + + + + + +

Stars

+ + +

Watchers

+ + +

Forks

+ + +
+
+ + +
+
+

Languages

+
+ + + + +
+ + +
+
+
+
+ +
+ +
+ + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/10-github.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/10-github.wthtml new file mode 100644 index 00000000..051abd96 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/10-github.wthtml @@ -0,0 +1,1798 @@ +@{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GitHub - WattleScript/wattlescript: WattleScript C# scripting engine. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + + + +
+ +
+ + + + + + + +
+ + + +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + +
+ + +
+ +
+ + + + + +
+
+ +
+ +
+
+ + + main + + + + +
+
+
+ Switch branches/tags + +
+ + + +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + +
+ + +
+
+
+
+ +
+ +
+ + + + +
+ + + + + + + + +
+ Code + +
+ +
+
+ +
+ + +
+
+ + + + +
+
+

Latest commit

+
+ +
+
 
+
+

Git stats

+ +
+
+
+

Files

+ + + + + Permalink + +
+ + + Failed to load latest commit information. + + + +
+
+
+
Type
+
Name
+
Latest commit message
+
Commit time
+
+ +
+
+ +
+ + + +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ +
+ src +
+ +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ + + +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ + + +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ +
+ AUTHORS +
+ +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ +
+ LICENSE +
+ +
+
 
+
+ +
+
 
+
+ +
+
+
+ +
+ +
+ README.md +
+ +
+
 
+
+ +
+
 
+
+ +
+
+ +
+ +
+ + +
+ + + + +
+ +
+
+ + +

+ + README.md + + +

+
+
+ + + +
+

WattleScript

+

https://wattlescript.github.io/

+
+

WattleScript is a scripting engine written in C# for runtimes supporting .NET Standard 2.0 and newer. (.NET 4.7.2+, .NET Core 3.1+). It is a dual-language environment, providing support for Lua 5.2 code as well as its own language, Wattle.

+

Using WattleScript is as easy as:

+
var script = new Script();
+script.DoString("print('Hello World!')");
+

WattleScript is based off the tried and tested MoonSharp project, inheriting its VM design and test suite. The design focuses on easy and fast interop with .NET objects and functions.

+

Features

+
    +
  • Wattle scripting language.
  • +
  • Lua mode 99% compatible with Lua 5.2, differences documented here.
  • +
  • Easily configured sandbox for safe execution of untrusted scripts.
  • +
  • Minimal garbage generation at runtime
  • +
  • No external dependencies
  • +
  • Easy and performant interop with CLR objects, with runtime code generation where supported
  • +
  • Source Generator for AOT interop.
  • +
  • Support for awaiting on returned values
  • +
  • Supports dumping/loading bytecode
  • +
  • Support for the complete Lua standard library with very few exceptions (mostly located in the debug module).
  • +
  • json module for loading json into tables safely at runtime.
  • +
+

License

+

WattleScript is licensed under the 3-clause BSD License. See LICENSE for details.

+
+
+
+ +
+ + +
+
+ +
+
+
+

About

+ +

+ WattleScript C# scripting engine. +

+
+ + + wattlescript.github.io + +
+ + +

Resources

+ + +

License

+ + + + + + +

Stars

+ + +

Watchers

+ + +

Forks

+ + +
+
+ + +
+
+

Languages

+
+ + + + +
+ + +
+
+
+
+ +
+ +
+ + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/11-github-min.html b/src/WattleScript.Tests/Templating/Tests/Html/11-github-min.html new file mode 100644 index 00000000..732108ec --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/11-github-min.html @@ -0,0 +1,200 @@ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/11-github-min.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/11-github-min.wthtml new file mode 100644 index 00000000..362feaa3 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/11-github-min.wthtml @@ -0,0 +1,202 @@ +@{ + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.html b/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.html index e51bd1f6..4bf4f23c 100644 --- a/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.html +++ b/src/WattleScript.Tests/Templating/Tests/Html/6-min-2.html @@ -1,4 +1,5 @@ - + + Find Unclosed Tags - Unclosed Tag Checker - HTML5 - by Alicia Ramirez diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.html index 0c45f675..081ed02a 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/For/2-for-ul.html @@ -1,3 +1,6 @@
    -
  • 1
  • 2
  • 3
  • + +
  • 1
  • +
  • 2
  • +
  • 3
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/10-if-elseif-multiple.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/10-if-elseif-multiple.html index eaa5a919..029f5618 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/10-if-elseif-multiple.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/10-if-elseif-multiple.html @@ -1 +1,2 @@ -
4
\ No newline at end of file + +
4
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/11-if-elseif-no-whitespace.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/11-if-elseif-no-whitespace.html index 5053de3d..38e65812 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/11-if-elseif-no-whitespace.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/11-if-elseif-no-whitespace.html @@ -1 +1,2 @@ -
5
\ No newline at end of file + +
5
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.html index 55a0109a..8793df21 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/12-self-closing.html @@ -1 +1,4 @@ -


100
\ No newline at end of file +
+
+
+
100
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.wthtml index f9ea9318..fad0fd67 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.wthtml +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/16-transition-text.wthtml @@ -1,6 +1,4 @@ @{ a = 10 - - Value of a is @a - + Value of a is @a } \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/2-if-else.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/2-if-else.html index 5053de3d..38e65812 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/2-if-else.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/2-if-else.html @@ -1 +1,2 @@ -
5
\ No newline at end of file + +
5
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/6-if-html-code-block.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/6-if-html-code-block.html index abef679d..b34f34eb 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/6-if-html-code-block.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/6-if-html-code-block.html @@ -1,3 +1,4 @@
+
2
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/7-if-block.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/7-if-block.html index ad68dc44..fd0f8821 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/7-if-block.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/7-if-block.html @@ -1,3 +1,4 @@
    -
  • 20
  • + +
  • 20
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/8-if-html-code-block-explicit.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/8-if-html-code-block-explicit.html index abef679d..b34f34eb 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/8-if-html-code-block-explicit.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/8-if-html-code-block-explicit.html @@ -1,3 +1,4 @@
+
2
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/9-if-elseif.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/9-if-elseif.html index 5053de3d..38e65812 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/9-if-elseif.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/9-if-elseif.html @@ -1 +1,2 @@ -
5
\ No newline at end of file + +
5
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.html index 073ba860..ab5307ba 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/1-while.html @@ -1 +1,3 @@ -
0
1
\ No newline at end of file + +
0
+
1
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.html index 073ba860..ab5307ba 100644 --- a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.html +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/While/2-do-while.html @@ -1 +1,3 @@ -
0
1
\ No newline at end of file + +
0
+
1
\ No newline at end of file From 3b17e589666be5d569964a4341c83e05afd85742 Mon Sep 17 00:00:00 2001 From: lofcz Date: Thu, 28 Apr 2022 01:03:16 +0200 Subject: [PATCH 40/74] Add yt tests --- .../Tree/Lexer/LexerUtils.cs | 60 ++++++++++++++- ...nicode-invalid.lua => 10-unicode-auto.lua} | 0 .../SyntaxCLike/Strings/10-unicode-auto.txt | 1 + .../Strings/10-unicode-invalid.txt | 0 .../Strings/12-unicode-auto-hex.lua | 2 + .../Strings/12-unicode-auto-hex.txt | 1 + .../Strings/13-unicode-auto-two-sequences.lua | 2 + .../Strings/13-unicode-auto-two-sequences.txt | 1 + .../Templating/Tests/Html/12-yt.html | 75 ++++++++++++++++++ .../Templating/Tests/Html/12-yt.wthtml | 77 +++++++++++++++++++ .../Templating/Tests/Html/13-yt-min.html | 1 + .../Templating/Tests/Html/13-yt-min.wthtml | 3 + 12 files changed, 221 insertions(+), 2 deletions(-) rename src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/{10-unicode-invalid.lua => 10-unicode-auto.lua} (100%) create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-auto.txt delete mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-invalid.txt create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/12-unicode-auto-hex.lua create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/12-unicode-auto-hex.txt create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/13-unicode-auto-two-sequences.lua create mode 100644 src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/13-unicode-auto-two-sequences.txt create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/12-yt.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/12-yt.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/13-yt-min.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/13-yt-min.wthtml diff --git a/src/WattleScript.Interpreter/Tree/Lexer/LexerUtils.cs b/src/WattleScript.Interpreter/Tree/Lexer/LexerUtils.cs index e1dae105..e1046bff 100755 --- a/src/WattleScript.Interpreter/Tree/Lexer/LexerUtils.cs +++ b/src/WattleScript.Interpreter/Tree/Lexer/LexerUtils.cs @@ -149,6 +149,8 @@ public static string UnescapeLuaString(Token token, string str) string val = ""; bool zmode = false; bool parsing_unicode_without_brks = false; + bool second_sequence = false; + int unicode_sequence_max_digits = 4; for (int i = 0; i < str.Length; i++) { @@ -259,10 +261,48 @@ public static string UnescapeLuaString(Token token, string str) } else { + int SafeHexParse(string n) + { + if (int.TryParse(n, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int parsedVal)) + { + return parsedVal; + } + + return -1; + } + void EndSequence() { try { + // 2. if current sequence uses surrogates we need to produce a scalar value out of two pairs + if (unicode_sequence_max_digits == 8) + { + if (val.Length != 8) + { + throw new SyntaxErrorException(token, $"Parsing unicode sequence failed at index {i}, char '{c}'. When surrogates are used to represent UTF-16 sequence, both subsequences must have exactly 4 hex digits (8 digits expected in total). Got {val.Length} digits - \"{val}\"."); + } + + string s1 = val.Substring(0, 4); + string s2 = val.Substring(4, 4); + + int h = SafeHexParse(s1); + int l = SafeHexParse(s2); + + if (h == -1) + { + throw new SyntaxErrorException(token, $"Parsing unicode sequence failed at index {i}, char '{c}'. Parsing high UTF-16 subsequence \"{s1}\" failed."); + } + + if (l == -1) + { + throw new SyntaxErrorException(token, $"Parsing unicode sequence failed at index {i}, char '{c}'. Parsing low UTF-16 subsequence \"{s2}\" failed."); + } + + int s = (h - 0xD800) * 0x400 + (l - 0xDC00) + 0x10000; + val = s.ToString("X4"); + } + if (int.TryParse(val, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int parsedVal)) { sb.Append(ConvertUtf32ToChar(parsedVal)); @@ -298,7 +338,7 @@ void EndSequence() { EndSequence(); } - else if (val.Length >= 8) + else if (val.Length > 8) { if (parsing_unicode_without_brks) { @@ -307,8 +347,24 @@ void EndSequence() throw new SyntaxErrorException(token, "'}' missing, or unicode code point too large after '\\u' (max 8 chars)"); } - else if (parsing_unicode_without_brks && !CharIsHexDigit(c)) + else if (parsing_unicode_without_brks && !CharIsHexDigit(c) || val.Length >= unicode_sequence_max_digits) // \uABCD. If more than 4 hex digits are required, two sequences are expected \uABCD\uEFHG { + if (c == '\\' && !second_sequence && str.Length > i + 2 && str[i + 1] == 'u') // peek \u + { + // 1. peeked sequence is a part of the current sequence if the current sequence is \uD800-\uDBFF (high surrogate) + int currentPair = SafeHexParse(val); + + if (currentPair == -1 || !(currentPair >= 0xD800 && currentPair <= 0xDBFF)) + { + goto parseAsSingleSequence; + } + + unicode_sequence_max_digits <<= 1; + i++; + continue; + } + + parseAsSingleSequence: EndSequence(); goto redo; } diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-invalid.lua b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-auto.lua similarity index 100% rename from src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-invalid.lua rename to src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-auto.lua diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-auto.txt b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-auto.txt new file mode 100644 index 00000000..ce081e48 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-auto.txt @@ -0,0 +1 @@ +✔aaaaaa \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-invalid.txt b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/10-unicode-invalid.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/12-unicode-auto-hex.lua b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/12-unicode-auto-hex.lua new file mode 100644 index 00000000..0c5459ff --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/12-unicode-auto-hex.lua @@ -0,0 +1,2 @@ +x = 'Click \u201CCustomize\u201D to' +print(x); \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/12-unicode-auto-hex.txt b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/12-unicode-auto-hex.txt new file mode 100644 index 00000000..f8c00777 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/12-unicode-auto-hex.txt @@ -0,0 +1 @@ +Click “Customize” to \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/13-unicode-auto-two-sequences.lua b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/13-unicode-auto-two-sequences.lua new file mode 100644 index 00000000..de48689b --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/13-unicode-auto-two-sequences.lua @@ -0,0 +1,2 @@ +x = 'music \uD834\uDD1E!' +print(x); \ No newline at end of file diff --git a/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/13-unicode-auto-two-sequences.txt b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/13-unicode-auto-two-sequences.txt new file mode 100644 index 00000000..c6e9a203 --- /dev/null +++ b/src/WattleScript.Tests/EndToEnd/CLike/SyntaxCLike/Strings/13-unicode-auto-two-sequences.txt @@ -0,0 +1 @@ +music 𝄞! \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/12-yt.html b/src/WattleScript.Tests/Templating/Tests/Html/12-yt.html new file mode 100644 index 00000000..674526f0 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/12-yt.html @@ -0,0 +1,75 @@ +Barotrauma - Getting started... like a boss! - YouTube
AboutPressCopyrightContact usCreatorsAdvertiseDevelopersTermsPrivacyPolicy & SafetyHow YouTube worksTest new features
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/12-yt.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/12-yt.wthtml new file mode 100644 index 00000000..90884a79 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/12-yt.wthtml @@ -0,0 +1,77 @@ +@{ +Barotrauma - Getting started... like a boss! - YouTube
AboutPressCopyrightContact usCreatorsAdvertiseDevelopersTermsPrivacyPolicy & SafetyHow YouTube worksTest new features
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/13-yt-min.html b/src/WattleScript.Tests/Templating/Tests/Html/13-yt-min.html new file mode 100644 index 00000000..a5cae8fd --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/13-yt-min.html @@ -0,0 +1 @@ +Click “Customize” to 𝄞 \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/13-yt-min.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/13-yt-min.wthtml new file mode 100644 index 00000000..78190418 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/13-yt-min.wthtml @@ -0,0 +1,3 @@ +@{ +Click “Customize” to 𝄞 +} \ No newline at end of file From 94b19de35cba433804baa4ec52936449463e7ef3 Mon Sep 17 00:00:00 2001 From: lofcz Date: Fri, 29 Apr 2022 15:53:50 +0200 Subject: [PATCH 41/74] Start work on tag helpers --- .../DataTypes/Closure.cs | 3 +- .../Execution/VM/FunctionProto.cs | 8 ++--- .../Parser/TagHelper.cs | 5 +++ .../TemplatingEngine.cs | 30 ++++++++++++++-- .../Templating/TemplatingTestsRunner.cs | 35 +++++++++++++++++++ .../Templating/Tests/TagHelpers/1-simple.html | 1 + .../Tests/TagHelpers/1-simple.wthtml | 1 + .../TagHelpers/TagDefinitions/mytag.wthtml | 7 ++++ 8 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/mytag.wthtml diff --git a/src/WattleScript.Interpreter/DataTypes/Closure.cs b/src/WattleScript.Interpreter/DataTypes/Closure.cs index 69aa8920..ab29d469 100644 --- a/src/WattleScript.Interpreter/DataTypes/Closure.cs +++ b/src/WattleScript.Interpreter/DataTypes/Closure.cs @@ -51,8 +51,7 @@ public enum UpvaluesType /// internal ClosureContext ClosureContext { get; private set; } - internal FunctionProto Function { get; private set; } - + public FunctionProto Function { get; private set; } /// /// Initializes a new instance of the class. diff --git a/src/WattleScript.Interpreter/Execution/VM/FunctionProto.cs b/src/WattleScript.Interpreter/Execution/VM/FunctionProto.cs index 1fd515ef..ba4ea96a 100644 --- a/src/WattleScript.Interpreter/Execution/VM/FunctionProto.cs +++ b/src/WattleScript.Interpreter/Execution/VM/FunctionProto.cs @@ -4,18 +4,18 @@ namespace WattleScript.Interpreter.Execution.VM { [Flags] - enum FunctionFlags + internal enum FunctionFlags { None = 0x0, IsChunk = 0x1, TakesSelf = 0x2, ImplicitThis = 0x4 } - class FunctionProto + public class FunctionProto { //Function Data public string Name; - public FunctionFlags Flags; + internal FunctionFlags Flags; public SymbolRef[] Locals; public SymbolRef[] Upvalues; public Annotation[] Annotations; @@ -25,7 +25,7 @@ class FunctionProto public string[] Strings; public double[] Numbers; //Source Code - public Instruction[] Code; + internal Instruction[] Code; public SourceRef[] SourceRefs; } } \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/TagHelper.cs b/src/WattleScript.Templating/Parser/TagHelper.cs index a3d933eb..c6483da8 100644 --- a/src/WattleScript.Templating/Parser/TagHelper.cs +++ b/src/WattleScript.Templating/Parser/TagHelper.cs @@ -3,6 +3,11 @@ public class TagHelper { public string Name { get; set; } + + public TagHelper(string name) + { + Name = name; + } } public class TagHelperAttribute diff --git a/src/WattleScript.Templating/TemplatingEngine.cs b/src/WattleScript.Templating/TemplatingEngine.cs index 160e3ebc..6e339bbd 100644 --- a/src/WattleScript.Templating/TemplatingEngine.cs +++ b/src/WattleScript.Templating/TemplatingEngine.cs @@ -1,5 +1,6 @@ using System.Text; using WattleScript.Interpreter; +using WattleScript.Interpreter.Execution.VM; namespace WattleScript.Templating; @@ -9,14 +10,14 @@ public class TemplatingEngine private readonly TemplatingEngineOptions options; private readonly Script script; private StringBuilder stdOut = new StringBuilder(); - private readonly List? tagHelpers; + private readonly List tagHelpers; public TemplatingEngine(Script script, TemplatingEngineOptions? options = null, List? tagHelpers = null) { options ??= TemplatingEngineOptions.Default; this.options = options; this.script = script ?? throw new ArgumentNullException(nameof(script)); - this.tagHelpers = tagHelpers; + this.tagHelpers = tagHelpers ?? new List(); script.Globals["stdout_line"] = PrintLine; script.Globals["stdout"] = Print; @@ -136,7 +137,7 @@ public string Debug(string code) string finalText = pooledSb.ToString(); return finalText; } - + public string Transpile(string code) { if (string.IsNullOrWhiteSpace(code)) @@ -188,6 +189,29 @@ public string Transpile(string code) return finalText; } + public async Task ParseTagHelper(string code) + { + stdOut.Clear(); + + string transpiledTemplate = Transpile(code); + DynValue dv = script.LoadString(transpiledTemplate); + + FunctionProto? renderFn = dv.Function.Function.Functions.FirstOrDefault(x => x.Name == "Render"); + + if (renderFn == null) + { + // [todo] err, mandatory Render() not found + return; + } + + IReadOnlyList? annots = dv.Function.Annotations; + + //Table ctx = new Table(script); + //await renderFn.Function.CallAsync(ctx); + + //tagHelpers.Add(new TagHelper()); + } + public async Task Render(string code, Table? globalContext = null, string? friendlyCodeName = null) { stdOut.Clear(); diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index 939e99c6..78589a69 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using NUnit.Framework; @@ -8,6 +9,8 @@ namespace WattleScript.Interpreter.Tests.Templating; +[TestFixture] +[Parallelizable(ParallelScope.All)] public class TemplatingTestsRunner { private const string ROOT_FOLDER = "Templating/Tests"; @@ -17,6 +20,32 @@ static string[] GetTestCases() string[] files = Directory.GetFiles(ROOT_FOLDER, "*.wthtml*", SearchOption.AllDirectories); return files; } + + [OneTimeSetUp] + public async Task Init() + { + foreach (string path in GetTestCases().Where(x => x.Contains("TagDefinitions"))) + { + string code = await File.ReadAllTextAsync(path); + + Script script = new Script(CoreModules.Preset_HardSandbox); + script.Options.IndexTablesFrom = 0; + script.Options.AnnotationPolicy = new CustomPolicy(AnnotationValueParsingPolicy.ForceTable); + script.Options.Syntax = ScriptSyntax.WattleScript; + script.Options.Directives.Add("using"); + + TemplatingEngine tmp = new TemplatingEngine(script); + + try + { + await tmp.ParseTagHelper(code); + } + catch (Exception e) + { + Assert.Fail($"Error parsing tag helper definition\nPath: {path}\nMessage: {e.Message}\nStacktrace: {e.StackTrace}"); + } + } + } [Test, TestCaseSource(nameof(GetTestCases))] public async Task RunThrowErros(string path) @@ -28,6 +57,12 @@ public async Task RunCore(string path, bool reportErrors = false) { string outputPath = path.Replace(".wthtml", ".html"); + if (outputPath.Contains("TagDefinitions")) + { + Assert.Pass($"Tag definition (test skipped)"); + return; + } + if (!File.Exists(outputPath)) { Assert.Inconclusive($"Missing output file for test {path}"); diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.html new file mode 100644 index 00000000..c110e59a --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.wthtml new file mode 100644 index 00000000..ff3bfed7 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.wthtml @@ -0,0 +1 @@ +
My tag!
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/mytag.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/mytag.wthtml new file mode 100644 index 00000000..6dc6cbef --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/mytag.wthtml @@ -0,0 +1,7 @@ +@{ + @@name("my-tag") + + function Render(context) { +
My tag!
+ } +} \ No newline at end of file From 5873e0c19eb3f0d0687a6a155352f227141bf3db Mon Sep 17 00:00:00 2001 From: lofcz Date: Fri, 29 Apr 2022 18:43:57 +0200 Subject: [PATCH 42/74] Support basic tag helpers --- src/WattleScript.Templating/Parser/Parser.cs | 77 +++++++++++++++-- .../Parser/ParserUtils.cs | 6 ++ .../Parser/TagHelper.cs | 4 +- .../TemplatingEngine.cs | 82 +++++++++++++++++-- .../Templating/TemplatingTestsRunner.cs | 39 ++++++--- .../Templating/Tests/TagHelpers/1-simple.html | 2 +- .../Tests/TagHelpers/1-simple.wthtml | 2 +- 7 files changed, 184 insertions(+), 28 deletions(-) diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 65c2b99d..76cd2a94 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -46,16 +46,18 @@ enum StepModes private StepModes stepMode = StepModes.CurrentLexeme; private bool parsingTransitionCharactersEnabled = true; private Document document; - private List? tagHelpers; private List openElements = new List(); private bool parsingBlock = false; private List fatalExceptions = new List(); private List recoveredExceptions = new List(); + private TemplatingEngine engine; + private HtmlTagParsingModes tagParsingMode = HtmlTagParsingModes.Native; + private int tagHelperContentStartStored = 0; - public Parser(Script? script, List? tagHelpers) + public Parser(TemplatingEngine engine, Script? script) { + this.engine = engine; this.script = script; - this.tagHelpers = tagHelpers; document = new Document(); KeywordsMap = new Dictionary?> @@ -1002,7 +1004,8 @@ bool LookaheadForClosingTag() bool ParseHtmlTag(string? parentTagName) { ParseWhitespaceAndNewlines(Sides.Client); - + AddToken(TokenTypes.Text); + if (Peek() != '<') { return false; @@ -1014,9 +1017,13 @@ bool ParseHtmlTag(string? parentTagName) el.Name = tagName; openElements.Push(el); - - ParseWhitespaceAndNewlines(Sides.Client); + if (engine.tagHelpersMap.ContainsKey(tagName.ToLowerInvariant())) + { + return ParseTagHelper(el); + } + ParseWhitespaceAndNewlines(Sides.Client); + while (!IsAtEnd()) { bool shouldContinue = LookaheadForTransitionClient(Sides.Client); @@ -1034,6 +1041,49 @@ bool ParseHtmlTag(string? parentTagName) return true; } + + bool ParseTagHelper(HtmlElement el) + { + // parser is located after name in a tag + /* + * 1. we parse opening tag as normal, + */ + DiscardCurrentLexeme(); + + tagParsingMode = HtmlTagParsingModes.TagHelper; + + ParseWhitespaceAndNewlines(Sides.Client); + while (!IsAtEnd()) + { + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); + if (shouldContinue) + { + continue; + } + + bool shouldEnd = LookaheadForAttributeOrClose(el.Name, false, el); + if (shouldEnd) + { + break; + } + } + + TagHelper helper = engine.tagHelpersMap[el.Name.ToLowerInvariant()]; + + Table ctxTable = new Table(engine.tagHelpersScript); + ctxTable.Set(0, "test"); + + engine.tagHelpersScript.DoString(helper.Template); + engine.tagHelpersScript.Globals.Get("Render").Function.Call(ctxTable); + + string tagOutput = engine.stdOutTagHelper.ToString(); + currentLexeme.Append(tagOutput); + + AddToken(TokenTypes.Text); + tagParsingMode = HtmlTagParsingModes.Native; + + return true; + } bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag, HtmlElement? el) { @@ -1155,6 +1205,11 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el) bool parseContent = false; string tagText = GetCurrentLexeme(); + if (tagParsingMode == HtmlTagParsingModes.TagHelper) + { + tagHelperContentStartStored = pos; + } + if (!startsFromClosingTag) { bool isSelfClosing = IsSelfClosingHtmlTag(tagName); @@ -1202,8 +1257,16 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el) DiscardCurrentLexeme(); return parseContent; } + + if (tagParsingMode == HtmlTagParsingModes.Native) + { + AddToken(TokenTypes.Text); + } + else + { + DiscardCurrentLexeme(); + } - AddToken(TokenTypes.Text); return parseContent; } diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index f4c82c9c..1e19d34b 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -16,6 +16,12 @@ internal enum HtmlCommentModes DoubleHyphen, // Cdata // or --> } + + internal enum HtmlTagParsingModes + { + Native, + TagHelper + } void ClearPooledBuilder() { diff --git a/src/WattleScript.Templating/Parser/TagHelper.cs b/src/WattleScript.Templating/Parser/TagHelper.cs index c6483da8..b18ba57e 100644 --- a/src/WattleScript.Templating/Parser/TagHelper.cs +++ b/src/WattleScript.Templating/Parser/TagHelper.cs @@ -3,10 +3,12 @@ public class TagHelper { public string Name { get; set; } + public string Template { get; set; } - public TagHelper(string name) + public TagHelper(string name, string template) { Name = name; + Template = template; } } diff --git a/src/WattleScript.Templating/TemplatingEngine.cs b/src/WattleScript.Templating/TemplatingEngine.cs index 6e339bbd..23a1f9f4 100644 --- a/src/WattleScript.Templating/TemplatingEngine.cs +++ b/src/WattleScript.Templating/TemplatingEngine.cs @@ -10,17 +10,39 @@ public class TemplatingEngine private readonly TemplatingEngineOptions options; private readonly Script script; private StringBuilder stdOut = new StringBuilder(); - private readonly List tagHelpers; - + internal StringBuilder stdOutTagHelper = new StringBuilder(); + public readonly List tagHelpers; + internal Dictionary tagHelpersMap = new Dictionary(); + internal readonly Script tagHelpersScript; + public TemplatingEngine(Script script, TemplatingEngineOptions? options = null, List? tagHelpers = null) { options ??= TemplatingEngineOptions.Default; this.options = options; this.script = script ?? throw new ArgumentNullException(nameof(script)); this.tagHelpers = tagHelpers ?? new List(); + + foreach (TagHelper th in this.tagHelpers) + { + if (tagHelpersMap.ContainsKey(th.Name.ToLowerInvariant())) + { + // [todo] err, tag helper duplicate name + } + + tagHelpersMap.TryAdd(th.Name.ToLowerInvariant(), th); + } script.Globals["stdout_line"] = PrintLine; script.Globals["stdout"] = Print; + + tagHelpersScript = new Script(CoreModules.Preset_HardSandbox); + tagHelpersScript.Options.IndexTablesFrom = 0; + tagHelpersScript.Options.AnnotationPolicy = new CustomPolicy(AnnotationValueParsingPolicy.ForceTable); + tagHelpersScript.Options.Syntax = ScriptSyntax.WattleScript; + tagHelpersScript.Options.Directives.Add("using"); + + tagHelpersScript.Globals["stdout_line"] = PrintTaghelper; + tagHelpersScript.Globals["stdout"] = PrintTaghelper; } string EncodeJsString(string s) @@ -120,7 +142,7 @@ public string Debug(string code) return ""; } - Parser parser = new Parser(script, tagHelpers); + Parser parser = new Parser(this, script); List tokens = parser.Parse(code); pooledSb.Clear(); @@ -145,7 +167,7 @@ public string Transpile(string code) return ""; } - Parser parser = new Parser(script, tagHelpers); + Parser parser = new Parser(this, script); List tokens = parser.Parse(code); StringBuilder sb = new StringBuilder(); @@ -206,10 +228,51 @@ public async Task ParseTagHelper(string code) IReadOnlyList? annots = dv.Function.Annotations; - //Table ctx = new Table(script); - //await renderFn.Function.CallAsync(ctx); + if (annots == null) + { + // [todo] err, mandatory annot "name" not found + return; + } + + Annotation? nameAnnot = annots.FirstOrDefault(x => x.Name == "name"); + + if (nameAnnot == null) + { + // [todo] err, mandatory annot "name" not found + return; + } + + if (nameAnnot.Value.Type != DataType.Table) + { + // [todo] err, annot not valid, possibly wrong annot mode is used + return; + } + + Table tbl = nameAnnot.Value.Table; + + if (tbl.Length < 1) + { + // [todo] err, annot "name" is empty + return; + } + + DynValue nameDv = tbl.Values.First(); + + if (nameDv.Type != DataType.String) + { + // [todo] err, annot "name" is something else than string + return; + } + + string name = nameDv.String; - //tagHelpers.Add(new TagHelper()); + if (string.IsNullOrWhiteSpace(name)) + { + // [todo] err, annot "name" is empty + return; + } + + tagHelpers.Add(new TagHelper(nameDv.String, transpiledTemplate)); } public async Task Render(string code, Table? globalContext = null, string? friendlyCodeName = null) @@ -233,6 +296,11 @@ private void Print(Script script, CallbackArguments args) stdOut.Append(args[0].CastToString()); } + private void PrintTaghelper(Script script, CallbackArguments args) + { + stdOutTagHelper.Append(args[0].CastToString()); + } + public class RenderResult { public string Output { get; init; } diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index 78589a69..1aca228f 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -14,17 +14,37 @@ namespace WattleScript.Interpreter.Tests.Templating; public class TemplatingTestsRunner { private const string ROOT_FOLDER = "Templating/Tests"; - + private static Filter filter = Filter.Tests; + private List tagHelpers = new List(); + + enum Filter + { + Tests, + TagDefinitions + } + static string[] GetTestCases() { string[] files = Directory.GetFiles(ROOT_FOLDER, "*.wthtml*", SearchOption.AllDirectories); - return files; + + if (filter == Filter.TagDefinitions) + { + return files.Where(x => x.Contains("TagDefinitions")).ToArray(); + } + + if (filter == Filter.Tests) + { + return files.Where(x => !x.Contains("TagDefinitions")).ToArray(); + } + + return Array.Empty(); } [OneTimeSetUp] public async Task Init() { - foreach (string path in GetTestCases().Where(x => x.Contains("TagDefinitions"))) + filter = Filter.TagDefinitions; + foreach (string path in GetTestCases()) { string code = await File.ReadAllTextAsync(path); @@ -35,16 +55,19 @@ public async Task Init() script.Options.Directives.Add("using"); TemplatingEngine tmp = new TemplatingEngine(script); - + try { await tmp.ParseTagHelper(code); + tagHelpers.AddRange(tmp.tagHelpers); } catch (Exception e) { Assert.Fail($"Error parsing tag helper definition\nPath: {path}\nMessage: {e.Message}\nStacktrace: {e.StackTrace}"); } } + + filter = Filter.Tests; } [Test, TestCaseSource(nameof(GetTestCases))] @@ -57,12 +80,6 @@ public async Task RunCore(string path, bool reportErrors = false) { string outputPath = path.Replace(".wthtml", ".html"); - if (outputPath.Contains("TagDefinitions")) - { - Assert.Pass($"Tag definition (test skipped)"); - return; - } - if (!File.Exists(outputPath)) { Assert.Inconclusive($"Missing output file for test {path}"); @@ -78,7 +95,7 @@ public async Task RunCore(string path, bool reportErrors = false) script.Options.Syntax = ScriptSyntax.WattleScript; script.Options.Directives.Add("using"); - TemplatingEngine tmp = new TemplatingEngine(script); + TemplatingEngine tmp = new TemplatingEngine(script, null, tagHelpers); TemplatingEngine.RenderResult rr = null; if (path.Contains("flaky")) diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.html index c110e59a..ff3bfed7 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.html +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.html @@ -1 +1 @@ - \ No newline at end of file +
My tag!
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.wthtml index ff3bfed7..c110e59a 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.wthtml +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/1-simple.wthtml @@ -1 +1 @@ -
My tag!
\ No newline at end of file + \ No newline at end of file From fa988150a047553f335e1ef1850fea1ebdbb66d4 Mon Sep 17 00:00:00 2001 From: lofcz Date: Fri, 29 Apr 2022 22:04:04 +0200 Subject: [PATCH 43/74] Add support for basic tag helpers with content --- src/WattleScript.Templating/Parser/Node.cs | 2 ++ src/WattleScript.Templating/Parser/Parser.cs | 21 +++++++++++++----- .../Parser/ParserKeywords.cs | 22 ++++++++++++++++++- .../Parser/ParserUtils.cs | 5 +++++ .../Tests/TagHelpers/2-content.html | 1 + .../Tests/TagHelpers/2-content.wthtml | 1 + .../TagDefinitions/contenttag.wthtml | 7 ++++++ .../TagHelpers/TagDefinitions/mytag.wthtml | 2 +- 8 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/2-content.html create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/2-content.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml diff --git a/src/WattleScript.Templating/Parser/Node.cs b/src/WattleScript.Templating/Parser/Node.cs index d32615fb..44633504 100644 --- a/src/WattleScript.Templating/Parser/Node.cs +++ b/src/WattleScript.Templating/Parser/Node.cs @@ -31,6 +31,8 @@ internal class NodeBase public bool Recovery { get; set; } public int Line { get; set; } public int Col { get; set; } + public int ContentFrom { get; set; } + public int ContentTo { get; set; } } internal class ServerNode : NodeBase diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 76cd2a94..79ede3db 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -52,7 +52,6 @@ enum StepModes private List recoveredExceptions = new List(); private TemplatingEngine engine; private HtmlTagParsingModes tagParsingMode = HtmlTagParsingModes.Native; - private int tagHelperContentStartStored = 0; public Parser(TemplatingEngine engine, Script? script) { @@ -1067,12 +1066,15 @@ bool ParseTagHelper(HtmlElement el) break; } } - + TagHelper helper = engine.tagHelpersMap[el.Name.ToLowerInvariant()]; + int contentFrom = el.ContentFrom; + int contentTo = el.ContentTo; + string contentStr = contentTo > 0 ? source.Substring(contentFrom, contentTo - contentFrom) : ""; Table ctxTable = new Table(engine.tagHelpersScript); - ctxTable.Set(0, "test"); - + ctxTable.Set(0, contentStr); + engine.tagHelpersScript.DoString(helper.Template); engine.tagHelpersScript.Globals.Get("Render").Function.Call(ctxTable); @@ -1205,9 +1207,17 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el) bool parseContent = false; string tagText = GetCurrentLexeme(); + if (el != null) + { + if (!startsFromClosingTag) + { + el.ContentFrom = pos; + } + } + if (tagParsingMode == HtmlTagParsingModes.TagHelper) { - tagHelperContentStartStored = pos; + DiscardCurrentLexeme(); } if (!startsFromClosingTag) @@ -1368,6 +1378,7 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName, HtmlElement el) if (string.Equals(openingTagName, closingNameLookahead, StringComparison.InvariantCultureIgnoreCase)) { + el.ContentTo = pos; str = GetCurrentLexeme(); string closingNameParsed = ParseHtmlClosingTag(openingTagName, el, false); diff --git a/src/WattleScript.Templating/Parser/ParserKeywords.cs b/src/WattleScript.Templating/Parser/ParserKeywords.cs index 1b786847..cc657a83 100644 --- a/src/WattleScript.Templating/Parser/ParserKeywords.cs +++ b/src/WattleScript.Templating/Parser/ParserKeywords.cs @@ -1,4 +1,6 @@ -namespace WattleScript.Templating; +using WattleScript.Interpreter; + +namespace WattleScript.Templating; internal partial class Parser { @@ -41,6 +43,24 @@ bool ParseKeywordSwitch() return ParseGenericBrkKeywordWithBlock("switch"); } + // tagContent + // parser has to be positioned after "tagContent" + bool ParseKeywordTagContent() + { + if (script != null) + { + DynValue dv = engine.tagHelpersScript.Globals.Get("__templatingEngineTagContent"); + if (dv.IsNil()) + { + return false; + } + + + } + + return true; + } + // do {} while () // parser has to be positioned after "do", either at opening { or at a whitespace preceding it bool ParseKeywordDo() diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index 1e19d34b..85b5df94 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -240,6 +240,11 @@ bool AddTokenSplitRightTrim(TokenTypes lhsType, TokenTypes rhsType) bool AddToken(TokenTypes type) { + if (tagParsingMode == HtmlTagParsingModes.TagHelper) + { + return false; + } + if (currentLexeme.Length == 0) { return false; diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/2-content.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/2-content.html new file mode 100644 index 00000000..4aa73269 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/2-content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/2-content.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/2-content.wthtml new file mode 100644 index 00000000..cc6214a1 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/2-content.wthtml @@ -0,0 +1 @@ +

title

\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml new file mode 100644 index 00000000..de35de9e --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml @@ -0,0 +1,7 @@ +@{ + @@name("content-tag") + + function Render(ctx) { + + } +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/mytag.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/mytag.wthtml index 6dc6cbef..2d8f8336 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/mytag.wthtml +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/mytag.wthtml @@ -1,7 +1,7 @@ @{ @@name("my-tag") - function Render(context) { + function Render(ctx) {
My tag!
} } \ No newline at end of file From f311863fcc81f7e9f8fe88539f3484747e93e48a Mon Sep 17 00:00:00 2001 From: lofcz Date: Sat, 30 Apr 2022 16:57:50 +0200 Subject: [PATCH 44/74] Add support for block exprs in tag helpers --- src/WattleScript.Templating/Parser/Parser.cs | 31 +++++++++++++--- .../Parser/ParserKeywords.cs | 18 --------- .../TemplatingEngine.cs | 37 +++++++++++++------ .../Tests/TagHelpers/3-content-server.html | 3 ++ .../Tests/TagHelpers/3-content-server.wthtml | 9 +++++ .../TagDefinitions/contenttag.wthtml | 2 +- 6 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.html create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 79ede3db..f03442e8 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -1072,18 +1072,32 @@ bool ParseTagHelper(HtmlElement el) int contentTo = el.ContentTo; string contentStr = contentTo > 0 ? source.Substring(contentFrom, contentTo - contentFrom) : ""; - Table ctxTable = new Table(engine.tagHelpersScript); - ctxTable.Set(0, contentStr); + Table ctxTable = new Table(engine.script); + ctxTable.Set(0, new TemplatingEngine(script).Transpile(contentStr)); + + script.Globals["stdout"] = engine.PrintTaghelper; + engine.stdOutTagHelper.Clear(); + + // 2. before resolving tag helper, we need to resolve the part of template currently transpiled + string pendingTemplate = engine.Transform(Tokens); + + DynValue pVal = engine.script.LoadString(pendingTemplate); + engine.script.Call(pVal); + + Tokens.Clear(); - engine.tagHelpersScript.DoString(helper.Template); - engine.tagHelpersScript.Globals.Get("Render").Function.Call(ctxTable); + engine.script.DoString(helper.Template); + engine.script.Globals.Get("Render").Function.Call(ctxTable); string tagOutput = engine.stdOutTagHelper.ToString(); + + script.Globals["stdout"] = engine.Print; + currentLexeme.Append(tagOutput); AddToken(TokenTypes.Text); tagParsingMode = HtmlTagParsingModes.Native; - + return true; } @@ -1380,7 +1394,12 @@ void ParseHtmlOrPlaintextUntilClosingTag(string openingTagName, HtmlElement el) { el.ContentTo = pos; str = GetCurrentLexeme(); - + + if (tagParsingMode == HtmlTagParsingModes.TagHelper) + { + StorePos(); + } + string closingNameParsed = ParseHtmlClosingTag(openingTagName, el, false); AddToken(TokenTypes.Text); openElements.Pop(); diff --git a/src/WattleScript.Templating/Parser/ParserKeywords.cs b/src/WattleScript.Templating/Parser/ParserKeywords.cs index cc657a83..98f0d07c 100644 --- a/src/WattleScript.Templating/Parser/ParserKeywords.cs +++ b/src/WattleScript.Templating/Parser/ParserKeywords.cs @@ -42,25 +42,7 @@ bool ParseKeywordSwitch() { return ParseGenericBrkKeywordWithBlock("switch"); } - - // tagContent - // parser has to be positioned after "tagContent" - bool ParseKeywordTagContent() - { - if (script != null) - { - DynValue dv = engine.tagHelpersScript.Globals.Get("__templatingEngineTagContent"); - if (dv.IsNil()) - { - return false; - } - - - } - return true; - } - // do {} while () // parser has to be positioned after "do", either at opening { or at a whitespace preceding it bool ParseKeywordDo() diff --git a/src/WattleScript.Templating/TemplatingEngine.cs b/src/WattleScript.Templating/TemplatingEngine.cs index 23a1f9f4..ed207bcd 100644 --- a/src/WattleScript.Templating/TemplatingEngine.cs +++ b/src/WattleScript.Templating/TemplatingEngine.cs @@ -8,12 +8,11 @@ public class TemplatingEngine { private readonly StringBuilder pooledSb = new StringBuilder(); private readonly TemplatingEngineOptions options; - private readonly Script script; + internal readonly Script script; private StringBuilder stdOut = new StringBuilder(); internal StringBuilder stdOutTagHelper = new StringBuilder(); public readonly List tagHelpers; internal Dictionary tagHelpersMap = new Dictionary(); - internal readonly Script tagHelpersScript; public TemplatingEngine(Script script, TemplatingEngineOptions? options = null, List? tagHelpers = null) { @@ -34,15 +33,23 @@ public TemplatingEngine(Script script, TemplatingEngineOptions? options = null, script.Globals["stdout_line"] = PrintLine; script.Globals["stdout"] = Print; + script.Globals["render_tag_content"] = RenderTagContent; + } + + DynValue RenderTagContent(Script s, CallbackArguments args) + { + if (args.Count > 0) + { + DynValue arg = args[0]; + string str = arg.String; + + script.DoString(str); - tagHelpersScript = new Script(CoreModules.Preset_HardSandbox); - tagHelpersScript.Options.IndexTablesFrom = 0; - tagHelpersScript.Options.AnnotationPolicy = new CustomPolicy(AnnotationValueParsingPolicy.ForceTable); - tagHelpersScript.Options.Syntax = ScriptSyntax.WattleScript; - tagHelpersScript.Options.Directives.Add("using"); + string output = stdOutTagHelper.ToString(); + return DynValue.NewString(output); + } - tagHelpersScript.Globals["stdout_line"] = PrintTaghelper; - tagHelpersScript.Globals["stdout"] = PrintTaghelper; + return DynValue.Nil; } string EncodeJsString(string s) @@ -170,6 +177,12 @@ public string Transpile(string code) Parser parser = new Parser(this, script); List tokens = parser.Parse(code); + string str = Transform(tokens); + return str; + } + + internal string Transform(List tokens) + { StringBuilder sb = new StringBuilder(); bool firstClientPending = true; @@ -291,16 +304,16 @@ private void PrintLine(Script script, CallbackArguments args) stdOut.AppendLine(args[0].CastToString()); } - private void Print(Script script, CallbackArguments args) + public void Print(Script script, CallbackArguments args) { stdOut.Append(args[0].CastToString()); } - private void PrintTaghelper(Script script, CallbackArguments args) + public void PrintTaghelper(Script script, CallbackArguments args) { stdOutTagHelper.Append(args[0].CastToString()); } - + public class RenderResult { public string Output { get; init; } diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.html new file mode 100644 index 00000000..1cbab12e --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.html @@ -0,0 +1,3 @@ + +
i: 10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.wthtml new file mode 100644 index 00000000..2805cb65 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.wthtml @@ -0,0 +1,9 @@ +@{ + i = 0; +} + + @{ + i = 10 + } + +
i: @i
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml index de35de9e..125f4f31 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml @@ -2,6 +2,6 @@ @@name("content-tag") function Render(ctx) { - + } } \ No newline at end of file From a06cad9ce11e42a9ca8f9994865ac9be81024a2e Mon Sep 17 00:00:00 2001 From: lofcz Date: Sat, 30 Apr 2022 21:06:50 +0200 Subject: [PATCH 45/74] Fix handling of empty strings in server side code --- src/WattleScript.Templating/Parser/Node.cs | 9 ++++++- src/WattleScript.Templating/Parser/Parser.cs | 27 ++++++++++++++----- .../Templating/TemplatingTestsRunner.cs | 7 +++++ .../TagDefinitions/contenttag.wthtml | 2 +- .../TagDefinitions/radioButtons.wthtml | 10 +++++++ 5 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml diff --git a/src/WattleScript.Templating/Parser/Node.cs b/src/WattleScript.Templating/Parser/Node.cs index 44633504..6b70d9e6 100644 --- a/src/WattleScript.Templating/Parser/Node.cs +++ b/src/WattleScript.Templating/Parser/Node.cs @@ -69,7 +69,7 @@ internal enum ClosingType } public HtmlElement Parent { get; set; } - public List Attributes { get; set; } + public List Attributes { get; set; } = new List(); public string Name { get; set; } public ClosingType Closing { get; set; } public bool ForceNativeTag { get; set; } @@ -93,4 +93,11 @@ internal enum HtmlAttributeQuoteType public string Name { get; set; } public string Value { get; set; } public HtmlAttributeQuoteType QuoteType { get; set; } + + internal HtmlAttribute(string name, string value, HtmlAttributeQuoteType quoteType) + { + Name = name; + Value = value; + QuoteType = quoteType; + } } \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index f03442e8..ec465394 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -137,10 +137,13 @@ void HandleStringSequence(char chr) if (c == '\'') { HandleStringSequence('\''); + continue; } - else if (c == '"') + + if (c == '"') { HandleStringSequence('"'); + continue; } } @@ -814,9 +817,10 @@ bool InSpecialSequence() void HandleStringSequence(char chr) { - Step(); + char stepC = Step(); string str = GetCurrentLexeme(); - if (LastStoredCharMatches(2, '\\')) // check that string symbol is not escaped + + if (LastStoredCharMatches(1, '\\')) // check that string symbol is not escaped { return; } @@ -844,14 +848,19 @@ void HandleStringSequence(char chr) if (Peek() == '\'') { HandleStringSequence('\''); + continue; } - else if (Peek() == '"') + + if (Peek() == '"') { HandleStringSequence('"'); + continue; } - else if (Peek() == '`') + + if (Peek() == '`') { HandleStringSequence('`'); + continue; } } @@ -1073,7 +1082,12 @@ bool ParseTagHelper(HtmlElement el) string contentStr = contentTo > 0 ? source.Substring(contentFrom, contentTo - contentFrom) : ""; Table ctxTable = new Table(engine.script); - ctxTable.Set(0, new TemplatingEngine(script).Transpile(contentStr)); + ctxTable.Set("content", DynValue.NewString(new TemplatingEngine(script).Transpile(contentStr))); + + Table attrTable = new Table(engine.script); + + + ctxTable.Set("attributes", DynValue.NewTable(attrTable)); script.Globals["stdout"] = engine.PrintTaghelper; engine.stdOutTagHelper.Clear(); @@ -1190,6 +1204,7 @@ bool ParseAttribute(string tagName, bool startsFromClosingTag, HtmlElement el) { Step(); string val = ParseAttributeValue(); + el.Attributes.Add(new HtmlAttribute(name, val, HtmlAttribute.HtmlAttributeQuoteType.Double)); } if (LookaheadForClosingTag()) diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index 1aca228f..e0a152e2 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -13,6 +13,8 @@ namespace WattleScript.Interpreter.Tests.Templating; [Parallelizable(ParallelScope.All)] public class TemplatingTestsRunner { + private const bool COMPILE_TAG_HELPERS = true; + private const string ROOT_FOLDER = "Templating/Tests"; private static Filter filter = Filter.Tests; private List tagHelpers = new List(); @@ -43,6 +45,11 @@ static string[] GetTestCases() [OneTimeSetUp] public async Task Init() { + if (!COMPILE_TAG_HELPERS) + { + return; + } + filter = Filter.TagDefinitions; foreach (string path in GetTestCases()) { diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml index 125f4f31..e8794e34 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/contenttag.wthtml @@ -2,6 +2,6 @@ @@name("content-tag") function Render(ctx) { - + } } \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml new file mode 100644 index 00000000..57922e1c --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml @@ -0,0 +1,10 @@ +@{ + @@name("radio-buttons") + + function Render(ctx) { + + x = ""; + + + } +} \ No newline at end of file From 041d4839dc4b85098b8b6845dee13c88e732dbcf Mon Sep 17 00:00:00 2001 From: lofcz Date: Sat, 30 Apr 2022 21:18:26 +0200 Subject: [PATCH 46/74] Add support for accessing attributes in tag helpers --- src/WattleScript.Templating/Parser/Parser.cs | 5 ++++- .../Templating/Tests/TagHelpers/4-attr.html | 1 + .../Templating/Tests/TagHelpers/4-attr.wthtml | 3 +++ .../Tests/TagHelpers/TagDefinitions/radioButtons.wthtml | 4 ++-- 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/4-attr.html create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/4-attr.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index ec465394..47ead457 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -1085,7 +1085,10 @@ bool ParseTagHelper(HtmlElement el) ctxTable.Set("content", DynValue.NewString(new TemplatingEngine(script).Transpile(contentStr))); Table attrTable = new Table(engine.script); - + foreach (HtmlAttribute attr in el.Attributes) + { + attrTable.Set(attr.Name, DynValue.NewString(attr.Value)); + } ctxTable.Set("attributes", DynValue.NewTable(attrTable)); diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/4-attr.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/4-attr.html new file mode 100644 index 00000000..9e7c6fd0 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/4-attr.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/4-attr.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/4-attr.wthtml new file mode 100644 index 00000000..2a56ac96 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/4-attr.wthtml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml index 57922e1c..33278be9 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml @@ -3,8 +3,8 @@ function Render(ctx) { - x = ""; + var name = ctx["attributes"]["name"]; - +
@render_tag_content(ctx["content"])
} } \ No newline at end of file From 2a46b00d8235758fa0f57bbcd12ee26dbcd25b2e Mon Sep 17 00:00:00 2001 From: lofcz Date: Sun, 1 May 2022 03:13:55 +0200 Subject: [PATCH 47/74] Support shared context between tag helpers --- src/WattleScript.Templating/Parser/Parser.cs | 95 ++++++++++++++----- .../TemplatingEngine.cs | 83 +++++++++++++--- .../Templating/TemplatingTestsRunner.cs | 21 +++- .../Tests/TagHelpers/3-content-server.html | 3 +- .../Tests/TagHelpers/3-content-server.wthtml | 3 +- .../Tests/TagHelpers/5-shared-context.html | 4 + .../Tests/TagHelpers/5-shared-context.wthtml | 5 + .../Templating/Tests/TagHelpers/6-ignore.html | 1 + .../Tests/TagHelpers/6-ignore.wthtml | 10 ++ .../TagHelpers/TagDefinitions/ignore.wthtml | 7 ++ .../TagDefinitions/radioButton.wthtml | 11 +++ .../TagDefinitions/radioButtons.wthtml | 1 + 12 files changed, 199 insertions(+), 45 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/5-shared-context.html create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/5-shared-context.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/6-ignore.html create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/6-ignore.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/ignore.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButton.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 47ead457..fc3b1bbb 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -52,11 +52,14 @@ enum StepModes private List recoveredExceptions = new List(); private TemplatingEngine engine; private HtmlTagParsingModes tagParsingMode = HtmlTagParsingModes.Native; + internal Table tagHelpersSharedTable; + private string friendlyName; - public Parser(TemplatingEngine engine, Script? script) + public Parser(TemplatingEngine engine, Script? script, Table? tagHelpersSharedTable) { this.engine = engine; this.script = script; + this.tagHelpersSharedTable = tagHelpersSharedTable ?? new Table(this.script); document = new Document(); KeywordsMap = new Dictionary?> @@ -70,9 +73,10 @@ public Parser(TemplatingEngine engine, Script? script) }; } - public List Parse(string templateSource) + public List Parse(string templateSource, string friendlyName) { source = templateSource; + this.friendlyName = friendlyName; ParseClient(); //Lookahead(); @@ -1024,8 +1028,13 @@ bool ParseHtmlTag(string? parentTagName) string tagName = ParseHtmlTagName(false); el.Name = tagName; openElements.Push(el); + + if (friendlyName != "tagHelperDefinition") + { + + } - if (engine.tagHelpersMap.ContainsKey(tagName.ToLowerInvariant())) + if (tagParsingMode == HtmlTagParsingModes.Native && engine.tagHelpersMap.ContainsKey(tagName.ToLowerInvariant())) { return ParseTagHelper(el); } @@ -1040,7 +1049,7 @@ bool ParseHtmlTag(string? parentTagName) continue; } - bool shouldEnd = LookaheadForAttributeOrClose(tagName, false, el); + bool shouldEnd = LookaheadForAttributeOrClose(tagName, false, el, true); if (shouldEnd) { break; @@ -1050,16 +1059,30 @@ bool ParseHtmlTag(string? parentTagName) return true; } + int ScanForElementClosingTag(HtmlElement el) + { + ParseHtmlOrPlaintextUntilClosingTag(el.Name, el); + + return el.ContentTo; + } + bool ParseTagHelper(HtmlElement el) { // parser is located after name in a tag /* * 1. we parse opening tag as normal, */ + + if (friendlyName != "tagHelperDefinition") + { + string str = GetCurrentLexeme(); + } + DiscardCurrentLexeme(); tagParsingMode = HtmlTagParsingModes.TagHelper; - + + // 1. parse until end of opening tag-helper tag ParseWhitespaceAndNewlines(Sides.Client); while (!IsAtEnd()) { @@ -1069,20 +1092,31 @@ bool ParseTagHelper(HtmlElement el) continue; } - bool shouldEnd = LookaheadForAttributeOrClose(el.Name, false, el); + bool shouldEnd = LookaheadForAttributeOrClose(el.Name, false, el, false); if (shouldEnd) { break; } } + + // 2. if tag-helper is not self closing, scan for end of its content + //if (el.Closing != HtmlElement.ClosingType.SelfClosing) + { + el.ContentTo = ScanForElementClosingTag(el); + } + + DynValue tagHelpersDataDv = DynValue.NewTable(tagHelpersSharedTable); + + Table ctxTable = new Table(engine.script); + ctxTable.Set("data", tagHelpersDataDv); TagHelper helper = engine.tagHelpersMap[el.Name.ToLowerInvariant()]; int contentFrom = el.ContentFrom; int contentTo = el.ContentTo; string contentStr = contentTo > 0 ? source.Substring(contentFrom, contentTo - contentFrom) : ""; - - Table ctxTable = new Table(engine.script); - ctxTable.Set("content", DynValue.NewString(new TemplatingEngine(script).Transpile(contentStr))); + + engine.script.Globals.Set("__tagData", tagHelpersDataDv); + ctxTable.Set("content", DynValue.NewString(contentStr)); Table attrTable = new Table(engine.script); foreach (HtmlAttribute attr in el.Attributes) @@ -1092,10 +1126,10 @@ bool ParseTagHelper(HtmlElement el) ctxTable.Set("attributes", DynValue.NewTable(attrTable)); - script.Globals["stdout"] = engine.PrintTaghelper; - engine.stdOutTagHelper.Clear(); + //engine.script.Globals["stdout"] = engine.PrintTaghelperTmp; + //engine.stdOutTagHelperTmp.Clear(); - // 2. before resolving tag helper, we need to resolve the part of template currently transpiled + // 3. before resolving tag helper, we need to resolve the part of template currently transpiled string pendingTemplate = engine.Transform(Tokens); DynValue pVal = engine.script.LoadString(pendingTemplate); @@ -1103,30 +1137,35 @@ bool ParseTagHelper(HtmlElement el) Tokens.Clear(); + engine.script.DoString(helper.Template); engine.script.Globals.Get("Render").Function.Call(ctxTable); string tagOutput = engine.stdOutTagHelper.ToString(); - script.Globals["stdout"] = engine.Print; + engine.script.Globals["stdout"] = engine.Print; + + // tag output is already in stdout + //currentLexeme.Append(tagOutput); + //AddToken(TokenTypes.Text); - currentLexeme.Append(tagOutput); - - AddToken(TokenTypes.Text); tagParsingMode = HtmlTagParsingModes.Native; + // 4. continue from the content + //ParseHtmlOrPlaintextUntilClosingTag(el.Name, el); + return true; } - bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag, HtmlElement? el) + bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag, HtmlElement? el, bool parseContent) { if (LookaheadForClosingTag()) { - CloseTag(tagName, startsFromClosingTag, el); + CloseTag(tagName, startsFromClosingTag, el, parseContent); return true; } - return ParseAttribute(tagName, startsFromClosingTag, el); + return ParseAttribute(tagName, startsFromClosingTag, el, parseContent); } string ParseAttributeName() @@ -1199,7 +1238,7 @@ string ParseAttributeValue() } - bool ParseAttribute(string tagName, bool startsFromClosingTag, HtmlElement el) + bool ParseAttribute(string tagName, bool startsFromClosingTag, HtmlElement el, bool parseTagContent) { string name = ParseAttributeName(); @@ -1212,14 +1251,14 @@ bool ParseAttribute(string tagName, bool startsFromClosingTag, HtmlElement el) if (LookaheadForClosingTag()) { - CloseTag(tagName, startsFromClosingTag, el); + CloseTag(tagName, startsFromClosingTag, el, parseTagContent); return true; } return false; } - bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el) + bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el, bool parseTagContent) { if (tagName == "html") { @@ -1230,6 +1269,11 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el) { Step(); Step(); + + if (el != null) + { + el.Closing = HtmlElement.ClosingType.SelfClosing; + } } else if (Peek() == '>') { @@ -1251,6 +1295,11 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el) { DiscardCurrentLexeme(); } + + if (!parseTagContent) + { + return true; + } if (!startsFromClosingTag) { @@ -1334,7 +1383,7 @@ string ParseHtmlClosingTag(string openingTagName, HtmlElement? el, bool inBuffer continue; } - bool shouldEnd = LookaheadForAttributeOrClose(closingTagName, true, el); + bool shouldEnd = LookaheadForAttributeOrClose(closingTagName, true, el, true); if (shouldEnd) { break; diff --git a/src/WattleScript.Templating/TemplatingEngine.cs b/src/WattleScript.Templating/TemplatingEngine.cs index ed207bcd..a1f9a769 100644 --- a/src/WattleScript.Templating/TemplatingEngine.cs +++ b/src/WattleScript.Templating/TemplatingEngine.cs @@ -13,6 +13,24 @@ public class TemplatingEngine internal StringBuilder stdOutTagHelper = new StringBuilder(); public readonly List tagHelpers; internal Dictionary tagHelpersMap = new Dictionary(); + internal StringBuilder stdOutTagHelperTmp = new StringBuilder(); + private Parser parser; + private Table? tagHelpersSharedTbl = null; + + public TemplatingEngine(TemplatingEngine parent, Table? tbl) + { + options = parent.options; + script = parent.script; + tagHelpers = parent.tagHelpers; + + stdOut = parent.stdOut; + stdOutTagHelper = parent.stdOutTagHelper; + stdOutTagHelperTmp = parent.stdOutTagHelperTmp; + + tagHelpersSharedTbl = tbl; + + SharedSetup(); + } public TemplatingEngine(Script script, TemplatingEngineOptions? options = null, List? tagHelpers = null) { @@ -21,7 +39,25 @@ public TemplatingEngine(Script script, TemplatingEngineOptions? options = null, this.script = script ?? throw new ArgumentNullException(nameof(script)); this.tagHelpers = tagHelpers ?? new List(); - foreach (TagHelper th in this.tagHelpers) + SharedSetup(); + } + + void SharedSetup() + { + MapTagHelpers(); + SetSpecials(); + } + + void SetSpecials() + { + script.Globals["stdout_line"] = PrintLine; + script.Globals["stdout"] = Print; + script.Globals["render_tag_content"] = RenderTagContent; + } + + void MapTagHelpers() + { + foreach (TagHelper th in tagHelpers) { if (tagHelpersMap.ContainsKey(th.Name.ToLowerInvariant())) { @@ -30,22 +66,32 @@ public TemplatingEngine(Script script, TemplatingEngineOptions? options = null, tagHelpersMap.TryAdd(th.Name.ToLowerInvariant(), th); } - - script.Globals["stdout_line"] = PrintLine; - script.Globals["stdout"] = Print; - script.Globals["render_tag_content"] = RenderTagContent; } DynValue RenderTagContent(Script s, CallbackArguments args) { if (args.Count > 0) { + Table? tbl= null; + + if (parser != null) + { + tbl = script.Globals.Get("__tagData").Table; + parser.tagHelpersSharedTable = tbl; + } + DynValue arg = args[0]; string str = arg.String; - - script.DoString(str); + string transpiled = new TemplatingEngine(this, tbl).Transpile(str); - string output = stdOutTagHelper.ToString(); + stdOutTagHelperTmp.Clear(); + script.Globals["stdout"] = PrintTaghelperTmp; + script.DoString(transpiled); + + string output = stdOutTagHelperTmp.ToString(); + stdOutTagHelper.Append(output); + + script.Globals["stdout"] = Print; return DynValue.NewString(output); } @@ -149,8 +195,8 @@ public string Debug(string code) return ""; } - Parser parser = new Parser(this, script); - List tokens = parser.Parse(code); + parser = new Parser(this, script, tagHelpersSharedTbl); + List tokens = parser.Parse(code, ""); pooledSb.Clear(); if (options.Optimise) @@ -167,15 +213,15 @@ public string Debug(string code) return finalText; } - public string Transpile(string code) + public string Transpile(string code, string friendlyName = "") { if (string.IsNullOrWhiteSpace(code)) { return ""; } - Parser parser = new Parser(this, script); - List tokens = parser.Parse(code); + parser = new Parser(this, script, tagHelpersSharedTbl); + List tokens = parser.Parse(code, friendlyName); string str = Transform(tokens); return str; @@ -228,7 +274,7 @@ public async Task ParseTagHelper(string code) { stdOut.Clear(); - string transpiledTemplate = Transpile(code); + string transpiledTemplate = Transpile(code, "tagHelperDefinition"); DynValue dv = script.LoadString(transpiledTemplate); FunctionProto? renderFn = dv.Function.Function.Functions.FirstOrDefault(x => x.Name == "Render"); @@ -311,7 +357,14 @@ public void Print(Script script, CallbackArguments args) public void PrintTaghelper(Script script, CallbackArguments args) { - stdOutTagHelper.Append(args[0].CastToString()); + string str = args[0].CastToString(); + stdOutTagHelper.Append(str); + } + + public void PrintTaghelperTmp(Script script, CallbackArguments args) + { + string str = args[0].CastToString(); + stdOutTagHelperTmp.Append(str); } public class RenderResult diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index e0a152e2..f8ad0d72 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -86,6 +86,7 @@ public async Task RunThrowErros(string path) public async Task RunCore(string path, bool reportErrors = false) { string outputPath = path.Replace(".wthtml", ".html"); + bool throwOnAe = true; if (!File.Exists(outputPath)) { @@ -126,17 +127,31 @@ public async Task RunCore(string path, bool reportErrors = false) try { rr = await tmp.Render(code); - Assert.AreEqual(output, rr.Output, $"Test {path} did not pass."); + + if (string.Equals(output, rr.Output)) + { + Assert.Pass(); + } + else + { + throwOnAe = false; + Assert.Fail($"Test failed. Output and expected HTML are not equal.\n---------------------- Expected ----------------------\n{output}\n---------------------- But was------------------------\n{rr.Output}\n------------------------------------------------------\n"); + } if (path.ToLowerInvariant().Contains("invalid")) { Assert.Fail("Expected to crash but 'passed'"); } - string debugStr = tmp.Debug(code); + //string debugStr = tmp.Debug(code); } catch (Exception e) { + if (e is SuccessException) + { + return; + } + if (path.ToLowerInvariant().Contains("invalid")) { Assert.Pass($"Crashed as expected: {e.Message}"); @@ -145,7 +160,7 @@ public async Task RunCore(string path, bool reportErrors = false) if (e is AssertionException ae) { - Assert.Fail($"Test {path} did not pass.\nMessage: {ae.Message}\n{ae.StackTrace}\nParsed template:\n{rr?.Transpiled ?? ""}"); + Assert.Fail($"---------------------- Parsed template ----------------------\n{rr?.Transpiled ?? ""}\n---------------------- Stacktrace ----------------------\n{ae.StackTrace}\n"); return; } diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.html index 1cbab12e..4ef2cb56 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.html +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.html @@ -1,3 +1,2 @@ -
i: 10
\ No newline at end of file +
i: 10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.wthtml index 2805cb65..4935e329 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.wthtml +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/3-content-server.wthtml @@ -5,5 +5,4 @@ @{ i = 10 } - -
i: @i
\ No newline at end of file +
i: @i
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/5-shared-context.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/5-shared-context.html new file mode 100644 index 00000000..51997757 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/5-shared-context.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/5-shared-context.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/5-shared-context.wthtml new file mode 100644 index 00000000..7828d325 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/5-shared-context.wthtml @@ -0,0 +1,5 @@ + + Good + Average + Bad + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/6-ignore.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/6-ignore.html new file mode 100644 index 00000000..751ce8eb --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/6-ignore.html @@ -0,0 +1 @@ +I value is: 0 \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/6-ignore.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/6-ignore.wthtml new file mode 100644 index 00000000..4af632d3 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/6-ignore.wthtml @@ -0,0 +1,10 @@ +@{ + i = 0 +} + + @{ + i = 10 + } + + +I value is: @i \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/ignore.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/ignore.wthtml new file mode 100644 index 00000000..ac18a889 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/ignore.wthtml @@ -0,0 +1,7 @@ +@{ + @@name("ignore") + + function Render(ctx) { + + } +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButton.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButton.wthtml new file mode 100644 index 00000000..60b227bd --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButton.wthtml @@ -0,0 +1,11 @@ +@{ + @@name("radio-button") + + function Render(ctx) { + + var name = ctx["data"]["radio-name"]; + + + + } +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml index 33278be9..81779f7a 100644 --- a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/radioButtons.wthtml @@ -4,6 +4,7 @@ function Render(ctx) { var name = ctx["attributes"]["name"]; + ctx["data"]["radio-name"] = name;
@render_tag_content(ctx["content"])
} From f177d95127e42923c0cc9aac1e95a11564f398bb Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 3 May 2022 21:58:50 +0200 Subject: [PATCH 48/74] Improve crashes on banned keywords @else, @elseif --- src/WattleScript.Templating/Parser/Parser.cs | 2 + .../Parser/ParserKeywords.cs | 14 ++ .../Templating/TemplatingTestsRunner.cs | 28 ++- .../Templating/Tests/Html/14-svg.html | 199 +++++++++++++++++ .../Templating/Tests/Html/14-svg.wthtml | 201 ++++++++++++++++++ .../Templating/Tests/Html/15-mathml.html | 36 ++++ .../Templating/Tests/Html/15-mathml.wthtml | 38 ++++ .../BannedKeyword/1-else-invalid.html | 0 .../BannedKeyword/1-else-invalid.wthtml | 3 + 9 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/14-svg.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/14-svg.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/15-mathml.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/15-mathml.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/1-else-invalid.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/1-else-invalid.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index fc3b1bbb..7d1f1406 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -70,6 +70,8 @@ public Parser(TemplatingEngine engine, Script? script, Table? tagHelpersSharedTa { "do", ParseKeywordDo }, { "function", ParseKeywordFunction }, { "switch", ParseKeywordSwitch }, + { "else", ParseKeywordInvalidElse }, + { "elseif", ParseKeywordInvalidElseIf }, }; } diff --git a/src/WattleScript.Templating/Parser/ParserKeywords.cs b/src/WattleScript.Templating/Parser/ParserKeywords.cs index 98f0d07c..d332a43a 100644 --- a/src/WattleScript.Templating/Parser/ParserKeywords.cs +++ b/src/WattleScript.Templating/Parser/ParserKeywords.cs @@ -4,6 +4,20 @@ namespace WattleScript.Templating; internal partial class Parser { + // @else {} + // parser has to be positioned after else + bool ParseKeywordInvalidElse() + { + return Throw("Found \"@else\". Please remove the leading @. Keywords else, elseif shouldn't be prefixed with @."); + } + + // @elseif {} + // parser has to be positioned after elseif + bool ParseKeywordInvalidElseIf() + { + return Throw("Found \"@elseif\". Please remove the leading @. Keywords else, elseif shouldn't be prefixed with @."); + } + // if (expr) {} // parser has to be positioned after if, either at opening ( or at whitespace before it bool ParseKeywordIf() diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index f8ad0d72..861a7167 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -25,6 +25,28 @@ enum Filter TagDefinitions } + static int strDifIndex(string s1, string s2) + { + int index = 0; + int min = Math.Min(s1.Length, s2.Length); + + while (index < min && s1[index] == s2[index]) + { + index++; + } + + return index == min && s1.Length == s2.Length ? -1 : index; + } + + static string strSnippet(string str, int pivot, int n) + { + int nR = Math.Min(str.Length - pivot, n); + int nL = pivot - n > 0 ? pivot - n : 0; + int tL = pivot - n > 0 ? n : n - pivot; + + return $"{str.Substring(nL, tL)}{str.Substring(pivot, nR)}"; + } + static string[] GetTestCases() { string[] files = Directory.GetFiles(ROOT_FOLDER, "*.wthtml*", SearchOption.AllDirectories); @@ -135,7 +157,11 @@ public async Task RunCore(string path, bool reportErrors = false) else { throwOnAe = false; - Assert.Fail($"Test failed. Output and expected HTML are not equal.\n---------------------- Expected ----------------------\n{output}\n---------------------- But was------------------------\n{rr.Output}\n------------------------------------------------------\n"); + int difIndex = strDifIndex(output, rr.Output); + string diffSnippet = strSnippet(rr.Output, difIndex, 50); + string expectedSnippet = strSnippet(output, difIndex, 50); + + Assert.Fail($"Test failed. Output and expected HTML are not equal.\nFirst difference at index: {difIndex}\nOutput near diff: {diffSnippet}\nExpected near diff: {expectedSnippet}\n---------------------- Expected ----------------------\n{output}\n---------------------- But was------------------------\n{rr.Output}\n------------------------------------------------------\n"); } if (path.ToLowerInvariant().Contains("invalid")) diff --git a/src/WattleScript.Tests/Templating/Tests/Html/14-svg.html b/src/WattleScript.Tests/Templating/Tests/Html/14-svg.html new file mode 100644 index 00000000..0215d299 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/14-svg.html @@ -0,0 +1,199 @@ + + happy_news + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/14-svg.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/14-svg.wthtml new file mode 100644 index 00000000..6b59541d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/14-svg.wthtml @@ -0,0 +1,201 @@ +@{ + + happy_news + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/15-mathml.html b/src/WattleScript.Tests/Templating/Tests/Html/15-mathml.html new file mode 100644 index 00000000..0ddd3ad5 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/15-mathml.html @@ -0,0 +1,36 @@ + + + x + = + + + + - + b + + ± + + + + b + 2 + + - + + 4 + + a + + c + + + + + + 2 + + a + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/15-mathml.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/15-mathml.wthtml new file mode 100644 index 00000000..be483b3a --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/15-mathml.wthtml @@ -0,0 +1,38 @@ +@{ + + + x + = + + + + - + b + + ± + + + + b + 2 + + - + + 4 + + a + + c + + + + + + 2 + + a + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/1-else-invalid.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/1-else-invalid.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/1-else-invalid.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/1-else-invalid.wthtml new file mode 100644 index 00000000..f86c1620 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/1-else-invalid.wthtml @@ -0,0 +1,3 @@ +@else { +
else
+} \ No newline at end of file From 61fb784bfb4d6aa5c8a0aa6b1659a9b4a450f495 Mon Sep 17 00:00:00 2001 From: lofcz Date: Tue, 3 May 2022 22:55:12 +0200 Subject: [PATCH 49/74] Add support for self-closing tag helpers --- src/WattleScript.Templating/Parser/Node.cs | 12 ++------ src/WattleScript.Templating/Parser/Parser.cs | 28 ++++++++++++------- .../Parser/ParserUtils.cs | 1 + .../Templating/TemplatingTestsRunner.cs | 4 ++- .../Tests/TagHelpers/7-self-closing.html | 1 + .../Tests/TagHelpers/7-self-closing.wthtml | 1 + .../TagDefinitions/selfClosing.wthtml | 12 ++++++++ 7 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/7-self-closing.html create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/7-self-closing.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/selfClosing.wthtml diff --git a/src/WattleScript.Templating/Parser/Node.cs b/src/WattleScript.Templating/Parser/Node.cs index 6b70d9e6..ac4f303f 100644 --- a/src/WattleScript.Templating/Parser/Node.cs +++ b/src/WattleScript.Templating/Parser/Node.cs @@ -63,6 +63,7 @@ internal class HtmlElement : NodeBase, INodeWithChildren { internal enum ClosingType { + Unknown, SelfClosing, ImplicitSelfClosing, EndTag @@ -83,18 +84,11 @@ public void AddChild(NodeBase child) internal class HtmlAttribute { - internal enum HtmlAttributeQuoteType - { - None, - Single, - Double - } - public string Name { get; set; } public string Value { get; set; } - public HtmlAttributeQuoteType QuoteType { get; set; } + public Parser.HtmlAttrEnclosingModes QuoteType { get; set; } - internal HtmlAttribute(string name, string value, HtmlAttributeQuoteType quoteType) + internal HtmlAttribute(string name, string value, Parser.HtmlAttrEnclosingModes quoteType) { Name = name; Value = value; diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 7d1f1406..1bceab0e 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -1100,9 +1100,9 @@ bool ParseTagHelper(HtmlElement el) break; } } - + // 2. if tag-helper is not self closing, scan for end of its content - //if (el.Closing != HtmlElement.ClosingType.SelfClosing) + if (el.Closing != HtmlElement.ClosingType.SelfClosing) { el.ContentTo = ScanForElementClosingTag(el); } @@ -1126,7 +1126,8 @@ bool ParseTagHelper(HtmlElement el) attrTable.Set(attr.Name, DynValue.NewString(attr.Value)); } - ctxTable.Set("attributes", DynValue.NewTable(attrTable)); + DynValue fAttrTable = DynValue.NewTable(attrTable); + ctxTable.Set("attributes", fAttrTable); //engine.script.Globals["stdout"] = engine.PrintTaghelperTmp; //engine.stdOutTagHelperTmp.Clear(); @@ -1172,6 +1173,8 @@ bool LookaheadForAttributeOrClose(string tagName, bool startsFromClosingTag, Htm string ParseAttributeName() { + ParseWhitespaceAndNewlines(Sides.Client); + StringBuilder sb = new StringBuilder(); while (!IsAtEnd()) { @@ -1192,7 +1195,7 @@ string ParseAttributeName() return sb.ToString(); } - string ParseAttributeValue() + Tuple ParseAttributeValue() { HtmlAttrEnclosingModes closeMode = HtmlAttrEnclosingModes.None; StringBuilder sb = new StringBuilder(); @@ -1218,25 +1221,25 @@ string ParseAttributeValue() if (IsWhitespaceOrNewline(Peek()) && closeMode == HtmlAttrEnclosingModes.None) { - return sb.ToString(); + return new Tuple(sb.ToString(), HtmlAttrEnclosingModes.None); } if (Peek() == '\'' && closeMode == HtmlAttrEnclosingModes.SingleQuote) { Step(); - return sb.ToString(); + return new Tuple(sb.ToString(), HtmlAttrEnclosingModes.SingleQuote); } if (Peek() == '"' && closeMode == HtmlAttrEnclosingModes.DoubleQuote) { Step(); - return sb.ToString(); + return new Tuple(sb.ToString(), HtmlAttrEnclosingModes.DoubleQuote); } sb.Append(Step()); } - return sb.ToString(); + return new Tuple(sb.ToString(), HtmlAttrEnclosingModes.Unknown); } @@ -1247,8 +1250,8 @@ bool ParseAttribute(string tagName, bool startsFromClosingTag, HtmlElement el, b if (Peek() == '=') { Step(); - string val = ParseAttributeValue(); - el.Attributes.Add(new HtmlAttribute(name, val, HtmlAttribute.HtmlAttributeQuoteType.Double)); + Tuple val = ParseAttributeValue(); + el.Attributes.Add(new HtmlAttribute(name, val.Item1, val.Item2)); } if (LookaheadForClosingTag()) @@ -1280,6 +1283,11 @@ bool CloseTag(string tagName, bool startsFromClosingTag, HtmlElement? el, bool p else if (Peek() == '>') { Step(); + + if (el != null) + { + el.Closing = HtmlElement.ClosingType.EndTag; + } } bool parseContent = false; diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index 85b5df94..bfc67cf7 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -6,6 +6,7 @@ internal partial class Parser { internal enum HtmlAttrEnclosingModes { + Unknown, None, SingleQuote, DoubleQuote diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index 861a7167..dacf2169 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -44,6 +44,7 @@ static string strSnippet(string str, int pivot, int n) int nL = pivot - n > 0 ? pivot - n : 0; int tL = pivot - n > 0 ? n : n - pivot; + return ""; return $"{str.Substring(nL, tL)}{str.Substring(pivot, nR)}"; } @@ -148,6 +149,8 @@ public async Task RunCore(string path, bool reportErrors = false) try { + //string debugStr = tmp.Debug(code); + rr = await tmp.Render(code); if (string.Equals(output, rr.Output)) @@ -169,7 +172,6 @@ public async Task RunCore(string path, bool reportErrors = false) Assert.Fail("Expected to crash but 'passed'"); } - //string debugStr = tmp.Debug(code); } catch (Exception e) { diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/7-self-closing.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/7-self-closing.html new file mode 100644 index 00000000..f2abf388 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/7-self-closing.html @@ -0,0 +1 @@ +
hi
world
test
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/7-self-closing.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/7-self-closing.wthtml new file mode 100644 index 00000000..392adb28 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/7-self-closing.wthtml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/selfClosing.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/selfClosing.wthtml new file mode 100644 index 00000000..2353537c --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/TagDefinitions/selfClosing.wthtml @@ -0,0 +1,12 @@ +@{ + @@name("self-closing") + + function Render(ctx) { + + var n1 = ctx["attributes"]["n1"]; + var n2 = ctx["attributes"]["n2"]; + var n3 = ctx["attributes"]["n3"]; + +
@n1
@n2
@n3
+ } +} \ No newline at end of file From 006535f3eb479a14785b34c1f92f9c5d1f99d77e Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 18 May 2022 05:00:16 +0200 Subject: [PATCH 50/74] Added support for preprocessor explicit transition @# --- src/WattleScript.Templating/Parser/Parser.cs | 16 ++++++++++++++++ .../AllowedKeyword/Preprocessor/1-simple.html | 1 + .../AllowedKeyword/Preprocessor/1-simple.wthtml | 2 ++ .../AllowedKeyword/Preprocessor/2-if.html | 2 ++ .../AllowedKeyword/Preprocessor/2-if.wthtml | 7 +++++++ 5 files changed, 28 insertions(+) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/1-simple.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/1-simple.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/2-if.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/2-if.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 1bceab0e..3483c4dd 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -421,6 +421,17 @@ void ParseRestOfLineAsClient() AddToken(TokenTypes.Text); } + void ParseRestOfLineAsServer() + { + char chr = Step(); + while (!IsAtEnd() && chr != '\n' && chr != '\r') + { + chr = Step(); + } + + AddToken(TokenTypes.BlockExpr); + } + /* In client mode everything is a literal * until we encouter @ * then we lookahead at next char and if it's not another @ (escape) @@ -534,6 +545,11 @@ bool ParseTransition(Sides currentSide) ParseCodeBlock(false, false); SetParsingControlChars(true); } + else if (c == '#') + { + Step(); + ParseRestOfLineAsServer(); + } else if (IsAlpha(c)) { ParseImplicitExpression(currentSide); diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/1-simple.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/1-simple.html new file mode 100644 index 00000000..244e2007 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/1-simple.html @@ -0,0 +1 @@ +pi is 3.14 \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/1-simple.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/1-simple.wthtml new file mode 100644 index 00000000..5bc5c0e4 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/1-simple.wthtml @@ -0,0 +1,2 @@ +@#define PI = 3.14 +pi is @PI \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/2-if.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/2-if.html new file mode 100644 index 00000000..5dd7eb4e --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/2-if.html @@ -0,0 +1,2 @@ +
test:
+YES diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/2-if.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/2-if.wthtml new file mode 100644 index 00000000..bb6153e7 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/Preprocessor/2-if.wthtml @@ -0,0 +1,7 @@ +@#define X +
test:
+@#if !X +NO +@#else +YES +@#endif \ No newline at end of file From 0d6d4d0196198df208cb8aa3fdbdbe469e012541 Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 18 May 2022 12:03:28 +0200 Subject: [PATCH 51/74] Added support for Html.Encode, Raw --- .../Templating/TemplatingTestsRunner.cs | 24 +++++++++++++++++++ .../Templating/Tests/Html/16-html-encode.html | 1 + .../Tests/Html/16-html-encode.wthtml | 1 + .../Templating/Tests/Html/17-html-raw.html | 1 + .../Templating/Tests/Html/17-html-raw.wthtml | 1 + 5 files changed, 28 insertions(+) create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/16-html-encode.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/16-html-encode.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/17-html-raw.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Html/17-html-raw.wthtml diff --git a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs index dacf2169..674e0aad 100644 --- a/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs +++ b/src/WattleScript.Tests/Templating/TemplatingTestsRunner.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; +using System.Web; using NUnit.Framework; using WattleScript.Templating; @@ -64,10 +66,26 @@ static string[] GetTestCases() return Array.Empty(); } + + [WattleScriptUserData] + class HtmlModule + { + public static DynValue Encode(ScriptExecutionContext context, CallbackArguments args) + { + return DynValue.NewString(HttpUtility.HtmlEncode(args[0].String)); + } + + public static DynValue Raw(ScriptExecutionContext context, CallbackArguments args) + { + return DynValue.NewString(args[0].String); + } + } [OneTimeSetUp] public async Task Init() { + UserData.RegisterAssembly(Assembly.GetAssembly(typeof(HtmlModule))); + if (!COMPILE_TAG_HELPERS) { return; @@ -84,6 +102,9 @@ public async Task Init() script.Options.Syntax = ScriptSyntax.WattleScript; script.Options.Directives.Add("using"); + HtmlModule htmlModule = new HtmlModule(); + script.Globals["Html"] = htmlModule; + TemplatingEngine tmp = new TemplatingEngine(script); try @@ -126,6 +147,9 @@ public async Task RunCore(string path, bool reportErrors = false) script.Options.Syntax = ScriptSyntax.WattleScript; script.Options.Directives.Add("using"); + HtmlModule htmlModule = new HtmlModule(); + script.Globals["Html"] = htmlModule; + TemplatingEngine tmp = new TemplatingEngine(script, null, tagHelpers); TemplatingEngine.RenderResult rr = null; diff --git a/src/WattleScript.Tests/Templating/Tests/Html/16-html-encode.html b/src/WattleScript.Tests/Templating/Tests/Html/16-html-encode.html new file mode 100644 index 00000000..b66dd31b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/16-html-encode.html @@ -0,0 +1 @@ +<div>>;<</div> \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/16-html-encode.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/16-html-encode.wthtml new file mode 100644 index 00000000..0464df3b --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/16-html-encode.wthtml @@ -0,0 +1 @@ +@Html.Encode("
>;<
") \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/17-html-raw.html b/src/WattleScript.Tests/Templating/Tests/Html/17-html-raw.html new file mode 100644 index 00000000..6b6b4a44 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/17-html-raw.html @@ -0,0 +1 @@ +
>;<
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/17-html-raw.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/17-html-raw.wthtml new file mode 100644 index 00000000..3b1d652f --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/17-html-raw.wthtml @@ -0,0 +1 @@ +@Html.Raw("
>;<
") \ No newline at end of file From 73af684e6dbdc807e7f76e6df9f078d4f6ef62c5 Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 18 May 2022 13:02:41 +0200 Subject: [PATCH 52/74] Support ! syntax to force native html elements --- src/WattleScript.Templating/Extensions.cs | 7 ++++ src/WattleScript.Templating/Parser/Parser.cs | 36 ++++++++++++++----- .../Parser/ParserUtils.cs | 21 +++++++++-- .../Tests/TagHelpers/8-force-default.html | 1 + .../Tests/TagHelpers/8-force-default.wthtml | 1 + 5 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/8-force-default.html create mode 100644 src/WattleScript.Tests/Templating/Tests/TagHelpers/8-force-default.wthtml diff --git a/src/WattleScript.Templating/Extensions.cs b/src/WattleScript.Templating/Extensions.cs index 258c5f2d..8607bbd6 100644 --- a/src/WattleScript.Templating/Extensions.cs +++ b/src/WattleScript.Templating/Extensions.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System; namespace WattleScript.Templating; @@ -31,4 +32,10 @@ public static void Push(this List list, T? itm) { list.Add(itm); } + + public static string ReplaceFirst(this string text, string search, string replace) + { + int pos = text.IndexOf(search, StringComparison.Ordinal); + return pos < 0 ? text : string.Concat(text[..pos], replace, text.AsSpan(pos + search.Length)); + } } \ No newline at end of file diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 3483c4dd..4a4a4c92 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -971,6 +971,25 @@ string ParseHtmlTagName(bool inBuffer, int offset = 1) ClearBuffer(true); SetStepMode(StepModes.Buffer); } + else + { + SetAddTokenAction(TokenTypes.Text, () => + { + // Next text block to add will be tag's name + // If starting with ! and not doctype or comment (html/cdata), ommit the ! + string str = GetCurrentLexeme(); + if (str.TrimStart().StartsWith(" 1 && inBuffer) { @@ -997,7 +1016,7 @@ string ParseHtmlTagName(bool inBuffer, int offset = 1) // The next few chars represent element's name while (!IsAtEnd() && IsHtmlTagChar(Peek())) { - bool shouldContinue = LookaheadForTransitionServerSide(); + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); if (shouldContinue) { continue; @@ -1015,12 +1034,16 @@ string ParseHtmlTagName(bool inBuffer, int offset = 1) if (inBuffer) { string tagName = GetBuffer(); - + AddBufferToCurrentLexeme(); ClearBuffer(false); SetStepMode(StepModes.CurrentLexeme); - - return tagName; + + return tagName; + } + else + { + AddToken(TokenTypes.Text); } return sb.ToString(); @@ -1047,11 +1070,6 @@ bool ParseHtmlTag(string? parentTagName) el.Name = tagName; openElements.Push(el); - if (friendlyName != "tagHelperDefinition") - { - - } - if (tagParsingMode == HtmlTagParsingModes.Native && engine.tagHelpersMap.ContainsKey(tagName.ToLowerInvariant())) { return ParseTagHelper(el); diff --git a/src/WattleScript.Templating/Parser/ParserUtils.cs b/src/WattleScript.Templating/Parser/ParserUtils.cs index bfc67cf7..d31505d1 100644 --- a/src/WattleScript.Templating/Parser/ParserUtils.cs +++ b/src/WattleScript.Templating/Parser/ParserUtils.cs @@ -108,6 +108,20 @@ char Step(int i = 1) return cc; } + private Dictionary AddTokenActions = new Dictionary(); + + void SetAddTokenAction(TokenTypes tokenType, Action action) + { + if (AddTokenActions.ContainsKey(tokenType)) + { + AddTokenActions[tokenType] = action; + } + else + { + AddTokenActions.Add(tokenType, action); + } + } + private int storedPos; void StorePos() { @@ -251,11 +265,12 @@ bool AddToken(TokenTypes type) return false; } - if (currentLexeme.ToString() == "\r\n") + if (AddTokenActions.ContainsKey(type)) { - string str = ""; + AddTokenActions[type].Invoke(); + AddTokenActions.Remove(type); } - + Token token = new Token(type, GetCurrentLexeme(), lastCommitedLine + 1, line + 1, lastCommitedPos + 1, pos + 1); Tokens.Add(token); DiscardCurrentLexeme(); diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/8-force-default.html b/src/WattleScript.Tests/Templating/Tests/TagHelpers/8-force-default.html new file mode 100644 index 00000000..392adb28 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/8-force-default.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/TagHelpers/8-force-default.wthtml b/src/WattleScript.Tests/Templating/Tests/TagHelpers/8-force-default.wthtml new file mode 100644 index 00000000..d741100d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/TagHelpers/8-force-default.wthtml @@ -0,0 +1 @@ + \ No newline at end of file From dc2fe1fb42d92525ea2484ea3afab075eb6ad84c Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 18 May 2022 13:10:03 +0200 Subject: [PATCH 53/74] Fix adding double tokens --- src/WattleScript.Templating/Parser/Parser.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index 4a4a4c92..a0a4aa8d 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -982,7 +982,7 @@ string ParseHtmlTagName(bool inBuffer, int offset = 1) { string subStr = str.TrimStart().Substring(2).ToLowerInvariant(); // skip ") && subStr != "-" && !subStr.StartsWith("--") && !subStr.StartsWith("[")) { DiscardCurrentLexeme(); currentLexeme.Append(str.ReplaceFirst("!", "")); @@ -1041,11 +1041,7 @@ string ParseHtmlTagName(bool inBuffer, int offset = 1) return tagName; } - else - { - AddToken(TokenTypes.Text); - } - + return sb.ToString(); } From 4f71011541f8541ee10dbdeadd0058541cd52fca Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 18 May 2022 14:34:15 +0200 Subject: [PATCH 54/74] Added tests for banned keywords --- .../Tests/ImplicitExpr/BannedKeyword/2-elseif-invalid.html | 0 .../ImplicitExpr/BannedKeyword/2-elseif-invalid.wthtml | 6 ++++++ .../Tests/ImplicitExpr/BannedKeyword/3-else-if-invalid.html | 0 .../ImplicitExpr/BannedKeyword/3-else-if-invalid.wthtml | 6 ++++++ 4 files changed, 12 insertions(+) create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/2-elseif-invalid.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/2-elseif-invalid.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/3-else-if-invalid.html create mode 100644 src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/3-else-if-invalid.wthtml diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/2-elseif-invalid.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/2-elseif-invalid.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/2-elseif-invalid.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/2-elseif-invalid.wthtml new file mode 100644 index 00000000..02ec910c --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/2-elseif-invalid.wthtml @@ -0,0 +1,6 @@ +@if (2 > 1) { +
else
+} +@elseif (1 >= 1) { +
else2
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/3-else-if-invalid.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/3-else-if-invalid.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/3-else-if-invalid.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/3-else-if-invalid.wthtml new file mode 100644 index 00000000..d698ff05 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/BannedKeyword/3-else-if-invalid.wthtml @@ -0,0 +1,6 @@ +@if (2 > 1) { +
else
+} +@else if (1 >= 1) { +
else2
+} \ No newline at end of file From 2ccc5f9c6a6d951e747bd51e91f31ad5e1b1c22d Mon Sep 17 00:00:00 2001 From: lofcz Date: Wed, 18 May 2022 14:45:59 +0200 Subject: [PATCH 55/74] Support transitioning to server side from html comments --- src/WattleScript.Templating/Parser/Parser.cs | 10 ++++++++-- .../Templating/Tests/Recovery/3-unclosed-el.html | 1 + .../Templating/Tests/Recovery/3-unclosed-el.wthtml | 1 + .../Tests/Recovery/4-server-side-tag-name.html | 1 + .../Tests/Recovery/4-server-side-tag-name.wthtml | 2 ++ .../Templating/Tests/Recovery/5-comment-server.html | 1 + .../Templating/Tests/Recovery/5-comment-server.wthtml | 4 ++++ 7 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/3-unclosed-el.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/3-unclosed-el.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/4-server-side-tag-name.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/4-server-side-tag-name.wthtml create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/5-comment-server.html create mode 100644 src/WattleScript.Tests/Templating/Tests/Recovery/5-comment-server.wthtml diff --git a/src/WattleScript.Templating/Parser/Parser.cs b/src/WattleScript.Templating/Parser/Parser.cs index a0a4aa8d..13c01bad 100644 --- a/src/WattleScript.Templating/Parser/Parser.cs +++ b/src/WattleScript.Templating/Parser/Parser.cs @@ -1610,9 +1610,15 @@ void ParseHtmlComment(HtmlCommentModes openCommentMode, Sides currentSide) StepN(3); AddToken(TokenTypes.Text); return; - } + } + + bool shouldContinue = LookaheadForTransitionClient(Sides.Client); + if (shouldContinue) + { + continue; + } - Step(); + Step(); } // [todo] error, unclosed html comment diff --git a/src/WattleScript.Tests/Templating/Tests/Recovery/3-unclosed-el.html b/src/WattleScript.Tests/Templating/Tests/Recovery/3-unclosed-el.html new file mode 100644 index 00000000..c408eb83 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Recovery/3-unclosed-el.html @@ -0,0 +1 @@ +content
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Recovery/4-server-side-tag-name.wthtml b/src/WattleScript.Tests/Templating/Tests/Recovery/4-server-side-tag-name.wthtml new file mode 100644 index 00000000..c3068721 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Recovery/4-server-side-tag-name.wthtml @@ -0,0 +1,2 @@ +@#define TAG "div" +<@TAG>content \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Recovery/5-comment-server.html b/src/WattleScript.Tests/Templating/Tests/Recovery/5-comment-server.html new file mode 100644 index 00000000..a1a27371 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Recovery/5-comment-server.html @@ -0,0 +1 @@ + + +

+ + Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/18-lua.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/18-lua.wthtml new file mode 100644 index 00000000..4b4b5fae --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/18-lua.wthtml @@ -0,0 +1,76 @@ +@{ + + + + Lua: demo + + + + + + + + +

+ Lua + Demo +

+ +

+ Try Lua before + downloading it. + Enter your Lua program + or + choose one of the demo programs below. + +

+ +

+

+ +

+ + + + +

+ +

Output

+

+ Your program ran successfully. + + +

+ + Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/19-pandas.html b/src/WattleScript.Tests/Templating/Tests/Html/19-pandas.html new file mode 100644 index 00000000..a62079f4 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/19-pandas.html @@ -0,0 +1,779 @@ + + + + + + + + pandas.concat — pandas 1.5.0.dev0+796.gecfcb35844.dirty documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + +
+
+ + + + + + + + +
+ + +
+ + + +
+ +
+ +
+ + +
+ + + + + + +
+ +
+ +
+

pandas.concat

+
+
+ pandas.concat(objs, axis=0, join='outer', ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False, sort=False, copy=True)[source]
+

Concatenate pandas objects along a particular axis with optional set logic + along the other axes.

+

Can also add a layer of hierarchical indexing on the concatenation axis, + which may be useful if the labels are the same (or overlapping) on + the passed axis number.

+
+
Parameters
+
+
objsa sequence or mapping of Series or DataFrame objects

If a mapping is passed, the sorted keys will be used as the keys + argument, unless it is passed, in which case the values will be + selected (see below). Any None objects will be dropped silently unless + they are all None in which case a ValueError will be raised.

+
+
axis{0/’index’, 1/’columns’}, default 0

The axis to concatenate along. + If 0, the outcome is identical to DataFrame.append().

+
+
join{‘inner’, ‘outer’}, default ‘outer’

How to handle indexes on other axis (or axes).

+
+
ignore_indexbool, default False

If True, do not use the index values along the concatenation axis. The + resulting axis will be labeled 0, …, n - 1. This is useful if you are + concatenating objects where the concatenation axis does not have + meaningful indexing information. Note the index values on the other + axes are still respected in the join.

+
+
keyssequence, default None

If multiple levels passed, should contain tuples. Construct + hierarchical index using the passed keys as the outermost level.

+
+
levelslist of sequences, default None

Specific levels (unique values) to use for constructing a + MultiIndex. Otherwise they will be inferred from the keys.

+
+
nameslist, default None

Names for the levels in the resulting hierarchical index.

+
+
verify_integritybool, default False

Check whether the new concatenated axis contains duplicates. This can + be very expensive relative to the actual data concatenation.

+
+
sortbool, default False

Sort non-concatenation axis if it is not already aligned when join + is ‘outer’. + This has no effect when join='inner', which already preserves + the order of the non-concatenation axis.

+
+

Changed in version 1.0.0: Changed to not sort by default.

+
+
+
copybool, default True

If False, do not copy data unnecessarily.

+
+
+
+
Returns
+
+
object, type of objs

When concatenating all Series along the index (axis=0), a + Series is returned. When objs contains at least one + DataFrame, a DataFrame is returned. When concatenating along + the columns (axis=1), a DataFrame is returned.

+
+
+
+
+
+

See also

+
+
DataFrame.join

Join DataFrames using indexes.

+
+
DataFrame.merge

Merge DataFrames by indexes or columns.

+
+
+
+

Notes

+

The keys, levels, and names arguments are all optional.

+

A walkthrough of how this method fits in with other tools for combining + pandas objects can be found here.

+

Examples

+

Combine two Series.

+
>>> s1 = pd.Series(['a', 'b'])
+>>> s2 = pd.Series(['c', 'd'])
+>>> pd.concat([s1, s2])
+0    a
+1    b
+0    c
+1    d
+dtype: object
+
+
+

Clear the existing index and reset it in the result + by setting the ignore_index option to True.

+
>>> pd.concat([s1, s2], ignore_index=True)
+0    a
+1    b
+2    c
+3    d
+dtype: object
+
+
+

Add a hierarchical index at the outermost level of + the data with the keys option.

+
>>> pd.concat([s1, s2], keys=['s1', 's2'])
+s1  0    a
+    1    b
+s2  0    c
+    1    d
+dtype: object
+
+
+

Label the index keys you create with the names option.

+
>>> pd.concat([s1, s2], keys=['s1', 's2'],
+...           names=['Series name', 'Row ID'])
+Series name  Row ID
+s1           0         a
+             1         b
+s2           0         c
+             1         d
+dtype: object
+
+
+

Combine two DataFrame objects with identical columns.

+
>>> df1 = pd.DataFrame([['a', 1], ['b', 2]],
+...                    columns=['letter', 'number'])
+>>> df1
+  letter  number
+0      a       1
+1      b       2
+>>> df2 = pd.DataFrame([['c', 3], ['d', 4]],
+...                    columns=['letter', 'number'])
+>>> df2
+  letter  number
+0      c       3
+1      d       4
+>>> pd.concat([df1, df2])
+  letter  number
+0      a       1
+1      b       2
+0      c       3
+1      d       4
+
+
+

Combine DataFrame objects with overlapping columns + and return everything. Columns outside the intersection will + be filled with NaN values.

+
>>> df3 = pd.DataFrame([['c', 3, 'cat'], ['d', 4, 'dog']],
+...                    columns=['letter', 'number', 'animal'])
+>>> df3
+  letter  number animal
+0      c       3    cat
+1      d       4    dog
+>>> pd.concat([df1, df3], sort=False)
+  letter  number animal
+0      a       1    NaN
+1      b       2    NaN
+0      c       3    cat
+1      d       4    dog
+
+
+

Combine DataFrame objects with overlapping columns + and return only those that are shared by passing inner to + the join keyword argument.

+
>>> pd.concat([df1, df3], join="inner")
+  letter  number
+0      a       1
+1      b       2
+0      c       3
+1      d       4
+
+
+

Combine DataFrame objects horizontally along the x axis by + passing in axis=1.

+
>>> df4 = pd.DataFrame([['bird', 'polly'], ['monkey', 'george']],
+...                    columns=['animal', 'name'])
+>>> pd.concat([df1, df4], axis=1)
+  letter  number  animal    name
+0      a       1    bird   polly
+1      b       2  monkey  george
+
+
+

Prevent the result from including duplicate index values with the + verify_integrity option.

+
>>> df5 = pd.DataFrame([1], index=['a'])
+>>> df5
+   0
+a  1
+>>> df6 = pd.DataFrame([2], index=['a'])
+>>> df6
+   0
+a  2
+>>> pd.concat([df5, df6], verify_integrity=True)
+Traceback (most recent call last):
+    ...
+ValueError: Indexes have overlapping values: ['a']
+
+
+

Append a single row to the end of a DataFrame object.

+
>>> a = pd.DataFrame({"A": 1, "B": 2}, index=[0])
+>>> a
+    A   B
+0   1   2
+>>> b = pd.DataFrame({"A": 3}, index=[0])
+>>> b
+    A
+0   3
+>>> for rowIndex, row in b.iterrows():
+>>>     print(pd.concat([a, row.to_frame().T], ignore_index=True))
+   A    B
+0  1  2.0
+1  3  NaN
+
+
+
+ +
+ + +
+ + + + + +
+ + +
+
+ + +
+
+ + + + + +
+
+ + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/19-pandas.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/19-pandas.wthtml new file mode 100644 index 00000000..d650edf0 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/19-pandas.wthtml @@ -0,0 +1,781 @@ +@{ + + + + + + + + pandas.concat — pandas 1.5.0.dev0+796.gecfcb35844.dirty documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ + +
+ + + +
+ +
+ +
+ + +
+ + + + + + +
+ +
+ +
+

pandas.concat

+
+
+ pandas.concat(objs, axis=0, join='outer', ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False, sort=False, copy=True)[source]
+

Concatenate pandas objects along a particular axis with optional set logic + along the other axes.

+

Can also add a layer of hierarchical indexing on the concatenation axis, + which may be useful if the labels are the same (or overlapping) on + the passed axis number.

+
+
Parameters
+
+
objsa sequence or mapping of Series or DataFrame objects

If a mapping is passed, the sorted keys will be used as the keys + argument, unless it is passed, in which case the values will be + selected (see below). Any None objects will be dropped silently unless + they are all None in which case a ValueError will be raised.

+
+
axis{0/’index’, 1/’columns’}, default 0

The axis to concatenate along. + If 0, the outcome is identical to DataFrame.append().

+
+
join{‘inner’, ‘outer’}, default ‘outer’

How to handle indexes on other axis (or axes).

+
+
ignore_indexbool, default False

If True, do not use the index values along the concatenation axis. The + resulting axis will be labeled 0, …, n - 1. This is useful if you are + concatenating objects where the concatenation axis does not have + meaningful indexing information. Note the index values on the other + axes are still respected in the join.

+
+
keyssequence, default None

If multiple levels passed, should contain tuples. Construct + hierarchical index using the passed keys as the outermost level.

+
+
levelslist of sequences, default None

Specific levels (unique values) to use for constructing a + MultiIndex. Otherwise they will be inferred from the keys.

+
+
nameslist, default None

Names for the levels in the resulting hierarchical index.

+
+
verify_integritybool, default False

Check whether the new concatenated axis contains duplicates. This can + be very expensive relative to the actual data concatenation.

+
+
sortbool, default False

Sort non-concatenation axis if it is not already aligned when join + is ‘outer’. + This has no effect when join='inner', which already preserves + the order of the non-concatenation axis.

+
+

Changed in version 1.0.0: Changed to not sort by default.

+
+
+
copybool, default True

If False, do not copy data unnecessarily.

+
+
+
+
Returns
+
+
object, type of objs

When concatenating all Series along the index (axis=0), a + Series is returned. When objs contains at least one + DataFrame, a DataFrame is returned. When concatenating along + the columns (axis=1), a DataFrame is returned.

+
+
+
+
+
+

See also

+
+
DataFrame.join

Join DataFrames using indexes.

+
+
DataFrame.merge

Merge DataFrames by indexes or columns.

+
+
+
+

Notes

+

The keys, levels, and names arguments are all optional.

+

A walkthrough of how this method fits in with other tools for combining + pandas objects can be found here.

+

Examples

+

Combine two Series.

+
>>> s1 = pd.Series(['a', 'b'])
+>>> s2 = pd.Series(['c', 'd'])
+>>> pd.concat([s1, s2])
+0    a
+1    b
+0    c
+1    d
+dtype: object
+
+
+

Clear the existing index and reset it in the result + by setting the ignore_index option to True.

+
>>> pd.concat([s1, s2], ignore_index=True)
+0    a
+1    b
+2    c
+3    d
+dtype: object
+
+
+

Add a hierarchical index at the outermost level of + the data with the keys option.

+
>>> pd.concat([s1, s2], keys=['s1', 's2'])
+s1  0    a
+    1    b
+s2  0    c
+    1    d
+dtype: object
+
+
+

Label the index keys you create with the names option.

+
>>> pd.concat([s1, s2], keys=['s1', 's2'],
+...           names=['Series name', 'Row ID'])
+Series name  Row ID
+s1           0         a
+             1         b
+s2           0         c
+             1         d
+dtype: object
+
+
+

Combine two DataFrame objects with identical columns.

+
>>> df1 = pd.DataFrame([['a', 1], ['b', 2]],
+...                    columns=['letter', 'number'])
+>>> df1
+  letter  number
+0      a       1
+1      b       2
+>>> df2 = pd.DataFrame([['c', 3], ['d', 4]],
+...                    columns=['letter', 'number'])
+>>> df2
+  letter  number
+0      c       3
+1      d       4
+>>> pd.concat([df1, df2])
+  letter  number
+0      a       1
+1      b       2
+0      c       3
+1      d       4
+
+
+

Combine DataFrame objects with overlapping columns + and return everything. Columns outside the intersection will + be filled with NaN values.

+
>>> df3 = pd.DataFrame([['c', 3, 'cat'], ['d', 4, 'dog']],
+...                    columns=['letter', 'number', 'animal'])
+>>> df3
+  letter  number animal
+0      c       3    cat
+1      d       4    dog
+>>> pd.concat([df1, df3], sort=False)
+  letter  number animal
+0      a       1    NaN
+1      b       2    NaN
+0      c       3    cat
+1      d       4    dog
+
+
+

Combine DataFrame objects with overlapping columns + and return only those that are shared by passing inner to + the join keyword argument.

+
>>> pd.concat([df1, df3], join="inner")
+  letter  number
+0      a       1
+1      b       2
+0      c       3
+1      d       4
+
+
+

Combine DataFrame objects horizontally along the x axis by + passing in axis=1.

+
>>> df4 = pd.DataFrame([['bird', 'polly'], ['monkey', 'george']],
+...                    columns=['animal', 'name'])
+>>> pd.concat([df1, df4], axis=1)
+  letter  number  animal    name
+0      a       1    bird   polly
+1      b       2  monkey  george
+
+
+

Prevent the result from including duplicate index values with the + verify_integrity option.

+
>>> df5 = pd.DataFrame([1], index=['a'])
+>>> df5
+   0
+a  1
+>>> df6 = pd.DataFrame([2], index=['a'])
+>>> df6
+   0
+a  2
+>>> pd.concat([df5, df6], verify_integrity=True)
+Traceback (most recent call last):
+    ...
+ValueError: Indexes have overlapping values: ['a']
+
+
+

Append a single row to the end of a DataFrame object.

+
>>> a = pd.DataFrame({"A": 1, "B": 2}, index=[0])
+>>> a
+    A   B
+0   1   2
+>>> b = pd.DataFrame({"A": 3}, index=[0])
+>>> b
+    A
+0   3
+>>> for rowIndex, row in b.iterrows():
+>>>     print(pd.concat([a, row.to_frame().T], ignore_index=True))
+   A    B
+0  1  2.0
+1  3  NaN
+
+
+
+ +
+ + +
+ + + + + +
+ + +
+
+ + +
+
+ + + + + +
+
+ + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/20-google-images-sminem-slow.html b/src/WattleScript.Tests/Templating/Tests/Html/20-google-images-sminem-slow.html new file mode 100644 index 00000000..b06c87a2 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/20-google-images-sminem-slow.html @@ -0,0 +1,292 @@ +sminem - Google Search

Accessibility Links

Settings

Search Modes

AllImagesVideosNewsShopping
Quick Settings
See all settings
Appearance
Google uses cookies to deliver its services, to personalize ads, and to analyze traffic. You can adjust your privacy controls anytime in your Google settings.

Search Results

Image Results

Sminem – Meaning & Origin 2022 (Term explained)

Sminem – Meaning & Origin 2022 (Term explained)
Sminem – Meaning & Origin 2022 (Term ...
slanglang.net

GRADUATE SMINEM | Foundation

GRADUATE SMINEM | Foundation
GRADUATE SMINEM | Foundation
foundation.app

Sminem 3838 (@SminemPompEt) / Twitter

Sminem 3838 (@SminemPompEt) / Twitter
Sminem 3838 (@SminemPompEt) / Twitter
twitter.com

Who Is Sminem? - The Russian Boy 4chan Turned Into A Meme - YouTube

Who Is Sminem? - The Russian Boy 4chan Turned Into A Meme - YouTube
Who Is Sminem? - The Russian Boy 4chan ...
youtube.com

Komunita služby Steam :: :: sminem <>

Komunita služby Steam :: :: sminem <>
Komunita služby Steam :: :: sminem <>
steamcommunity.com

I'm sorry for posting that (Akko Sminem) : r/LittleWitchAcademia

I'm sorry for posting that (Akko Sminem) : r/LittleWitchAcademia
I'm sorry for posting that (Akko Sminem ...
reddit.com

Sminem TikTok (eng sub) [1] - YouTube

Sminem TikTok (eng sub) [1] - YouTube
Sminem TikTok (eng sub) [1] - YouTube
youtube.com

Koupit Unisex Fashion TShirt boy sminem cool Print Plus Size XS-6XL T-Shirt 100%Cotton Tops O Neck Short Sleeve Tees za dobrou cenu — doprava zdarma, skutečné recenze s fotkami — Joom

Koupit Unisex Fashion TShirt boy sminem cool Print Plus Size XS-6XL T-Shirt  100%Cotton Tops O Neck Short Sleeve Tees za dobrou cenu — doprava zdarma,  skutečné recenze s fotkami — Joom
Koupit Unisex Fashion TShirt boy sminem ...
joom.com · Out of stock

Stream Joseph (Sminem) music | Listen to songs, albums, playlists for free on SoundCloud

Stream Joseph (Sminem) music | Listen to songs, albums, playlists for free  on SoundCloud
Stream Joseph (Sminem) music | Listen ...
soundcloud.com

Anybody else got Sminem? : r/enlistedgame

Anybody else got Sminem? : r/enlistedgame
Anybody else got Sminem? : r/enlistedgame
reddit.com

Hey boy Sminem stop... - Bogposting - Take Your Bog Pill | Facebook

Hey boy Sminem stop... - Bogposting - Take Your Bog Pill | Facebook
Hey boy Sminem stop... - Bogposting ...
facebook.com

Sminem - Profile | OpenSea

Sminem - Profile | OpenSea
Sminem - Profile | OpenSea
opensea.io

Sminem Bogdanoff GIF - Sminem Bogdanoff Gme - Discover & Share GIFs

Sminem Bogdanoff GIF - Sminem Bogdanoff Gme - Discover & Share GIFs
Sminem Bogdanoff GIF - Sminem Bogdanoff ...
tenor.com

Sminem on chrismas | Sminem | Know Your Meme

Sminem on chrismas | Sminem | Know Your Meme
Sminem on chrismas | Sminem | Know Your ...
knowyourmeme.com

Sminem with a phone Blank Template - Imgflip

Sminem with a phone Blank Template - Imgflip
Sminem with a phone Blank Template ...
imgflip.com

BOY SMINEM - Rarible | OpenSea

BOY SMINEM - Rarible | OpenSea
BOY SMINEM - Rarible | OpenSea
opensea.io

Boy Sminem watches over... - Scenic Depictions of Slavic Life | Facebook

Boy Sminem watches over... - Scenic Depictions of Slavic Life | Facebook
Boy Sminem watches over... - Scenic ...
facebook.com

Image Results

Boy Sminem Cool T Shirt Sminem So Cool Russia A Normal Day In Russia Reddit 4chan Meme Dank Meme Crypto Cryptocurrency 2454R|T-Shirts| - AliExpress

Boy Sminem Cool T Shirt Sminem So Cool Russia A Normal Day In Russia Reddit  4chan Meme Dank Meme Crypto Cryptocurrency 2454R|T-Shirts| - AliExpress
Boy Sminem Cool T Shirt Sminem So Cool ...
aliexpress.com · In stock

The SMINEM Collection | Foundation

The SMINEM Collection | Foundation
The SMINEM Collection | Foundation
foundation.app

Boy Sminem | Character | zKillboard

Boy Sminem | Character | zKillboard
Boy Sminem | Character | zKillboard
zkillboard.com

Maschine 📉 on Twitter: "@CryptoMessiah Must love #Sminem https://t.co/YByHOX6dcQ" / Twitter

Maschine 📉 on Twitter: "@CryptoMessiah Must love #Sminem  https://t.co/YByHOX6dcQ" / Twitter
CryptoMessiah Must love #Sminem https ...
twitter.com

Objevuj oblíbená videa na téma Sminem | TikTok

Objevuj oblíbená videa na téma Sminem | TikTok
videa na téma Sminem | TikTok
tiktok.com

Sminem Green GIF - Sminem Green Stand - Discover & Share GIFs

Sminem Green GIF - Sminem Green Stand - Discover & Share GIFs
Sminem Green GIF - Sminem Green Stand ...
tenor.com

CHADNEM | Sminem | Know Your Meme

CHADNEM | Sminem | Know Your Meme
CHADNEM | Sminem | Know Your Meme
knowyourmeme.com

Sminem HOPE : Clothing, Shoes & Jewelry

Sminem HOPE : Clothing, Shoes & Jewelry
Sminem HOPE : Clothing, Shoes & Jewelry
amazon.com

RETURN OF SMINEM - YouTube

RETURN OF SMINEM - YouTube
RETURN OF SMINEM - YouTube
youtube.com

Invest in Sminem. We found him fellows. He's coming… : r/MemeEconomy

Invest in Sminem. We found him fellows. He's coming… : r/MemeEconomy
Invest in Sminem. We found him fellows ...
reddit.com

Squatting Slavs In Tracksuits - Sminem President , big biznis! | Facebook

Squatting Slavs In Tracksuits - Sminem President , big biznis! | Facebook
Squatting Slavs In Tracksuits - Sminem ...
m.facebook.com

Sminem (@ru_sminem) / Twitter

Sminem (@ru_sminem) / Twitter
Sminem (@ru_sminem) / Twitter
twitter.com

Birthday Sminem | Sminem | Know Your Meme

Birthday Sminem | Sminem | Know Your Meme
Birthday Sminem | Sminem | Know Your Meme
knowyourmeme.com

A Brief History of Sminem - YouTube

A Brief History of Sminem - YouTube
A Brief History of Sminem - YouTube
youtube.com

Know Your Meme on Twitter: "The Sminem Boy Cool NFT project is slated to be auctioned off as a unique crypto-collectible broken up into more than 12K pieces. To learn more, we

Know Your Meme on Twitter: "The Sminem Boy Cool NFT project is slated to be  auctioned off as a unique crypto-collectible broken up into more than 12K  pieces. To learn more, we
The Sminem Boy Cool NFT project is ...
twitter.com

Workshop služby Steam::SMINEM VS BOGDANOFF

Workshop služby Steam::SMINEM VS BOGDANOFF
Workshop služby Steam::SMINEM VS BOGDANOFF
steamcommunity.com

Player statistics - Sminem | CS:GO Stats

Player statistics - Sminem | CS:GO Stats
Player statistics - Sminem | CS:GO Stats
csgostats.gg

SMINEM's Music Profile | Last.fm

SMINEM's Music Profile | Last.fm
SMINEM's Music Profile | Last.fm
last.fm
Wait while more content is being loaded
Google apps
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/20-google-images-sminem-slow.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/20-google-images-sminem-slow.wthtml new file mode 100644 index 00000000..8ed63a92 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/20-google-images-sminem-slow.wthtml @@ -0,0 +1,294 @@ +@!{ +sminem - Google Search

Accessibility Links

Settings

Search Modes

AllImagesVideosNewsShopping
Quick Settings
See all settings
Appearance
Google uses cookies to deliver its services, to personalize ads, and to analyze traffic. You can adjust your privacy controls anytime in your Google settings.

Search Results

Image Results

Sminem – Meaning & Origin 2022 (Term explained)

Sminem – Meaning & Origin 2022 (Term explained)
Sminem – Meaning & Origin 2022 (Term ...
slanglang.net

GRADUATE SMINEM | Foundation

GRADUATE SMINEM | Foundation
GRADUATE SMINEM | Foundation
foundation.app

Sminem 3838 (@SminemPompEt) / Twitter

Sminem 3838 (@SminemPompEt) / Twitter
Sminem 3838 (@SminemPompEt) / Twitter
twitter.com

Who Is Sminem? - The Russian Boy 4chan Turned Into A Meme - YouTube

Who Is Sminem? - The Russian Boy 4chan Turned Into A Meme - YouTube
Who Is Sminem? - The Russian Boy 4chan ...
youtube.com

Komunita služby Steam :: :: sminem <>

Komunita služby Steam :: :: sminem <>
Komunita služby Steam :: :: sminem <>
steamcommunity.com

I'm sorry for posting that (Akko Sminem) : r/LittleWitchAcademia

I'm sorry for posting that (Akko Sminem) : r/LittleWitchAcademia
I'm sorry for posting that (Akko Sminem ...
reddit.com

Sminem TikTok (eng sub) [1] - YouTube

Sminem TikTok (eng sub) [1] - YouTube
Sminem TikTok (eng sub) [1] - YouTube
youtube.com

Koupit Unisex Fashion TShirt boy sminem cool Print Plus Size XS-6XL T-Shirt 100%Cotton Tops O Neck Short Sleeve Tees za dobrou cenu — doprava zdarma, skutečné recenze s fotkami — Joom

Koupit Unisex Fashion TShirt boy sminem cool Print Plus Size XS-6XL T-Shirt  100%Cotton Tops O Neck Short Sleeve Tees za dobrou cenu — doprava zdarma,  skutečné recenze s fotkami — Joom
Koupit Unisex Fashion TShirt boy sminem ...
joom.com · Out of stock

Stream Joseph (Sminem) music | Listen to songs, albums, playlists for free on SoundCloud

Stream Joseph (Sminem) music | Listen to songs, albums, playlists for free  on SoundCloud
Stream Joseph (Sminem) music | Listen ...
soundcloud.com

Anybody else got Sminem? : r/enlistedgame

Anybody else got Sminem? : r/enlistedgame
Anybody else got Sminem? : r/enlistedgame
reddit.com

Hey boy Sminem stop... - Bogposting - Take Your Bog Pill | Facebook

Hey boy Sminem stop... - Bogposting - Take Your Bog Pill | Facebook
Hey boy Sminem stop... - Bogposting ...
facebook.com

Sminem - Profile | OpenSea

Sminem - Profile | OpenSea
Sminem - Profile | OpenSea
opensea.io

Sminem Bogdanoff GIF - Sminem Bogdanoff Gme - Discover & Share GIFs

Sminem Bogdanoff GIF - Sminem Bogdanoff Gme - Discover & Share GIFs
Sminem Bogdanoff GIF - Sminem Bogdanoff ...
tenor.com

Sminem on chrismas | Sminem | Know Your Meme

Sminem on chrismas | Sminem | Know Your Meme
Sminem on chrismas | Sminem | Know Your ...
knowyourmeme.com

Sminem with a phone Blank Template - Imgflip

Sminem with a phone Blank Template - Imgflip
Sminem with a phone Blank Template ...
imgflip.com

BOY SMINEM - Rarible | OpenSea

BOY SMINEM - Rarible | OpenSea
BOY SMINEM - Rarible | OpenSea
opensea.io

Boy Sminem watches over... - Scenic Depictions of Slavic Life | Facebook

Boy Sminem watches over... - Scenic Depictions of Slavic Life | Facebook
Boy Sminem watches over... - Scenic ...
facebook.com

Image Results

Boy Sminem Cool T Shirt Sminem So Cool Russia A Normal Day In Russia Reddit 4chan Meme Dank Meme Crypto Cryptocurrency 2454R|T-Shirts| - AliExpress

Boy Sminem Cool T Shirt Sminem So Cool Russia A Normal Day In Russia Reddit  4chan Meme Dank Meme Crypto Cryptocurrency 2454R|T-Shirts| - AliExpress
Boy Sminem Cool T Shirt Sminem So Cool ...
aliexpress.com · In stock

The SMINEM Collection | Foundation

The SMINEM Collection | Foundation
The SMINEM Collection | Foundation
foundation.app

Boy Sminem | Character | zKillboard

Boy Sminem | Character | zKillboard
Boy Sminem | Character | zKillboard
zkillboard.com

Maschine 📉 on Twitter: "@CryptoMessiah Must love #Sminem https://t.co/YByHOX6dcQ" / Twitter

Maschine 📉 on Twitter: "@CryptoMessiah Must love #Sminem  https://t.co/YByHOX6dcQ" / Twitter
CryptoMessiah Must love #Sminem https ...
twitter.com

Objevuj oblíbená videa na téma Sminem | TikTok

Objevuj oblíbená videa na téma Sminem | TikTok
videa na téma Sminem | TikTok
tiktok.com

Sminem Green GIF - Sminem Green Stand - Discover & Share GIFs

Sminem Green GIF - Sminem Green Stand - Discover & Share GIFs
Sminem Green GIF - Sminem Green Stand ...
tenor.com

CHADNEM | Sminem | Know Your Meme

CHADNEM | Sminem | Know Your Meme
CHADNEM | Sminem | Know Your Meme
knowyourmeme.com

Sminem HOPE : Clothing, Shoes & Jewelry

Sminem HOPE : Clothing, Shoes & Jewelry
Sminem HOPE : Clothing, Shoes & Jewelry
amazon.com

RETURN OF SMINEM - YouTube

RETURN OF SMINEM - YouTube
RETURN OF SMINEM - YouTube
youtube.com

Invest in Sminem. We found him fellows. He's coming… : r/MemeEconomy

Invest in Sminem. We found him fellows. He's coming… : r/MemeEconomy
Invest in Sminem. We found him fellows ...
reddit.com

Squatting Slavs In Tracksuits - Sminem President , big biznis! | Facebook

Squatting Slavs In Tracksuits - Sminem President , big biznis! | Facebook
Squatting Slavs In Tracksuits - Sminem ...
m.facebook.com

Sminem (@ru_sminem) / Twitter

Sminem (@ru_sminem) / Twitter
Sminem (@ru_sminem) / Twitter
twitter.com

Birthday Sminem | Sminem | Know Your Meme

Birthday Sminem | Sminem | Know Your Meme
Birthday Sminem | Sminem | Know Your Meme
knowyourmeme.com

A Brief History of Sminem - YouTube

A Brief History of Sminem - YouTube
A Brief History of Sminem - YouTube
youtube.com

Know Your Meme on Twitter: "The Sminem Boy Cool NFT project is slated to be auctioned off as a unique crypto-collectible broken up into more than 12K pieces. To learn more, we

Know Your Meme on Twitter: "The Sminem Boy Cool NFT project is slated to be  auctioned off as a unique crypto-collectible broken up into more than 12K  pieces. To learn more, we
The Sminem Boy Cool NFT project is ...
twitter.com

Workshop služby Steam::SMINEM VS BOGDANOFF

Workshop služby Steam::SMINEM VS BOGDANOFF
Workshop služby Steam::SMINEM VS BOGDANOFF
steamcommunity.com

Player statistics - Sminem | CS:GO Stats

Player statistics - Sminem | CS:GO Stats
Player statistics - Sminem | CS:GO Stats
csgostats.gg

SMINEM's Music Profile | Last.fm

SMINEM's Music Profile | Last.fm
SMINEM's Music Profile | Last.fm
last.fm
Wait while more content is being loaded
Google apps
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/21-wikipedia-bogdanoff-slow.html b/src/WattleScript.Tests/Templating/Tests/Html/21-wikipedia-bogdanoff-slow.html new file mode 100644 index 00000000..af9228cd --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/21-wikipedia-bogdanoff-slow.html @@ -0,0 +1,765 @@ + + + + + Igor and Grichka Bogdanoff - Wikipedia + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+

Igor and Grichka Bogdanoff

+
+
From Wikipedia, the free encyclopedia
+
+
+ +
+ Jump to navigation + Jump to search +
+ +

+

+
Igor and Grichka Bogdanoff
Présentation équipe DMBC, 10 septembre 2016 - 6.jpg
Grichka (left) and Igor (right) in 2016
Born
Igor Youriévitch Bogdanoff
Grégoire Youriévitch Bogdanoff

(1949-08-29)29 August 1949
DiedIgor: 3 January 2022(2022-01-03) (aged 72)
Grichka: 28 December 2021(2021-12-28) (aged 72)
Paris, France
Other names
+
  • Bogdanoff twins
+
OccupationMedia personalities
Known for
Alma materUniversity of Burgundy
Scientific career
FieldsTheoretical physics
ThesisFluctuations quantiques de la signature de la métrique à l'échelle de Planck (1999)
Doctoral advisorMoshé Flato [fr], Daniel Sternheimer
+
+
+

Igor Youriévitch Bogdanoff (French pronunciation: ​[iɡɔʁ juʁi.evitʃ bɔɡdanɔf]; 29 August 1949 – 3 January 2022) and Grégoire "Grichka" Youriévitch Bogdanoff (French: [ɡʁeɡwaʁ ɡʁiʃka]; 29 August 1949 – 28 December 2021) were French twin television presenters,[1] producers, and essayists who, from the 1970s on, presented various subjects in science fiction, popular science, and cosmology. They were involved in a number of controversies, most notably the Bogdanov affair, in which the brothers were alleged to have written nonsensical advanced physics papers that were nonetheless published in reputable scientific journals. +

+ + +

Early years[edit]

+
The twins' maternal grandmother, Berta Kolowrat-Krakowská
+

Igor and Grichka Bogdanoff were identical twin brothers born to Maria "Maya" Dolores Franzyska Kolowrat-Krakowská (1926–1982) and Yuri Mikhaïlovitch Bogdanoff (1928–2012),[2] an itinerant Russian farm worker, later a painter. Igor Bogdanoff was born 40 minutes before Grichka. They had no connection to, or involvement with, their father's family, and were raised by their maternal grandmother, Countess Bertha Kolowrat-Krakowská (1890–1982),[3] in her castle in southern France. Bertha Kolowrat-Krakowská belonged to the noble Kolowrat family of Bohemia and was married to a member of the Austrian princely house of Colloredo-Mannsfeld.[4] Her pregnancy by African-American tenor Roland Hayes caused her to forfeit access to her four elder children, to her palatial homes in Berlin and Prague, and also her reputation in European society.[3] She tried to sustain her episodic relationship with Hayes after her divorce and his return to the United States, but declined his offer to legally adopt and raise their daughter, who became Igor and Grichka's mother.[3] +

Although the Bogdanoff twins claimed to be descended paternally from a noble Muslim Tatar family traceable to the beginning of the 17th century (originally from Penza, one of whose mirzas converted to Orthodox Christianity, and was rewarded with the title of prince by a decree from Tsar Feodor III; the mirza did not exercise this right, and the title of "Prince Bogdanoff" was lost by the end of the 19th century),[5][6] there is scant evidence for that. Genealogist William Addams Reitwiesner observed: "The Bogdanov twins claim that their father was a member of a princely Russian family. Other than a statement by Dr. Stanislaus Dumin (included in a message posted by the twins on 7 Jan 2005 to the alt.talk.royalty Usenet newsgroup), there isn't much evidence to support this claim."[7] The journalist and documentary filmmaker Maud Guillaumin, author of Le mystère Bogdanoff (L'Archipel, 2019), comprehensively examined the twins' account, noting it to comprise "approximations and historical inaccuracies"; she found that Yuri Bogdanoff had gone to Spain as a young man, and, unable to return to the U.S.S.R. because he would have been considered a spy and imprisoned, went to France and began "a life of wandering from farm to Pyrenean farm" before, in 1948 aged 21, arriving at the castle of his future mother-in-law, "renowned in the Gers for employing Slavs". Guillaumin noted that "the twins totally deny this sad odyssey. They explain that they have found proof that their father was the descendant of a prince, the right arm of Tsar Peter the Great", that "according to them, Youra was a young artist [...] he would have followed 'a solid training as a painter as a free auditor at the Beaux-Arts'", and that "it was there, according to the twins, who love romance, that a "famous writer" met in Paris would have introduced Youra to their grandmother". Guillaumin's interview with the Bogdanoff twins' godmother, Monique David, contradicted their romantic account, and established that the twins' mother, Maya, was pregnant with them at the time of her marriage to Yuri Bogdanoff, who Countess Bertha Kolowrat-Krakowská considered an unworthy match for her daughter. She "chased him away", leading him to be absent from his sons' lives until they were ten years old, and subsequently divorced from Maya.[8][9] +

Besides French, they spoke German, Russian, and English. Their grandmother spoke several languages, as well.[10] +

+

Television shows[edit]

+
Igor (left) and Grichka (right) in the 1990s
+

The brothers began careers in television, hosting several popular programs on science and science fiction.[11][12][13] The first of these, Temps X (Time X), ran from 1979 to 1989[12][14] and introduced several British and American science-fiction series to the French public, including The Prisoner, Star Trek, and Doctor Who, in addition to featuring musical guests such as Jean-Michel Jarre.[15] +

In 2002, the Bogdanoffs launched a new weekly television show, Rayons X (X Rays), on the French public channel France 2. In August 2004, they presented a 90-minute special cosmology program.[16] +

+

Academic careers[edit]

+

Grichka Bogdanoff received a Ph.D. degree in mathematics from the University of Burgundy (Dijon) in 1999.[17][11] In 2002, Igor Bogdanoff received a Ph.D. in theoretical physics from the University of Burgundy.[11] Both brothers received the lowest passing grade of "honorable".[11] +

+

Bogdanov affair[edit]

+ +
Grichka (left) and Igor (right) Bogdanoff in 2010
+

In 2001 and 2002, the brothers published five papers (including "Topological field theory of the initial singularity of spacetime") in peer-reviewed physics journals.[18][19] Controversy over the Bogdanoffs' work began on 22 October 2002, with an email sent by University of Tours physicist Max Niedermaier to University of Pittsburgh physicist Ezra T. Newman.[20] Niedermaier suggested that the Bogdanoffs' Ph.D. theses and papers were "spoof[s]", created by throwing together instances of theoretical-physics jargon, including terminology from string theory: "The abstracts are delightfully meaningless combinations of buzzwords ... which apparently have been taken seriously."[20][21] +

Copies of the email reached American mathematical physicist John C. Baez, and on 23 October he created a discussion thread about the Bogdanoffs' work on the Usenet newsgroup sci.physics.research, titled "Physics bitten by reverse Alan Sokal hoax?"[12][22] Baez was comparing the Bogdanoffs' publications to the 1996 Sokal affair, in which physicist Alan Sokal successfully submitted an intentionally nonsensical paper to a cultural studies journal in order to criticize that field's lax standards for discussing science. The Bogdanoffs quickly became a popular discussion topic, with most respondents agreeing that the papers were flawed.[19] +

The story spread in public media, prompting Niedermaier to offer an apology to the Bogdanoffs, admitting that he had not read the papers himself. The Bogdanoffs' background in entertainment lent some plausibility to the idea that they were attempting a deliberate hoax, but Igor Bogdanoff quickly denied the accusation.[11][19] +

In October 2002, the Bogdanoffs released an email containing apparently supportive statements by Laurent Freidel, then a visiting professor at the Perimeter Institute for Theoretical Physics.[12] Soon after, Freidel denied writing any such remarks, telling the press that he had forwarded a message containing that text to a friend.[12] +

The online discussion was quickly followed by media attention. The Register reported on the dispute on 1 November 2002,[23] and stories in The Chronicle of Higher Education,[12] Nature,[20] The New York Times,[11] and other publications appeared soon after.[24][25] These news stories included commentary by physicists. +

One of the scientists who approved Igor Bogdanoff's thesis, Roman Jackiw of the Massachusetts Institute of Technology, spoke to The New York Times reporter Dennis Overbye. Overbye wrote that Jackiw was intrigued by the thesis, although it contained many points he did not understand. Jackiw defended the thesis.[11] In contrast, Ignatios Antoniadis (of the École Polytechnique), who approved Grichka Bogdanoff's thesis, later reversed his judgment of it. Antoniadis told Le Monde: +

+

I had given a favorable opinion for Grichka's defense, based on a rapid and indulgent reading of the thesis text. Alas, I was completely mistaken. The scientific language was just an appearance behind which hid incompetence and ignorance of even basic physics.[24]

+

The journal Classical and Quantum Gravity (CQG) published one of the Bogdanoffs' papers, titled "Topological field theory of the initial singularity of spacetime";[26] Ian Russell, assistant director of its journals division, later issued a statement stating that "we deployed our standard peer-review process on that paper."[12] After the publication of the article and the publicity surrounding the controversy, mathematician Greg Kuperberg posted to Usenet a statement written by the journal's senior publisher, Andrew Wray, and its co-editor, Hermann Nicolai. The statement read, in part, +

+

Regrettably, despite the best efforts, the refereeing process cannot be 100% effective. Thus the paper ... made it through the review process even though, in retrospect, it does not meet the standards expected of articles in this journal... The paper was discussed extensively at the annual Editorial Board meeting ... and there was general agreement that it should not have been published. Since then several steps have been taken to further improve the peer review process in order to improve the quality assessment on articles submitted to the journal and reduce the likelihood that this could happen again.[27]

+

The statement was quoted in The New York Times,[11] The Chronicle of Higher Education,[12] and Nature.[20] Moreover, Die Zeit quoted Nicolai as saying that had the paper reached his desk, he would have immediately rejected it.[25] +

The Chinese Journal of Physics published Igor Bogdanoff's "The KMS state of spacetime at the Planck scale", while Nuovo Cimento published "KMS space-time at the Planck scale". According to physicist Arun Bala, all of these papers "involved purported applications of quantum theory to understand processes at the dawn of the universe", but ultimately turned out to be a "hoax perpetrated on the physics community."[26] +

Not all review evaluations were positive. Eli Hawkins, acting as a referee on behalf of the Journal of Physics A, suggested rejecting one of the Bogdanoffs' papers: "It would take up too much space to enumerate all the mistakes: indeed it is difficult to say where one error ends and the next begins."[28] +

Eventually, the controversy attracted mainstream media attention, opening new avenues for physicists' comments to be disseminated. Le Monde quoted Alain Connes, recipient of the 1982 Fields Medal, as saying, "I didn't need long to convince myself that they're talking about things that they haven't mastered."[24] The New York Times reported that the physicists David Gross, Carlo Rovelli, and Lee Smolin considered the Bogdanoff papers nonsensical.[11] Nobel laureate Georges Charpak later stated on a French talk show that the Bogdanoffs' presence in the scientific community was "nonexistent".[29][30] +

Robert Oeckl's official MathSciNet review of "Topological field theory of the initial singularity of spacetime" states that the paper is "rife with nonsensical or meaningless statements and suffers from a serious lack of coherence", follows up with several examples to illustrate his point, and concludes that the paper "falls short of scientific standards and appears to have no meaningful content."[31] An official report from the Centre national de la recherche scientifique (CNRS), which became public in 2010, concluded that the paper "ne peut en aucune façon être qualifié de contribution scientifique" ("cannot in any way be considered a scientific contribution").[32][33] +

The CNRS report summarized the Bogdanoffs' theses thus: "Ces thèses n’ont pas de valeur scientifique. […] Rarement aura-t-on vu un travail creux habillé avec une telle sophistication" ("These theses have no scientific value. [...] Rarely have we seen a hollow work dressed with such sophistication").[34][35] +

+

Lawsuits[edit]

+

On December 30, 2004, the Bogdanoffs sued Ciel et Espace for defamation over the publication of a critical article titled "The Mystification of the Bogdanoffs".[36] In September 2006, the case was dismissed after the Bogdanoffs missed court deadlines; they were ordered to pay 2,500 to the magazine's publisher to cover its legal costs.[36][37] There was never a substantive ruling on whether or not the Bogdanoffs had been defamed.[37] +

+ Alain Riazuelo, an astrophysicist at the Institut d'Astrophysique de Paris, participated in many of the online discussions of the Bogdanoffs' work. He posted an unpublished version of Grichka Bogdanoff's Ph.D. thesis on his personal website, along with his critical analysis. Bogdanoff subsequently described this version as "dating from 1991 and too unfinished to be made public". Rather than suing Riazuelo for defamation, Bogdanoff filed a criminal complaint of copyright (droit d'auteur) violation against him in May 2011.[38] +

The police detained and interrogated Riazuelo. He was convicted in March 2012. A fine of €2,000 the court imposed was suspended, and only €1.00 of damages was awarded,[38] but in passing judgement the court stated that the scientist had "lacked prudence", given "the fame of the plaintiff".[39] +

The verdict outraged many scientists, who felt that the police and courts should have no say in a discussion of the scientific merits of a piece of work. In April 2012, a group of 170 scientists published an open letter titled L'affaire Bogdanoff: Liberté, Science et Justice, Des scientifiques revendiquent leur droit au blâme (The Bogdanoff Affair: Liberty, Science and Justice, scientists claim their right of critique).[40] +

In 2014, the Bogdanoffs sued the weekly magazine Marianne for defamation, on account of reporting the magazine had published in 2010[41] which had brought the CNRS report to light. The magazine was eventually ordered to pay €64,000 in damages, much less than the €800,000 each which the Bogdanoffs had originally demanded.[42] The Bogdanoffs also sued the CNRS for €1.2 million in damages, claiming that the CNRS report had "porté atteinte à leur honneur, à leur réputation et à leur crédit" ("undermined their honor, reputation and credit") and calling the report committee a "Stasi scientifique", but a tribunal ruled against them in 2015 and ordered them to pay €2,000.[35][43] +

+

Megatrend University[edit]

+

In 2005, the Bogdanoffs became professors at Megatrend University in Belgrade, where they were appointed to Chairs of Cosmology and made directors of the 'Megatrend Laboratory of Cosmology'.[44][45] Mića Jovanović, the rector and owner of Megatrend University, wrote a preface for the Serbian edition of Avant le Big Bang.[45] Jovanović himself later became embroiled in controversy and resigned his post, when he was found out to not have obtained a Ph.D. at the London School of Economics as he had claimed.[46] This scandal, combined with the presence of the Bogdanoffs, contributed to an atmosphere of controversy surrounding Megatrend.[47] +

+

Personal lives[edit]

+

The Bogdanoff twins, who denied having undergone plastic surgery,[48] became known for their prominent cheekbones and chins. In 2010, The Sydney Morning Herald described the twins' cheekbones as "so high and bulbous as to appear to threaten their owners' vision", adding that the twins' appearance at the Cannes Film Festival had "caused a stir around the world". The Herald noted that the twins' cheekbones had become noticeably larger in the 1990s, and that "growth in their lips and chins continued unabated through the last decade".[49] According to former education minister Luc Ferry, a friend of the brothers, they had both received botox injections for cosmetic treatment.[15] +

The twins became popular Internet memes, especially among enthusiasts of cryptocurrency, jokingly depicting the Bogdanoffs as "all-powerful market makers". Their status as "crypto memes" was covered by several outlets upon their deaths, including CNN, Business Insider, and The Daily Telegraph.[28][50][51] The twins "went along with their meme fame", according to Business Insider, and said they predicted cryptocurrency in the 1980s on Temps X.[51] +

Igor Bogdanoff had six children, four from his first marriage and two from his second.[52] He married his second wife, Amélie de Bourbon-Parme, civilly in Paris on 1 October 2009 and religiously in Chambord two days later.[53] +

+

Deaths[edit]

+

The Bogdanoff twins were both hospitalized, at the Georges Pompidou European Hospital in Paris,[54] in critical condition on 15 December 2021, after contracting COVID-19. Grichka died on 28 December,[55] and Igor died six days later, on 3 January 2022.[56] They were 72 and both were unvaccinated.[57][58][15] The funeral for both twins was held on 10 January 2022, in the Church of the Madeleine, in Paris, France.[59] +

+

Publications[edit]

+

The Bogdanoff brothers published a number of works in science fiction, philosophy and popular science. Since 1991, they signed their books as "Bogdanov", preferring "v" to "ff". +

+
  • Clefs pour la science-fiction (essay), Éditions Seghers, 378 p., Paris, 1976[ISBN missing], BNF:34707099q.
  • +
  • L'Effet science-fiction: à la recherche d'une définition (essay), Éditions Robert Laffont, Paris, 1979, 423 p., ISBN 978-2-221-00411-1, BNF:34650185 g.
  • +
  • Chroniques du "Temps X" (preface by Gérard Klein), Éditions du Guépard, Paris, 1981, 247 p., ISBN 978-2-86527-030-9, BNF: 34734883f.
  • +
  • La Machine fantôme, Éditions J'ai lu, 1985, 251 p., ISBN 978-2-277-21921-7, BNF:34842073t.
  • +
  • La Mémoire double (novel), first as hardcover on Éditions Hachette, Paris, 1985, 381 p., ISBN 978-2-01-011494-6, BNF:348362498; then as pocket book
  • +
  • Dieu et la science: vers le métaréalisme (interviews with Jean Guitton): Hardcover Éditions Grasset, Paris, 1991, 195 p., ISBN 978-2-246-42411-6, BNF: 35458968t; then as a pocketbook
  • +
  • Avant le Big Bang: la création du monde (essay), 2004[60]
  • +
  • Voyage vers l'Instant Zéro, Éditions EPA, Paris, 2006, 185 p., ISBN 978-2-85120-635-0, BNF: 40986028h.
  • +
  • Nous ne sommes pas seuls dans l'univers, Éditions EPA, Paris, 2007, 191 p., ISBN 978-2-85120-664-0, BNF: 411885989.
  • +
  • Au commencement du temps, Éditions Flammarion, Paris, 2009, 317 p., ISBN 978-2-08-120832-2, BNF: 420019981.
  • +
  • Le Visage de Dieu, (with a preface by Robert Woodrow Wilson and endnotes by Jim Peebles, Robert Woodrow Wilson and John Mather, Éditions Grasset, Paris, May 2010, 282 p., ISBN 978-2-246-77231-6, BNF: 42207600f.
  • +
  • Le Dernier Jour des dinosaures Éditions de la Martinière, Octobre 2011, ISBN 978-2732447100
  • +
  • La Pensée de Dieu, (with endnotes by Luis Gonzalez-Mestres), Éditions Grasset, Paris, June 2012, ISBN 978-2-246-78509-5
  • +
  • Le mystère du satellite Planck (Qu'y avait-il avant le Big Bang ?) (with preface and endnotes by Luis Gonzalez-Mestres, Éditions Eyrolles, June 2013, ISBN 978-2-212-55732-9
  • +
  • La Fin du hasard, Éditions Grasset, Paris, Octobre 2013, ISBN 978-2-246-80990-6
  • +
  • 3 minutes pour comprendre la grande théorie du Big Bang (preface by John Mather, end notes by Luis Gonzalez-Mestres, Éditions Le Courrier du Livre, October 2014, ISBN 978-2702911211
+

References[edit]

+
+
    +
  1. ^ Luis Gonzalez-Mestres, L'Énigme Bogdanov, Éditions Télémaque, Paris, 2015, 320 p., ISBN 978-2-7533-0266-2 +
  2. +
  3. ^ "Youra Bogdanoff, père des célèbres frères Igor et Grishka Bogdanoff est décédé cette semaine. D'origine russe, il était né le 28 janvier 1928 à Saint-Pétersbourg. Artiste-peintre, il s'était établi à Saint-Lary avec son épouse Maya, avant la naissance de leurs premiers enfants, Igor et Grichka. Les frères Bogdanoff ont quatre frères et sœurs plus jeunes : François, Laurence, Géraldine et Véronique. Un recueillement en sa mémoire aura lieu le mercredi 8 août, à 11 heures, au cimetière de Saint-Lary. La Dépêche du Midi présente ses condoléances à la famille (Youra Bogdanof)" [father of famous brothers Igor and Grishka Bogdanoff died this week. Of Russian origin, he was born on January 28, 1928 in Saint Petersburg. Artist-painter, he had settled in Saint-Lary with his wife Maya, before the birth of their first children, Igor and Grichka. The Bogdanoff brothers have four younger siblings: François, Laurence, Géraldine and Véronique. A meditation in his memory will take place on Wednesday August 8, at 11 a.m., at the Saint-Lary cemetery. La Dépêche du Midi presents its condolences to the family]. www.ladepeche.fr. +
  4. +
  5. ^ a b c Brooks, Christopher A. Roland Hayes: The Legacy of an American Tenor. Indiana University Press. Bloomington. 2015. pp. 358, 361–62, 366–67, 379. ISBN 978-0-253-01536-5. +
  6. +
  7. ^ Genealogisches Handbuch des Adels, Fürstliche Häuser XIX. "Colloredo-Mannsfeld". C.A. Starke Verlag, 2011, pp. 127–29. (German). ISBN 978-3-7980-0849-6. +
  8. +
  9. ^ Après consultation de tous les nobiliaires faisant autorité, aucun ne mentionne une quelconque famille de prince Bogdanoff : Patrick de Gmeline. « Dictionnaire de la noblesse russe ». Éditions Contrepoint, 1978, Almanach de Gotha 1918, Almanach de Gotha 1940, Almanach de Gotha, 2013. +
  10. +
  11. ^ Cependant, cela ne les prive pas théoriquement de la possibilité de confirmer à nouveau leur droit au titre de prince sur les voies de la grâce du chef de la maison impériale de Russie (selon plusieurs exemples connus) : les Bogdanoff sont de nouveau cités comme famille princière en 1906 dans des dictionnaires généalogiques russes. La reconnaissance par le chef de la maison souveraine royale de Georgie, Irakli Bagration-Mukhraneli, des droits de Youra Bogdanoff au titre de prince serait considérée en elle-même comme une raison juridique suffisante à sa confirmation dans la dignité princière au sein de l'empire de Russie. Un tel document confirmerait en effet du point de vue juridique la dignité princière et fixerait la tradition généalogique familiale de cette famille. C'est pourquoi Igor et Grichka Bogdanoff, ainsi que les enfants légitimes d'Igor, s'octroient le droit d'user aujourd'hui des titres de princes et princesses Bogdanoff (voir Lettre du Dr Stanislaw W. Dumin, président de la Fédération Russe de Généalogie et de la Société d'Histoire et de Généalogie à Moscou, Secrétaire général de l'Académie Internationale de Généalogie, et du prince Vadime Lopoukhine, vice-président de l'Assemblée de la Noblesse Russe : Certificat quant aux droits de Youri Mikhailovitch Bogdanoff et de sa descendance à la dignité et au titre princier. 25 décembre 2001). +
  12. +
  13. ^ "Ancestry of Igor and Grichka Bogdanov". www.wargs.com. +
  14. +
  15. ^ "Igor et Grischka Bogdanoff : le triste destin de leur pèr... - Closer". www.closermag.fr. 24 February 2019. +
  16. +
  17. ^ Maud Guillaumin (2019). Le mystère Bogdanoff. L'Archipel. +
  18. +
  19. ^ Mustafa, Filiz (29 December 2021). "Bogdanoff twins before surgery look explored as Grichka and Igor die at 72". HITC. Retrieved 4 January 2022. +
  20. +
  21. ^ a b c d e f g h i Overbye, Dennis (9 November 2002). "Are They a) Geniuses or b) Jokers?; French Physicists' Cosmic Theory Creates a Big Bang of Its Own". The New York Times. p. B2. +
  22. +
  23. ^ a b c d e f g h Richard Monastersky (5 November 2002). "The Emperor's New Science: French TV Stars Rock the World of Theoretical Physics". The Chronicle of Higher Education. Retrieved 11 January 2021. +
  24. +
  25. ^ Johnson, George (17 November 2002). "Ideas & Trends: In Theory, It's True (Or Not)". The New York Times. p. 4004004. +
  26. +
  27. ^ Schubert, Frank (14 June 2008). "Eine Nullnummer". Spektrum.de. Retrieved 11 January 2021. +
  28. +
  29. ^ a b c "France's Bogdanoff TV twins die of Covid six days apart". bbc.co.uk. 4 January 2022. Retrieved 4 January 2022. +
  30. +
  31. ^ Fossé, David (October 2004). "La mystification Bogdanov" (PDF). Ciel et Espace (in French). pp. 52–55. Retrieved 21 July 2019. +
  32. +
  33. ^ Grichka Bogdanoff (1999). Fluctuations quantiques de la signature de la métrique à l'échelle de Planck (entry in the French academic library directory) (doctorate in mathematics). Supervised by Daniel Sternheimer. University of Burgundy. +
  34. +
  35. ^ "INSPIRE-HEP citation information for Bogdanov papers". INSPIRE-HEP. Retrieved 24 February 2018. +
  36. +
  37. ^ a b c "Publish and perish". The Economist. 16 November 2002. +
  38. +
  39. ^ a b c d Butler, Declan (2002). "Theses spark twin dilemma for physicists". Nature. 420 (5): 5. Bibcode:2002Natur.420Q...5B. doi:10.1038/420005a. PMID 12422173. +
  40. +
  41. ^ Muir, Hazel (16 November 2002). "Twins raise ruckus". New Scientist. p. 6. Retrieved 11 July 2019. +
  42. +
  43. ^ John Baez (24 October 2002). "Physics bitten by reverse Alan Sokal hoax?". Newsgroupsci.physics.research. Usenet: ap7tq6$eme$1@glue.ucr.edu. +
  44. +
  45. ^ Orlowski, Andrew (1 November 2002). "Physics hoaxers discover Quantum Bogosity". The Register. Retrieved 27 February 2018. +
  46. +
  47. ^ a b c Hervé Morin (19 December 2002). "La réputation scientifique contestée des frères Bogdanov". Le Monde (in French). Retrieved 11 January 2021. +
  48. +
  49. ^ a b (in German) Christoph Drösser, Ulrich Schnabel. "Die Märchen der Gebrüder Bogdanov" ("Fairy tales of the Brothers Bogdanov") Die Zeit (2002), issue 46. +
  50. +
  51. ^ a b Arun Bala (2016). Complementarity Beyond Physics: Niels Bohr's Parallels. Palgrave MacMillan. pp. 26–27. +
  52. +
  53. ^ Kuperberg, Greg (1 November 2002). "If not a hoax, it's still an embarrassment". Newsgroupsci.physics.research. Usenet: apu93q$2a2$1@conifold.math.ucdavis.edu. Retrieved 21 July 2019. +
  54. +
  55. ^ a b "Igor and Grichka Bogdanoff, eccentric French TV star twins at the centre of a notorious scientific controversy – obituary". The Daily Telegraph. 4 January 2022. Archived from the original on 12 January 2022. Retrieved 4 January 2022. +
  56. +
  57. ^ France 2 TV talk show, Tout le monde en parle, 12 June 2004. See Riché, Pascal (30 September 2010). "Quand Charpak parlait de son Nobel (et faisait le mariole)". L'Obs (in French). Retrieved 21 November 2018. +
  58. +
  59. ^ "Les frères Bogdanov, la science et les médias". Acrimed (in French). 29 November 2004. Retrieved 11 January 2021. +
  60. +
  61. ^ Oeckl, Robert. "Review of 'Topological field theory of the initial singularity of spacetime'". MathSciNet. MR 1894907. {{cite journal}}: Cite journal requires |journal= (help) +
  62. +
  63. ^ Huet, Sylvestre (15 October 2010). "Un document accablant pour les Bogdanov". Libération (in French). Retrieved 21 July 2019. +
  64. +
  65. ^ "Rapport sur l'article "Topological field theory of the initial singularity of spacetime"" (PDF). Retrieved 21 July 2019. +
  66. +
  67. ^ Parienté, Jonathan (16 October 2010). "Les jumeaux Bogdanov étrillés par le CNRS". En quête de sciences (in French). Le Monde. Retrieved 24 February 2018. +
  68. +
  69. ^ a b "Les Bogdanov réclamaient un million, ils sont condamnés à payer 2000 euros". L'Express (in French). 2 July 2015. Retrieved 25 February 2018. +
  70. +
  71. ^ a b "Les frères Bogdanov condamnés". Ciel et Espace (in French). October 2006. Archived from the original on 17 November 2006. Retrieved 7 October 2006. +
  72. +
  73. ^ a b "Fin du litige avec Ciel et Espace". L'Obs (in French). 14 October 2006. Retrieved 25 February 2018. +
  74. +
  75. ^ a b Huet, Sylvestre (15 March 2012). "Un curieux jugement pour les frères Bogdanov". Libération (in French). Retrieved 12 July 2019. +
  76. +
  77. ^ Foucart, Stéphane (20 April 2012). "Les chercheurs et la menace Bogdanov (Researchers and the Bogdanov threat)". Le Monde (in French). +
  78. +
  79. ^ "Frères Bogdanov : 170 scientifiques réclament le droit de les critiquer" [Bogdanov brothers: 170 scientists claim the right of critique]. Le Nouvel Observateur (in French). 26 April 2012. +
  80. +
  81. ^ Gathié, Nathalie (16 October 2010). "Le vrai visage des Bogdanoff". Marianne (in French). Vol. 74. p. 62. +
  82. +
  83. ^ "Les frères Bogdanov font condamner "Marianne"". Le Point (in French). 21 May 2014. Retrieved 21 July 2019. +
  84. +
  85. ^ Auffret, Simon (27 June 2018). "Igor et Grichka Bogdanov, 40 ans d'affaires et de succès populaires". Le Monde (in French). Retrieved 22 July 2019. +
  86. +
  87. ^ "Prof. Grichka Bogdanoff, PhD & Prof. Igor Bogdanoff, PhD". Megatrend University. Archived from the original on 14 July 2014. Retrieved 27 February 2018. +
  88. +
  89. ^ a b Оташевић, Ана (10 June 2014). Како је Мића ректор постао космолог. politika.rs (in Serbian). Retrieved 27 February 2018. +
  90. +
  91. ^ Robinson, Matt (23 June 2014). "The minister, his mentor and the fight against a suspect system in Serbia". Reuters. Retrieved 27 February 2018. +
  92. +
  93. ^ Subasic, Katarina (26 June 2014). "Bogus academic claims tarnish Serbia's ivory tower". Yahoo! News. Agence France-Presse. Retrieved 27 February 2018. +
  94. +
  95. ^ "France's Bogdanoff TV twins die of Covid six days apart". BBC News. 4 January 2022. Retrieved 27 March 2022. +
  96. +
  97. ^ Robinson, Georgina (21 May 2012). "Who are those jaw-dropping twins?". Sydney Morning Herald. Retrieved 18 September 2021. +
  98. +
  99. ^ Mawad, Dalal; Ataman, Joseph (4 January 2022). "French TV star Igor Bogdanoff dies of Covid, days after twin brother". CNN. Retrieved 4 January 2022. +
  100. +
  101. ^ a b Dailey, Natasha (4 January 2022). "Wall Street Bets mourns loss of crypto-meme-famous Bogdanoff twins, who died of COVID-19". Business Insider. Retrieved 4 January 2022. +
  102. +
  103. ^ Média, Prisma. "Igor Bogdanov, six fois papa". Gala.fr (in French). Retrieved 1 January 2022. +
  104. +
  105. ^ "Igor convole à Chambord". ladepeche.fr (in French). Retrieved 1 January 2022. +
  106. +
  107. ^ "French TV star Igor Bogdanoff dies of Covid, days after twin brother". CNN. 4 January 2022. Retrieved 4 January 2021. [...] they had been at the Georges Pompidou hospital since December 15 [...] +
  108. +
  109. ^ "Grichka Bogdanoff, l'un des jumeaux stars des années 1980, est mort du Covid-19". La Monde. 28 December 2021. Retrieved 28 December 2021. +
  110. +
  111. ^ "Igor Bogdanoff est mort, six jours après son frère jumeau Grichka". L'Obs (in French). Agence France-Presse. 3 January 2022. Retrieved 3 January 2022. +
  112. +
  113. ^ "Grichka Bogdanoff, l'un des jumeaux stars des années 1980, est mort du Covid-19". Le Monde.fr (in French). 28 December 2021. Retrieved 4 January 2022. +
  114. +
  115. ^ "Mort d'Igor Bogdanoff, six jours après son frère Grichka". BFMTV (in French). Retrieved 4 January 2022. +
  116. +
  117. ^ "Les obsèques des frères Bogdanoff célébrées dans l'intimité de l'église de la Madeleine à Paris". Ouest France (in French). 10 January 2022. Retrieved 16 January 2022.. +
  118. +
  119. ^ Alberganti, Michel (27 April 2012). "Les frères Bogdanoff: mentalistes de la science (fiction)". Slate.fr (in French). Retrieved 4 January 2022. +
  120. +
+

Sources[edit]

+
  • Luboš Motl, L'équation Bogdanoff: le secret de l'origine de l'univers?, translated from English by Sonia Quémener, Marc Lenoir and Laurent Martein; Preface by Clóvis de Matos, Presses de la Renaissance, Paris, 2008, 237 pp., ISBN 978-2-7509-0386-2, BNF 411908225
+ + + + + +
+
+ +
+
+ +
+

Navigation menu

+
+ + + + +
+ + + + + + + + +
+
+ + + + + + + + + + + +
+
+ + + + +
+ + + + + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/21-wikipedia-bogdanoff-slow.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/21-wikipedia-bogdanoff-slow.wthtml new file mode 100644 index 00000000..9e474667 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/21-wikipedia-bogdanoff-slow.wthtml @@ -0,0 +1,767 @@ +@! { + + + + + Igor and Grichka Bogdanoff - Wikipedia + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+

Igor and Grichka Bogdanoff

+
+
From Wikipedia, the free encyclopedia
+
+
+ +
+ Jump to navigation + Jump to search +
+ +

+

+
Igor and Grichka Bogdanoff
Présentation équipe DMBC, 10 septembre 2016 - 6.jpg
Grichka (left) and Igor (right) in 2016
Born
Igor Youriévitch Bogdanoff
Grégoire Youriévitch Bogdanoff

(1949-08-29)29 August 1949
DiedIgor: 3 January 2022(2022-01-03) (aged 72)
Grichka: 28 December 2021(2021-12-28) (aged 72)
Paris, France
Other names
+
  • Bogdanoff twins
+
OccupationMedia personalities
Known for
Alma materUniversity of Burgundy
Scientific career
FieldsTheoretical physics
ThesisFluctuations quantiques de la signature de la métrique à l'échelle de Planck (1999)
Doctoral advisorMoshé Flato [fr], Daniel Sternheimer
+
+
+

Igor Youriévitch Bogdanoff (French pronunciation: ​[iɡɔʁ juʁi.evitʃ bɔɡdanɔf]; 29 August 1949 – 3 January 2022) and Grégoire "Grichka" Youriévitch Bogdanoff (French: [ɡʁeɡwaʁ ɡʁiʃka]; 29 August 1949 – 28 December 2021) were French twin television presenters,[1] producers, and essayists who, from the 1970s on, presented various subjects in science fiction, popular science, and cosmology. They were involved in a number of controversies, most notably the Bogdanov affair, in which the brothers were alleged to have written nonsensical advanced physics papers that were nonetheless published in reputable scientific journals. +

+ + +

Early years[edit]

+
The twins' maternal grandmother, Berta Kolowrat-Krakowská
+

Igor and Grichka Bogdanoff were identical twin brothers born to Maria "Maya" Dolores Franzyska Kolowrat-Krakowská (1926–1982) and Yuri Mikhaïlovitch Bogdanoff (1928–2012),[2] an itinerant Russian farm worker, later a painter. Igor Bogdanoff was born 40 minutes before Grichka. They had no connection to, or involvement with, their father's family, and were raised by their maternal grandmother, Countess Bertha Kolowrat-Krakowská (1890–1982),[3] in her castle in southern France. Bertha Kolowrat-Krakowská belonged to the noble Kolowrat family of Bohemia and was married to a member of the Austrian princely house of Colloredo-Mannsfeld.[4] Her pregnancy by African-American tenor Roland Hayes caused her to forfeit access to her four elder children, to her palatial homes in Berlin and Prague, and also her reputation in European society.[3] She tried to sustain her episodic relationship with Hayes after her divorce and his return to the United States, but declined his offer to legally adopt and raise their daughter, who became Igor and Grichka's mother.[3] +

Although the Bogdanoff twins claimed to be descended paternally from a noble Muslim Tatar family traceable to the beginning of the 17th century (originally from Penza, one of whose mirzas converted to Orthodox Christianity, and was rewarded with the title of prince by a decree from Tsar Feodor III; the mirza did not exercise this right, and the title of "Prince Bogdanoff" was lost by the end of the 19th century),[5][6] there is scant evidence for that. Genealogist William Addams Reitwiesner observed: "The Bogdanov twins claim that their father was a member of a princely Russian family. Other than a statement by Dr. Stanislaus Dumin (included in a message posted by the twins on 7 Jan 2005 to the alt.talk.royalty Usenet newsgroup), there isn't much evidence to support this claim."[7] The journalist and documentary filmmaker Maud Guillaumin, author of Le mystère Bogdanoff (L'Archipel, 2019), comprehensively examined the twins' account, noting it to comprise "approximations and historical inaccuracies"; she found that Yuri Bogdanoff had gone to Spain as a young man, and, unable to return to the U.S.S.R. because he would have been considered a spy and imprisoned, went to France and began "a life of wandering from farm to Pyrenean farm" before, in 1948 aged 21, arriving at the castle of his future mother-in-law, "renowned in the Gers for employing Slavs". Guillaumin noted that "the twins totally deny this sad odyssey. They explain that they have found proof that their father was the descendant of a prince, the right arm of Tsar Peter the Great", that "according to them, Youra was a young artist [...] he would have followed 'a solid training as a painter as a free auditor at the Beaux-Arts'", and that "it was there, according to the twins, who love romance, that a "famous writer" met in Paris would have introduced Youra to their grandmother". Guillaumin's interview with the Bogdanoff twins' godmother, Monique David, contradicted their romantic account, and established that the twins' mother, Maya, was pregnant with them at the time of her marriage to Yuri Bogdanoff, who Countess Bertha Kolowrat-Krakowská considered an unworthy match for her daughter. She "chased him away", leading him to be absent from his sons' lives until they were ten years old, and subsequently divorced from Maya.[8][9] +

Besides French, they spoke German, Russian, and English. Their grandmother spoke several languages, as well.[10] +

+

Television shows[edit]

+
Igor (left) and Grichka (right) in the 1990s
+

The brothers began careers in television, hosting several popular programs on science and science fiction.[11][12][13] The first of these, Temps X (Time X), ran from 1979 to 1989[12][14] and introduced several British and American science-fiction series to the French public, including The Prisoner, Star Trek, and Doctor Who, in addition to featuring musical guests such as Jean-Michel Jarre.[15] +

In 2002, the Bogdanoffs launched a new weekly television show, Rayons X (X Rays), on the French public channel France 2. In August 2004, they presented a 90-minute special cosmology program.[16] +

+

Academic careers[edit]

+

Grichka Bogdanoff received a Ph.D. degree in mathematics from the University of Burgundy (Dijon) in 1999.[17][11] In 2002, Igor Bogdanoff received a Ph.D. in theoretical physics from the University of Burgundy.[11] Both brothers received the lowest passing grade of "honorable".[11] +

+

Bogdanov affair[edit]

+ +
Grichka (left) and Igor (right) Bogdanoff in 2010
+

In 2001 and 2002, the brothers published five papers (including "Topological field theory of the initial singularity of spacetime") in peer-reviewed physics journals.[18][19] Controversy over the Bogdanoffs' work began on 22 October 2002, with an email sent by University of Tours physicist Max Niedermaier to University of Pittsburgh physicist Ezra T. Newman.[20] Niedermaier suggested that the Bogdanoffs' Ph.D. theses and papers were "spoof[s]", created by throwing together instances of theoretical-physics jargon, including terminology from string theory: "The abstracts are delightfully meaningless combinations of buzzwords ... which apparently have been taken seriously."[20][21] +

Copies of the email reached American mathematical physicist John C. Baez, and on 23 October he created a discussion thread about the Bogdanoffs' work on the Usenet newsgroup sci.physics.research, titled "Physics bitten by reverse Alan Sokal hoax?"[12][22] Baez was comparing the Bogdanoffs' publications to the 1996 Sokal affair, in which physicist Alan Sokal successfully submitted an intentionally nonsensical paper to a cultural studies journal in order to criticize that field's lax standards for discussing science. The Bogdanoffs quickly became a popular discussion topic, with most respondents agreeing that the papers were flawed.[19] +

The story spread in public media, prompting Niedermaier to offer an apology to the Bogdanoffs, admitting that he had not read the papers himself. The Bogdanoffs' background in entertainment lent some plausibility to the idea that they were attempting a deliberate hoax, but Igor Bogdanoff quickly denied the accusation.[11][19] +

In October 2002, the Bogdanoffs released an email containing apparently supportive statements by Laurent Freidel, then a visiting professor at the Perimeter Institute for Theoretical Physics.[12] Soon after, Freidel denied writing any such remarks, telling the press that he had forwarded a message containing that text to a friend.[12] +

The online discussion was quickly followed by media attention. The Register reported on the dispute on 1 November 2002,[23] and stories in The Chronicle of Higher Education,[12] Nature,[20] The New York Times,[11] and other publications appeared soon after.[24][25] These news stories included commentary by physicists. +

One of the scientists who approved Igor Bogdanoff's thesis, Roman Jackiw of the Massachusetts Institute of Technology, spoke to The New York Times reporter Dennis Overbye. Overbye wrote that Jackiw was intrigued by the thesis, although it contained many points he did not understand. Jackiw defended the thesis.[11] In contrast, Ignatios Antoniadis (of the École Polytechnique), who approved Grichka Bogdanoff's thesis, later reversed his judgment of it. Antoniadis told Le Monde: +

+

I had given a favorable opinion for Grichka's defense, based on a rapid and indulgent reading of the thesis text. Alas, I was completely mistaken. The scientific language was just an appearance behind which hid incompetence and ignorance of even basic physics.[24]

+

The journal Classical and Quantum Gravity (CQG) published one of the Bogdanoffs' papers, titled "Topological field theory of the initial singularity of spacetime";[26] Ian Russell, assistant director of its journals division, later issued a statement stating that "we deployed our standard peer-review process on that paper."[12] After the publication of the article and the publicity surrounding the controversy, mathematician Greg Kuperberg posted to Usenet a statement written by the journal's senior publisher, Andrew Wray, and its co-editor, Hermann Nicolai. The statement read, in part, +

+

Regrettably, despite the best efforts, the refereeing process cannot be 100% effective. Thus the paper ... made it through the review process even though, in retrospect, it does not meet the standards expected of articles in this journal... The paper was discussed extensively at the annual Editorial Board meeting ... and there was general agreement that it should not have been published. Since then several steps have been taken to further improve the peer review process in order to improve the quality assessment on articles submitted to the journal and reduce the likelihood that this could happen again.[27]

+

The statement was quoted in The New York Times,[11] The Chronicle of Higher Education,[12] and Nature.[20] Moreover, Die Zeit quoted Nicolai as saying that had the paper reached his desk, he would have immediately rejected it.[25] +

The Chinese Journal of Physics published Igor Bogdanoff's "The KMS state of spacetime at the Planck scale", while Nuovo Cimento published "KMS space-time at the Planck scale". According to physicist Arun Bala, all of these papers "involved purported applications of quantum theory to understand processes at the dawn of the universe", but ultimately turned out to be a "hoax perpetrated on the physics community."[26] +

Not all review evaluations were positive. Eli Hawkins, acting as a referee on behalf of the Journal of Physics A, suggested rejecting one of the Bogdanoffs' papers: "It would take up too much space to enumerate all the mistakes: indeed it is difficult to say where one error ends and the next begins."[28] +

Eventually, the controversy attracted mainstream media attention, opening new avenues for physicists' comments to be disseminated. Le Monde quoted Alain Connes, recipient of the 1982 Fields Medal, as saying, "I didn't need long to convince myself that they're talking about things that they haven't mastered."[24] The New York Times reported that the physicists David Gross, Carlo Rovelli, and Lee Smolin considered the Bogdanoff papers nonsensical.[11] Nobel laureate Georges Charpak later stated on a French talk show that the Bogdanoffs' presence in the scientific community was "nonexistent".[29][30] +

Robert Oeckl's official MathSciNet review of "Topological field theory of the initial singularity of spacetime" states that the paper is "rife with nonsensical or meaningless statements and suffers from a serious lack of coherence", follows up with several examples to illustrate his point, and concludes that the paper "falls short of scientific standards and appears to have no meaningful content."[31] An official report from the Centre national de la recherche scientifique (CNRS), which became public in 2010, concluded that the paper "ne peut en aucune façon être qualifié de contribution scientifique" ("cannot in any way be considered a scientific contribution").[32][33] +

The CNRS report summarized the Bogdanoffs' theses thus: "Ces thèses n’ont pas de valeur scientifique. […] Rarement aura-t-on vu un travail creux habillé avec une telle sophistication" ("These theses have no scientific value. [...] Rarely have we seen a hollow work dressed with such sophistication").[34][35] +

+

Lawsuits[edit]

+

On December 30, 2004, the Bogdanoffs sued Ciel et Espace for defamation over the publication of a critical article titled "The Mystification of the Bogdanoffs".[36] In September 2006, the case was dismissed after the Bogdanoffs missed court deadlines; they were ordered to pay 2,500 to the magazine's publisher to cover its legal costs.[36][37] There was never a substantive ruling on whether or not the Bogdanoffs had been defamed.[37] +

+ Alain Riazuelo, an astrophysicist at the Institut d'Astrophysique de Paris, participated in many of the online discussions of the Bogdanoffs' work. He posted an unpublished version of Grichka Bogdanoff's Ph.D. thesis on his personal website, along with his critical analysis. Bogdanoff subsequently described this version as "dating from 1991 and too unfinished to be made public". Rather than suing Riazuelo for defamation, Bogdanoff filed a criminal complaint of copyright (droit d'auteur) violation against him in May 2011.[38] +

The police detained and interrogated Riazuelo. He was convicted in March 2012. A fine of €2,000 the court imposed was suspended, and only €1.00 of damages was awarded,[38] but in passing judgement the court stated that the scientist had "lacked prudence", given "the fame of the plaintiff".[39] +

The verdict outraged many scientists, who felt that the police and courts should have no say in a discussion of the scientific merits of a piece of work. In April 2012, a group of 170 scientists published an open letter titled L'affaire Bogdanoff: Liberté, Science et Justice, Des scientifiques revendiquent leur droit au blâme (The Bogdanoff Affair: Liberty, Science and Justice, scientists claim their right of critique).[40] +

In 2014, the Bogdanoffs sued the weekly magazine Marianne for defamation, on account of reporting the magazine had published in 2010[41] which had brought the CNRS report to light. The magazine was eventually ordered to pay €64,000 in damages, much less than the €800,000 each which the Bogdanoffs had originally demanded.[42] The Bogdanoffs also sued the CNRS for €1.2 million in damages, claiming that the CNRS report had "porté atteinte à leur honneur, à leur réputation et à leur crédit" ("undermined their honor, reputation and credit") and calling the report committee a "Stasi scientifique", but a tribunal ruled against them in 2015 and ordered them to pay €2,000.[35][43] +

+

Megatrend University[edit]

+

In 2005, the Bogdanoffs became professors at Megatrend University in Belgrade, where they were appointed to Chairs of Cosmology and made directors of the 'Megatrend Laboratory of Cosmology'.[44][45] Mića Jovanović, the rector and owner of Megatrend University, wrote a preface for the Serbian edition of Avant le Big Bang.[45] Jovanović himself later became embroiled in controversy and resigned his post, when he was found out to not have obtained a Ph.D. at the London School of Economics as he had claimed.[46] This scandal, combined with the presence of the Bogdanoffs, contributed to an atmosphere of controversy surrounding Megatrend.[47] +

+

Personal lives[edit]

+

The Bogdanoff twins, who denied having undergone plastic surgery,[48] became known for their prominent cheekbones and chins. In 2010, The Sydney Morning Herald described the twins' cheekbones as "so high and bulbous as to appear to threaten their owners' vision", adding that the twins' appearance at the Cannes Film Festival had "caused a stir around the world". The Herald noted that the twins' cheekbones had become noticeably larger in the 1990s, and that "growth in their lips and chins continued unabated through the last decade".[49] According to former education minister Luc Ferry, a friend of the brothers, they had both received botox injections for cosmetic treatment.[15] +

The twins became popular Internet memes, especially among enthusiasts of cryptocurrency, jokingly depicting the Bogdanoffs as "all-powerful market makers". Their status as "crypto memes" was covered by several outlets upon their deaths, including CNN, Business Insider, and The Daily Telegraph.[28][50][51] The twins "went along with their meme fame", according to Business Insider, and said they predicted cryptocurrency in the 1980s on Temps X.[51] +

Igor Bogdanoff had six children, four from his first marriage and two from his second.[52] He married his second wife, Amélie de Bourbon-Parme, civilly in Paris on 1 October 2009 and religiously in Chambord two days later.[53] +

+

Deaths[edit]

+

The Bogdanoff twins were both hospitalized, at the Georges Pompidou European Hospital in Paris,[54] in critical condition on 15 December 2021, after contracting COVID-19. Grichka died on 28 December,[55] and Igor died six days later, on 3 January 2022.[56] They were 72 and both were unvaccinated.[57][58][15] The funeral for both twins was held on 10 January 2022, in the Church of the Madeleine, in Paris, France.[59] +

+

Publications[edit]

+

The Bogdanoff brothers published a number of works in science fiction, philosophy and popular science. Since 1991, they signed their books as "Bogdanov", preferring "v" to "ff". +

+
  • Clefs pour la science-fiction (essay), Éditions Seghers, 378 p., Paris, 1976[ISBN missing], BNF:34707099q.
  • +
  • L'Effet science-fiction: à la recherche d'une définition (essay), Éditions Robert Laffont, Paris, 1979, 423 p., ISBN 978-2-221-00411-1, BNF:34650185 g.
  • +
  • Chroniques du "Temps X" (preface by Gérard Klein), Éditions du Guépard, Paris, 1981, 247 p., ISBN 978-2-86527-030-9, BNF: 34734883f.
  • +
  • La Machine fantôme, Éditions J'ai lu, 1985, 251 p., ISBN 978-2-277-21921-7, BNF:34842073t.
  • +
  • La Mémoire double (novel), first as hardcover on Éditions Hachette, Paris, 1985, 381 p., ISBN 978-2-01-011494-6, BNF:348362498; then as pocket book
  • +
  • Dieu et la science: vers le métaréalisme (interviews with Jean Guitton): Hardcover Éditions Grasset, Paris, 1991, 195 p., ISBN 978-2-246-42411-6, BNF: 35458968t; then as a pocketbook
  • +
  • Avant le Big Bang: la création du monde (essay), 2004[60]
  • +
  • Voyage vers l'Instant Zéro, Éditions EPA, Paris, 2006, 185 p., ISBN 978-2-85120-635-0, BNF: 40986028h.
  • +
  • Nous ne sommes pas seuls dans l'univers, Éditions EPA, Paris, 2007, 191 p., ISBN 978-2-85120-664-0, BNF: 411885989.
  • +
  • Au commencement du temps, Éditions Flammarion, Paris, 2009, 317 p., ISBN 978-2-08-120832-2, BNF: 420019981.
  • +
  • Le Visage de Dieu, (with a preface by Robert Woodrow Wilson and endnotes by Jim Peebles, Robert Woodrow Wilson and John Mather, Éditions Grasset, Paris, May 2010, 282 p., ISBN 978-2-246-77231-6, BNF: 42207600f.
  • +
  • Le Dernier Jour des dinosaures Éditions de la Martinière, Octobre 2011, ISBN 978-2732447100
  • +
  • La Pensée de Dieu, (with endnotes by Luis Gonzalez-Mestres), Éditions Grasset, Paris, June 2012, ISBN 978-2-246-78509-5
  • +
  • Le mystère du satellite Planck (Qu'y avait-il avant le Big Bang ?) (with preface and endnotes by Luis Gonzalez-Mestres, Éditions Eyrolles, June 2013, ISBN 978-2-212-55732-9
  • +
  • La Fin du hasard, Éditions Grasset, Paris, Octobre 2013, ISBN 978-2-246-80990-6
  • +
  • 3 minutes pour comprendre la grande théorie du Big Bang (preface by John Mather, end notes by Luis Gonzalez-Mestres, Éditions Le Courrier du Livre, October 2014, ISBN 978-2702911211
+

References[edit]

+
+
    +
  1. ^ Luis Gonzalez-Mestres, L'Énigme Bogdanov, Éditions Télémaque, Paris, 2015, 320 p., ISBN 978-2-7533-0266-2 +
  2. +
  3. ^ "Youra Bogdanoff, père des célèbres frères Igor et Grishka Bogdanoff est décédé cette semaine. D'origine russe, il était né le 28 janvier 1928 à Saint-Pétersbourg. Artiste-peintre, il s'était établi à Saint-Lary avec son épouse Maya, avant la naissance de leurs premiers enfants, Igor et Grichka. Les frères Bogdanoff ont quatre frères et sœurs plus jeunes : François, Laurence, Géraldine et Véronique. Un recueillement en sa mémoire aura lieu le mercredi 8 août, à 11 heures, au cimetière de Saint-Lary. La Dépêche du Midi présente ses condoléances à la famille (Youra Bogdanof)" [father of famous brothers Igor and Grishka Bogdanoff died this week. Of Russian origin, he was born on January 28, 1928 in Saint Petersburg. Artist-painter, he had settled in Saint-Lary with his wife Maya, before the birth of their first children, Igor and Grichka. The Bogdanoff brothers have four younger siblings: François, Laurence, Géraldine and Véronique. A meditation in his memory will take place on Wednesday August 8, at 11 a.m., at the Saint-Lary cemetery. La Dépêche du Midi presents its condolences to the family]. www.ladepeche.fr. +
  4. +
  5. ^ a b c Brooks, Christopher A. Roland Hayes: The Legacy of an American Tenor. Indiana University Press. Bloomington. 2015. pp. 358, 361–62, 366–67, 379. ISBN 978-0-253-01536-5. +
  6. +
  7. ^ Genealogisches Handbuch des Adels, Fürstliche Häuser XIX. "Colloredo-Mannsfeld". C.A. Starke Verlag, 2011, pp. 127–29. (German). ISBN 978-3-7980-0849-6. +
  8. +
  9. ^ Après consultation de tous les nobiliaires faisant autorité, aucun ne mentionne une quelconque famille de prince Bogdanoff : Patrick de Gmeline. « Dictionnaire de la noblesse russe ». Éditions Contrepoint, 1978, Almanach de Gotha 1918, Almanach de Gotha 1940, Almanach de Gotha, 2013. +
  10. +
  11. ^ Cependant, cela ne les prive pas théoriquement de la possibilité de confirmer à nouveau leur droit au titre de prince sur les voies de la grâce du chef de la maison impériale de Russie (selon plusieurs exemples connus) : les Bogdanoff sont de nouveau cités comme famille princière en 1906 dans des dictionnaires généalogiques russes. La reconnaissance par le chef de la maison souveraine royale de Georgie, Irakli Bagration-Mukhraneli, des droits de Youra Bogdanoff au titre de prince serait considérée en elle-même comme une raison juridique suffisante à sa confirmation dans la dignité princière au sein de l'empire de Russie. Un tel document confirmerait en effet du point de vue juridique la dignité princière et fixerait la tradition généalogique familiale de cette famille. C'est pourquoi Igor et Grichka Bogdanoff, ainsi que les enfants légitimes d'Igor, s'octroient le droit d'user aujourd'hui des titres de princes et princesses Bogdanoff (voir Lettre du Dr Stanislaw W. Dumin, président de la Fédération Russe de Généalogie et de la Société d'Histoire et de Généalogie à Moscou, Secrétaire général de l'Académie Internationale de Généalogie, et du prince Vadime Lopoukhine, vice-président de l'Assemblée de la Noblesse Russe : Certificat quant aux droits de Youri Mikhailovitch Bogdanoff et de sa descendance à la dignité et au titre princier. 25 décembre 2001). +
  12. +
  13. ^ "Ancestry of Igor and Grichka Bogdanov". www.wargs.com. +
  14. +
  15. ^ "Igor et Grischka Bogdanoff : le triste destin de leur pèr... - Closer". www.closermag.fr. 24 February 2019. +
  16. +
  17. ^ Maud Guillaumin (2019). Le mystère Bogdanoff. L'Archipel. +
  18. +
  19. ^ Mustafa, Filiz (29 December 2021). "Bogdanoff twins before surgery look explored as Grichka and Igor die at 72". HITC. Retrieved 4 January 2022. +
  20. +
  21. ^ a b c d e f g h i Overbye, Dennis (9 November 2002). "Are They a) Geniuses or b) Jokers?; French Physicists' Cosmic Theory Creates a Big Bang of Its Own". The New York Times. p. B2. +
  22. +
  23. ^ a b c d e f g h Richard Monastersky (5 November 2002). "The Emperor's New Science: French TV Stars Rock the World of Theoretical Physics". The Chronicle of Higher Education. Retrieved 11 January 2021. +
  24. +
  25. ^ Johnson, George (17 November 2002). "Ideas & Trends: In Theory, It's True (Or Not)". The New York Times. p. 4004004. +
  26. +
  27. ^ Schubert, Frank (14 June 2008). "Eine Nullnummer". Spektrum.de. Retrieved 11 January 2021. +
  28. +
  29. ^ a b c "France's Bogdanoff TV twins die of Covid six days apart". bbc.co.uk. 4 January 2022. Retrieved 4 January 2022. +
  30. +
  31. ^ Fossé, David (October 2004). "La mystification Bogdanov" (PDF). Ciel et Espace (in French). pp. 52–55. Retrieved 21 July 2019. +
  32. +
  33. ^ Grichka Bogdanoff (1999). Fluctuations quantiques de la signature de la métrique à l'échelle de Planck (entry in the French academic library directory) (doctorate in mathematics). Supervised by Daniel Sternheimer. University of Burgundy. +
  34. +
  35. ^ "INSPIRE-HEP citation information for Bogdanov papers". INSPIRE-HEP. Retrieved 24 February 2018. +
  36. +
  37. ^ a b c "Publish and perish". The Economist. 16 November 2002. +
  38. +
  39. ^ a b c d Butler, Declan (2002). "Theses spark twin dilemma for physicists". Nature. 420 (5): 5. Bibcode:2002Natur.420Q...5B. doi:10.1038/420005a. PMID 12422173. +
  40. +
  41. ^ Muir, Hazel (16 November 2002). "Twins raise ruckus". New Scientist. p. 6. Retrieved 11 July 2019. +
  42. +
  43. ^ John Baez (24 October 2002). "Physics bitten by reverse Alan Sokal hoax?". Newsgroupsci.physics.research. Usenet: ap7tq6$eme$1@glue.ucr.edu. +
  44. +
  45. ^ Orlowski, Andrew (1 November 2002). "Physics hoaxers discover Quantum Bogosity". The Register. Retrieved 27 February 2018. +
  46. +
  47. ^ a b c Hervé Morin (19 December 2002). "La réputation scientifique contestée des frères Bogdanov". Le Monde (in French). Retrieved 11 January 2021. +
  48. +
  49. ^ a b (in German) Christoph Drösser, Ulrich Schnabel. "Die Märchen der Gebrüder Bogdanov" ("Fairy tales of the Brothers Bogdanov") Die Zeit (2002), issue 46. +
  50. +
  51. ^ a b Arun Bala (2016). Complementarity Beyond Physics: Niels Bohr's Parallels. Palgrave MacMillan. pp. 26–27. +
  52. +
  53. ^ Kuperberg, Greg (1 November 2002). "If not a hoax, it's still an embarrassment". Newsgroupsci.physics.research. Usenet: apu93q$2a2$1@conifold.math.ucdavis.edu. Retrieved 21 July 2019. +
  54. +
  55. ^ a b "Igor and Grichka Bogdanoff, eccentric French TV star twins at the centre of a notorious scientific controversy – obituary". The Daily Telegraph. 4 January 2022. Archived from the original on 12 January 2022. Retrieved 4 January 2022. +
  56. +
  57. ^ France 2 TV talk show, Tout le monde en parle, 12 June 2004. See Riché, Pascal (30 September 2010). "Quand Charpak parlait de son Nobel (et faisait le mariole)". L'Obs (in French). Retrieved 21 November 2018. +
  58. +
  59. ^ "Les frères Bogdanov, la science et les médias". Acrimed (in French). 29 November 2004. Retrieved 11 January 2021. +
  60. +
  61. ^ Oeckl, Robert. "Review of 'Topological field theory of the initial singularity of spacetime'". MathSciNet. MR 1894907. {{cite journal}}: Cite journal requires |journal= (help) +
  62. +
  63. ^ Huet, Sylvestre (15 October 2010). "Un document accablant pour les Bogdanov". Libération (in French). Retrieved 21 July 2019. +
  64. +
  65. ^ "Rapport sur l'article "Topological field theory of the initial singularity of spacetime"" (PDF). Retrieved 21 July 2019. +
  66. +
  67. ^ Parienté, Jonathan (16 October 2010). "Les jumeaux Bogdanov étrillés par le CNRS". En quête de sciences (in French). Le Monde. Retrieved 24 February 2018. +
  68. +
  69. ^ a b "Les Bogdanov réclamaient un million, ils sont condamnés à payer 2000 euros". L'Express (in French). 2 July 2015. Retrieved 25 February 2018. +
  70. +
  71. ^ a b "Les frères Bogdanov condamnés". Ciel et Espace (in French). October 2006. Archived from the original on 17 November 2006. Retrieved 7 October 2006. +
  72. +
  73. ^ a b "Fin du litige avec Ciel et Espace". L'Obs (in French). 14 October 2006. Retrieved 25 February 2018. +
  74. +
  75. ^ a b Huet, Sylvestre (15 March 2012). "Un curieux jugement pour les frères Bogdanov". Libération (in French). Retrieved 12 July 2019. +
  76. +
  77. ^ Foucart, Stéphane (20 April 2012). "Les chercheurs et la menace Bogdanov (Researchers and the Bogdanov threat)". Le Monde (in French). +
  78. +
  79. ^ "Frères Bogdanov : 170 scientifiques réclament le droit de les critiquer" [Bogdanov brothers: 170 scientists claim the right of critique]. Le Nouvel Observateur (in French). 26 April 2012. +
  80. +
  81. ^ Gathié, Nathalie (16 October 2010). "Le vrai visage des Bogdanoff". Marianne (in French). Vol. 74. p. 62. +
  82. +
  83. ^ "Les frères Bogdanov font condamner "Marianne"". Le Point (in French). 21 May 2014. Retrieved 21 July 2019. +
  84. +
  85. ^ Auffret, Simon (27 June 2018). "Igor et Grichka Bogdanov, 40 ans d'affaires et de succès populaires". Le Monde (in French). Retrieved 22 July 2019. +
  86. +
  87. ^ "Prof. Grichka Bogdanoff, PhD & Prof. Igor Bogdanoff, PhD". Megatrend University. Archived from the original on 14 July 2014. Retrieved 27 February 2018. +
  88. +
  89. ^ a b Оташевић, Ана (10 June 2014). Како је Мића ректор постао космолог. politika.rs (in Serbian). Retrieved 27 February 2018. +
  90. +
  91. ^ Robinson, Matt (23 June 2014). "The minister, his mentor and the fight against a suspect system in Serbia". Reuters. Retrieved 27 February 2018. +
  92. +
  93. ^ Subasic, Katarina (26 June 2014). "Bogus academic claims tarnish Serbia's ivory tower". Yahoo! News. Agence France-Presse. Retrieved 27 February 2018. +
  94. +
  95. ^ "France's Bogdanoff TV twins die of Covid six days apart". BBC News. 4 January 2022. Retrieved 27 March 2022. +
  96. +
  97. ^ Robinson, Georgina (21 May 2012). "Who are those jaw-dropping twins?". Sydney Morning Herald. Retrieved 18 September 2021. +
  98. +
  99. ^ Mawad, Dalal; Ataman, Joseph (4 January 2022). "French TV star Igor Bogdanoff dies of Covid, days after twin brother". CNN. Retrieved 4 January 2022. +
  100. +
  101. ^ a b Dailey, Natasha (4 January 2022). "Wall Street Bets mourns loss of crypto-meme-famous Bogdanoff twins, who died of COVID-19". Business Insider. Retrieved 4 January 2022. +
  102. +
  103. ^ Média, Prisma. "Igor Bogdanov, six fois papa". Gala.fr (in French). Retrieved 1 January 2022. +
  104. +
  105. ^ "Igor convole à Chambord". ladepeche.fr (in French). Retrieved 1 January 2022. +
  106. +
  107. ^ "French TV star Igor Bogdanoff dies of Covid, days after twin brother". CNN. 4 January 2022. Retrieved 4 January 2021. [...] they had been at the Georges Pompidou hospital since December 15 [...] +
  108. +
  109. ^ "Grichka Bogdanoff, l'un des jumeaux stars des années 1980, est mort du Covid-19". La Monde. 28 December 2021. Retrieved 28 December 2021. +
  110. +
  111. ^ "Igor Bogdanoff est mort, six jours après son frère jumeau Grichka". L'Obs (in French). Agence France-Presse. 3 January 2022. Retrieved 3 January 2022. +
  112. +
  113. ^ "Grichka Bogdanoff, l'un des jumeaux stars des années 1980, est mort du Covid-19". Le Monde.fr (in French). 28 December 2021. Retrieved 4 January 2022. +
  114. +
  115. ^ "Mort d'Igor Bogdanoff, six jours après son frère Grichka". BFMTV (in French). Retrieved 4 January 2022. +
  116. +
  117. ^ "Les obsèques des frères Bogdanoff célébrées dans l'intimité de l'église de la Madeleine à Paris". Ouest France (in French). 10 January 2022. Retrieved 16 January 2022.. +
  118. +
  119. ^ Alberganti, Michel (27 April 2012). "Les frères Bogdanoff: mentalistes de la science (fiction)". Slate.fr (in French). Retrieved 4 January 2022. +
  120. +
+

Sources[edit]

+
  • Luboš Motl, L'équation Bogdanoff: le secret de l'origine de l'univers?, translated from English by Sonia Quémener, Marc Lenoir and Laurent Martein; Preface by Clóvis de Matos, Presses de la Renaissance, Paris, 2008, 237 pp., ISBN 978-2-7509-0386-2, BNF 411908225
+ + + + + +
+
+ +
+
+ +
+

Navigation menu

+
+ + + + +
+ + + + + + + + +
+
+ + + + + + + + + + + +
+
+ + + + +
+ + + + + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/22-jetbrains-youtrack.html b/src/WattleScript.Tests/Templating/Tests/Html/22-jetbrains-youtrack.html new file mode 100644 index 00000000..adb7a7b8 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/22-jetbrains-youtrack.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ +
+
+
+
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/Html/22-jetbrains-youtrack.wthtml b/src/WattleScript.Tests/Templating/Tests/Html/22-jetbrains-youtrack.wthtml new file mode 100644 index 00000000..164e7fee --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/Html/22-jetbrains-youtrack.wthtml @@ -0,0 +1,73 @@ +@{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ +
+
+
+
+ +
+
+ + + + + + + + + + + +} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/1-if.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/1-if.html new file mode 100644 index 00000000..a8ea4b8d --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/1-if.html @@ -0,0 +1 @@ +
10
\ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/1-if.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/1-if.wthtml new file mode 100644 index 00000000..b01176bd --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/1-if.wthtml @@ -0,0 +1,3 @@ +@if (2 > 1) +
10
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/2-if-missing-expr.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/2-if-missing-expr.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/2-if-missing-expr.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/2-if-missing-expr.wthtml new file mode 100644 index 00000000..1a76b3ad --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/2-if-missing-expr.wthtml @@ -0,0 +1,3 @@ +@if { +
10
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/3-if-missing-expr.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/3-if-missing-expr.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/3-if-missing-expr.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/3-if-missing-expr.wthtml new file mode 100644 index 00000000..20f7e268 --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/3-if-missing-expr.wthtml @@ -0,0 +1,4 @@ +@ +@if { +
10
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/4-if-missing-expr.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/4-if-missing-expr.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/4-if-missing-expr.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/4-if-missing-expr.wthtml new file mode 100644 index 00000000..1c9a49ef --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/4-if-missing-expr.wthtml @@ -0,0 +1,6 @@ +@{ + x = 9 +} +@if { +
10
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/5-if-missing-expr.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/5-if-missing-expr.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/5-if-missing-expr.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/5-if-missing-expr.wthtml new file mode 100644 index 00000000..78df982c --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/5-if-missing-expr.wthtml @@ -0,0 +1,6 @@ +@{ + x = 9 +} +@if (```) { +
10
+} \ No newline at end of file diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/6-if-comment.html b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/6-if-comment.html new file mode 100644 index 00000000..e69de29b diff --git a/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/6-if-comment.wthtml b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/6-if-comment.wthtml new file mode 100644 index 00000000..f035f9bd --- /dev/null +++ b/src/WattleScript.Tests/Templating/Tests/ImplicitExpr/AllowedKeyword/IfElseElseif/Invalid/6-if-comment.wthtml @@ -0,0 +1,3 @@ +@if (