Compare commits

...

2 Commits

Author SHA1 Message Date
Sen ec8ff367c0 Merge branch 'master' of git.touhou.dev:StellatedCUBE/ShintenScript into master 2023-02-02 14:06:16 +00:00
Sen abbd4a74ef Parser stuff 2023-02-02 14:05:53 +00:00
25 changed files with 1044 additions and 32 deletions

20
ASTNodeAdd.cs Normal file
View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ASTNodeAdd : ASTNodeBinaryNumericOperator
{
public ASTNodeAdd(IASTNode lhs, IASTNode rhs) : base(lhs, rhs) { }
public override object CreateFunction11(Func<ExecutionContext, float> left, Func<ExecutionContext, float> right)
{
return (Func<ExecutionContext, float>)(ctx => left(ctx) + right(ctx));
}
public override string Op() => "+";
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public abstract class ASTNodeBinaryNumericOperator : IASTNode
{
public IASTNode lhs, rhs;
protected ASTNodeBinaryNumericOperator(IASTNode lhs, IASTNode rhs)
{
this.lhs = lhs;
this.rhs = rhs;
}
public bool Const()
{
return lhs.Const() && rhs.Const();
}
public object CreateFunction()
{
SSType lhst = lhs.Type();
SSType rhst = rhs.Type();
if (lhst == SSType.real && rhst == SSType.real)
{
Func<ExecutionContext, float> left = (Func<ExecutionContext, float>)lhs.CreateFunction();
Func<ExecutionContext, float> right = (Func<ExecutionContext, float>)rhs.CreateFunction();
return CreateFunction11(left, right);
}
throw new Exception("This should be unreachable");
}
public SSType Type()
{
SSType lhst = lhs.Type();
SSType rhst = rhs.Type();
if (lhst == SSType.real && rhst == SSType.real)
return SSType.real;
throw new TypeException($"Unsupported types for operator '{Op()}': {lhst} and {rhst}");
}
public abstract string Op();
public abstract object CreateFunction11(Func<ExecutionContext, float> left, Func<ExecutionContext, float> right);
}
}

96
ASTNodeBlock.cs Normal file
View File

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ASTNodeBlock : IASTNode
{
public IList<IASTNode> statements;
public ASTNodeBlock(IList<IASTNode> statements)
{
this.statements = statements;
}
bool IASTNode.Const() => false;
object IASTNode.CreateFunction()
{
SSType type = ((IASTNode)this).Type();
if (type == SSType.none)
{
List<Action<ExecutionContext>> actions = new List<Action<ExecutionContext>>();
foreach (IASTNode statement in statements)
{
object func = statement.CreateFunction();
if (func is Action<ExecutionContext> action)
actions.Add(action);
else
{
Func<ExecutionContext, object> ofunc = (Func<ExecutionContext, object>)func;
actions.Add(ctx => ofunc.DynamicInvoke(ctx));
}
}
return (Action<ExecutionContext>)(ctx =>
{
foreach (Action<ExecutionContext> action in actions)
action(ctx);
});
}
List<Action<ExecutionContext>> actionsm1 = new List<Action<ExecutionContext>>();
foreach (IASTNode statement in statements)
{
if (actionsm1.Count == statements.Count - 1)
break;
object func = statement.CreateFunction();
if (func is Action<ExecutionContext> action)
actionsm1.Add(action);
else
{
Delegate ofunc = (Delegate)func;
actionsm1.Add(ctx => ofunc.DynamicInvoke(ctx));
}
}
if (type == SSType.real)
{
Func<ExecutionContext, float> final = (Func<ExecutionContext, float>)statements[statements.Count - 1].CreateFunction();
return (Func<ExecutionContext, float>)(ctx =>
{
foreach (Action<ExecutionContext> action in actionsm1)
action(ctx);
return final(ctx);
});
}
if (type == SSType.boolean)
{
Func<ExecutionContext, bool> final = (Func<ExecutionContext, bool>)statements[statements.Count - 1].CreateFunction();
return (Func<ExecutionContext, bool>)(ctx =>
{
foreach (Action<ExecutionContext> action in actionsm1)
action(ctx);
return final(ctx);
});
}
throw new Exception("This should be unreachable");
}
SSType IASTNode.Type()
{
return statements[statements.Count - 1].Type();
}
}
}

134
ASTNodeComparison.cs Normal file
View File

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ASTNodeComparison : IASTNode
{
public readonly IList<IASTNode> nodes;
public readonly IList<Token.Type> comparisons;
public ASTNodeComparison(IList<IASTNode> nodes, IList<Token.Type> comparisons)
{
this.nodes = nodes;
this.comparisons = comparisons;
}
public bool Const()
{
return nodes.All(node => node.Const());
}
public object CreateFunction()
{
SSType argType = nodes[0].Type();
if (comparisons.Count == 1)
{
if (argType == SSType.boolean)
{
Func<ExecutionContext, bool> left = (Func<ExecutionContext, bool>)nodes[0].CreateFunction();
Func<ExecutionContext, bool> right = (Func<ExecutionContext, bool>)nodes[1].CreateFunction();
switch (comparisons[0])
{
case Token.Type.EQ: return (Func<ExecutionContext, bool>)(ctx => left(ctx) == right(ctx));
case Token.Type.NE: return (Func<ExecutionContext, bool>)(ctx => left(ctx) != right(ctx));
}
}
if (argType == SSType.real)
{
Func<ExecutionContext, float> left = (Func<ExecutionContext, float>)nodes[0].CreateFunction();
Func<ExecutionContext, float> right = (Func<ExecutionContext, float>)nodes[1].CreateFunction();
switch (comparisons[0])
{
case Token.Type.EQ: return (Func<ExecutionContext, bool>)(ctx => left(ctx) == right(ctx));
case Token.Type.NE: return (Func<ExecutionContext, bool>)(ctx => left(ctx) != right(ctx));
case Token.Type.LT: return (Func<ExecutionContext, bool>)(ctx => left(ctx) < right(ctx));
case Token.Type.GT: return (Func<ExecutionContext, bool>)(ctx => left(ctx) > right(ctx));
case Token.Type.LE: return (Func<ExecutionContext, bool>)(ctx => left(ctx) <= right(ctx));
case Token.Type.GE: return (Func<ExecutionContext, bool>)(ctx => left(ctx) >= right(ctx));
}
}
throw new Exception("This should be unreachable");
}
if (argType == SSType.boolean)
{
Func<ExecutionContext, bool>[] args = nodes.Select(node => (Func<ExecutionContext, bool>)node.CreateFunction()).ToArray();
return (Func<ExecutionContext, bool>)(ctx =>
{
bool against = args[0](ctx);
for (int i = 1; i < args.Length; i++)
{
bool vs = args[i](ctx);
if ((against == vs) ^ (comparisons[i - 1] == Token.Type.EQ))
return false;
against = vs;
}
return true;
});
}
if (argType == SSType.real)
{
Func<ExecutionContext, float>[] args = nodes.Select(node => (Func<ExecutionContext, float>)node.CreateFunction()).ToArray();
return (Func<ExecutionContext, bool>)(ctx =>
{
float against = args[0](ctx);
for (int i = 1; i < args.Length; i++)
{
float vs = args[i](ctx);
switch (comparisons[i - 1])
{
case Token.Type.EQ: if (vs != against) return false; break;
case Token.Type.NE: if (vs == against) return false; break;
case Token.Type.GT: if (vs >= against) return false; break;
case Token.Type.LT: if (vs <= against) return false; break;
case Token.Type.GE: if (vs > against) return false; break;
case Token.Type.LE: if (vs < against) return false; break;
}
against = vs;
}
return true;
});
}
throw new Exception("This should be unreachable");
}
public SSType Type()
{
bool ord = !comparisons.All(cmp => cmp == Token.Type.EQ || cmp == Token.Type.NE);
SSType argType = nodes[0].Type();
if (argType == SSType.none)
throw new TypeException("Cannot compare null");
if (nodes.Skip(1).Any(node => node.Type() != argType))
throw new TypeException("Cannot compare different types");
if (ord && argType != SSType.real)
throw new TypeException($"Type {argType} cannot be ordered");
return SSType.boolean;
}
}
}

20
ASTNodeDivide.cs Normal file
View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ASTNodeDivide : ASTNodeBinaryNumericOperator
{
public ASTNodeDivide(IASTNode lhs, IASTNode rhs) : base(lhs, rhs) { }
public override object CreateFunction11(Func<ExecutionContext, float> left, Func<ExecutionContext, float> right)
{
return (Func<ExecutionContext, float>)(ctx => left(ctx) / right(ctx));
}
public override string Op() => "/";
}
}

46
ASTNodeLiteral.cs Normal file
View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ASTNodeLiteral : IASTNode
{
public readonly object value;
public ASTNodeLiteral(object value)
{
this.value = value;
}
public bool Const() => true;
public object CreateFunction()
{
SSType type = Type();
if (type == SSType.none) return (Action<ExecutionContext>)(ctx => { });
if (type == SSType.boolean) return (bool)value ? (Func<ExecutionContext, bool>)(ctx => true) : ctx => false;
if (type == SSType.real)
{
float real = (float)value;
return (Func<ExecutionContext, float>)(ctx => real);
}
throw new Exception("This should be unreachable");
}
public SSType Type()
{
if (value == null) return SSType.none;
if (value is bool) return SSType.boolean;
if (value is float) return SSType.real;
throw new Exception("This should be unreachable");
}
}
}

20
ASTNodeMultiply.cs Normal file
View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ASTNodeMultiply : ASTNodeBinaryNumericOperator
{
public ASTNodeMultiply(IASTNode lhs, IASTNode rhs) : base(lhs, rhs) { }
public override object CreateFunction11(Func<ExecutionContext, float> left, Func<ExecutionContext, float> right)
{
return (Func<ExecutionContext, float>)(ctx => left(ctx) * right(ctx));
}
public override string Op() => "*";
}
}

37
ASTNodeNegative.cs Normal file
View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ASTNodeNegative : IASTNode
{
public IASTNode node;
public ASTNodeNegative(IASTNode of)
{
node = of;
}
public bool Const()
{
return node.Const();
}
public object CreateFunction()
{
Func<ExecutionContext, float> func = (Func<ExecutionContext, float>)node.CreateFunction();
return (Func<ExecutionContext, float>)(ctx => -func(ctx));
}
public SSType Type()
{
if (node.Type() != SSType.real)
throw new TypeException($"Unary '-' operator not supported for {node.Type()}");
return SSType.real;
}
}
}

20
ASTNodeSubtract.cs Normal file
View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ASTNodeSubtract : ASTNodeBinaryNumericOperator
{
public ASTNodeSubtract(IASTNode lhs, IASTNode rhs) : base(lhs, rhs) { }
public override object CreateFunction11(Func<ExecutionContext, float> left, Func<ExecutionContext, float> right)
{
return (Func<ExecutionContext, float>)(ctx => left(ctx) - right(ctx));
}
public override string Op() => "-";
}
}

View File

@ -390,19 +390,19 @@ a.percentagebar {
<col />
</colgroup>
<tbody>
<tr><th>Generated on:</th><td>1/27/2023 - 10:02:10 AM</td></tr>
<tr><th>Generated on:</th><td>2/2/2023 - 2:01:44 PM</td></tr>
<tr><th>Parser:</th><td>CoberturaParser</td></tr>
<tr><th>Assemblies:</th><td>2</td></tr>
<tr><th>Classes:</th><td>4</td></tr>
<tr><th>Files:</th><td>4</td></tr>
<tr><th>Covered lines:</th><td>165</td></tr>
<tr><th>Uncovered lines:</th><td>50</td></tr>
<tr><th>Coverable lines:</th><td>215</td></tr>
<tr><th>Total lines:</th><td>355</td></tr>
<tr><th>Line coverage:</th><td>76.7% (165 of 215)</td></tr>
<tr><th>Covered branches:</th><td>44</td></tr>
<tr><th>Total branches:</th><td>68</td></tr>
<tr><th>Branch coverage:</th><td>64.7% (44 of 68)</td></tr>
<tr><th>Classes:</th><td>20</td></tr>
<tr><th>Files:</th><td>20</td></tr>
<tr><th>Covered lines:</th><td>532</td></tr>
<tr><th>Uncovered lines:</th><td>188</td></tr>
<tr><th>Coverable lines:</th><td>720</td></tr>
<tr><th>Total lines:</th><td>1289</td></tr>
<tr><th>Line coverage:</th><td>73.8% (532 of 720)</td></tr>
<tr><th>Covered branches:</th><td>112</td></tr>
<tr><th>Total branches:</th><td>232</td></tr>
<tr><th>Branch coverage:</th><td>48.2% (112 of 232)</td></tr>
</tbody>
</table>
<h1>Risk Hotspots</h1>
@ -427,16 +427,32 @@ a.percentagebar {
</colgroup>
<thead><tr><th>Name</th><th class="right">Covered</th><th class="right">Uncovered</th><th class="right">Coverable</th><th class="right">Total</th><th class="center" colspan="2">Line coverage</th><th class="right">Covered</th><th class="right">Total</th><th class="center" colspan="2">Branch coverage</th></tr></thead>
<tbody>
<tr><th>ShintenScript</th><th class="right">113</th><th class="right">50</th><th class="right">163</th><th class="right">288</th><th title="LineCoverage: 113/163" class="right">69.3%</th><th><table class="coverage"><tr><td class="green covered69">&nbsp;</td><td class="red covered31">&nbsp;</td></tr></table></th><th class="right">44</th><th class="right">68</th><th class="right" title="44/68">64.7%</th><th><table class="coverage"><tr><td class="green covered65">&nbsp;</td><td class="red covered35">&nbsp;</td></tr></table></th></tr>
<tr><td><a href="ShintenScript_Lexer.html">ShintenScript.Lexer</a></td><td class="right">105</td><td class="right">40</td><td class="right">145</td><td class="right">217</td><td title="LineCoverage: 105/145" class="right">72.4%</td><td><table class="coverage"><tr><td class="green covered72">&nbsp;</td><td class="red covered28">&nbsp;</td></tr></table></td><td class="right">35</td><td class="right">50</td><td class="right" title="35/50">70%</td><td><table class="coverage"><tr><td class="green covered70">&nbsp;</td><td class="red covered30">&nbsp;</td></tr></table></td></tr>
<tr><th>ShintenScript</th><th class="right">320</th><th class="right">181</th><th class="right">501</th><th class="right">939</th><th title="LineCoverage: 320/501" class="right">63.8%</th><th><table class="coverage"><tr><td class="green covered64">&nbsp;</td><td class="red covered36">&nbsp;</td></tr></table></th><th class="right">103</th><th class="right">216</th><th class="right" title="103/216">47.6%</th><th><table class="coverage"><tr><td class="green covered48">&nbsp;</td><td class="red covered52">&nbsp;</td></tr></table></th></tr>
<tr><td><a href="ShintenScript_ASTNodeAdd.html">ShintenScript.ASTNodeAdd</a></td><td class="right">4</td><td class="right">1</td><td class="right">5</td><td class="right">20</td><td title="LineCoverage: 4/5" class="right">80%</td><td><table class="coverage"><tr><td class="green covered80">&nbsp;</td><td class="red covered20">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ASTNodeBinaryNumericOperator.html">ShintenScript.ASTNodeBinaryNumericOperator</a></td><td class="right">20</td><td class="right">5</td><td class="right">25</td><td class="right">53</td><td title="LineCoverage: 20/25" class="right">80%</td><td><table class="coverage"><tr><td class="green covered80">&nbsp;</td><td class="red covered20">&nbsp;</td></tr></table></td><td class="right">4</td><td class="right">10</td><td class="right" title="4/10">40%</td><td><table class="coverage"><tr><td class="green covered40">&nbsp;</td><td class="red covered60">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ASTNodeBlock.html">ShintenScript.ASTNodeBlock</a></td><td class="right">33</td><td class="right">30</td><td class="right">63</td><td class="right">96</td><td title="LineCoverage: 33/63" class="right">52.3%</td><td><table class="coverage"><tr><td class="green covered52">&nbsp;</td><td class="red covered48">&nbsp;</td></tr></table></td><td class="right">5</td><td class="right">12</td><td class="right" title="5/12">41.6%</td><td><table class="coverage"><tr><td class="green covered42">&nbsp;</td><td class="red covered58">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ASTNodeComparison.html">ShintenScript.ASTNodeComparison</a></td><td class="right">5</td><td class="right">86</td><td class="right">91</td><td class="right">134</td><td title="LineCoverage: 5/91" class="right">5.4%</td><td><table class="coverage"><tr><td class="green covered5">&nbsp;</td><td class="red covered95">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">64</td><td class="right" title="0/64">0%</td><td><table class="coverage"><tr><td class="red covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ASTNodeDivide.html">ShintenScript.ASTNodeDivide</a></td><td class="right">4</td><td class="right">1</td><td class="right">5</td><td class="right">20</td><td title="LineCoverage: 4/5" class="right">80%</td><td><table class="coverage"><tr><td class="green covered80">&nbsp;</td><td class="red covered20">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ASTNodeLiteral.html">ShintenScript.ASTNodeLiteral</a></td><td class="right">18</td><td class="right">3</td><td class="right">21</td><td class="right">46</td><td title="LineCoverage: 18/21" class="right">85.7%</td><td><table class="coverage"><tr><td class="green covered86">&nbsp;</td><td class="red covered14">&nbsp;</td></tr></table></td><td class="right">10</td><td class="right">20</td><td class="right" title="10/20">50%</td><td><table class="coverage"><tr><td class="green covered50">&nbsp;</td><td class="red covered50">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ASTNodeMultiply.html">ShintenScript.ASTNodeMultiply</a></td><td class="right">4</td><td class="right">1</td><td class="right">5</td><td class="right">20</td><td title="LineCoverage: 4/5" class="right">80%</td><td><table class="coverage"><tr><td class="green covered80">&nbsp;</td><td class="red covered20">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ASTNodeNegative.html">ShintenScript.ASTNodeNegative</a></td><td class="right">12</td><td class="right">4</td><td class="right">16</td><td class="right">37</td><td title="LineCoverage: 12/16" class="right">75%</td><td><table class="coverage"><tr><td class="green covered75">&nbsp;</td><td class="red covered25">&nbsp;</td></tr></table></td><td class="right">1</td><td class="right">2</td><td class="right" title="1/2">50%</td><td><table class="coverage"><tr><td class="green covered50">&nbsp;</td><td class="red covered50">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ASTNodeSubtract.html">ShintenScript.ASTNodeSubtract</a></td><td class="right">4</td><td class="right">1</td><td class="right">5</td><td class="right">20</td><td title="LineCoverage: 4/5" class="right">80%</td><td><table class="coverage"><tr><td class="green covered80">&nbsp;</td><td class="red covered20">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_Lexer.html">ShintenScript.Lexer</a></td><td class="right">132</td><td class="right">20</td><td class="right">152</td><td class="right">228</td><td title="LineCoverage: 132/152" class="right">86.8%</td><td><table class="coverage"><tr><td class="green covered87">&nbsp;</td><td class="red covered13">&nbsp;</td></tr></table></td><td class="right">51</td><td class="right">62</td><td class="right" title="51/62">82.2%</td><td><table class="coverage"><tr><td class="green covered82">&nbsp;</td><td class="red covered18">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_LexingException.html">ShintenScript.LexingException</a></td><td class="right">0</td><td class="right">3</td><td class="right">3</td><td class="right">16</td><td title="LineCoverage: 0/3" class="right">0%</td><td><table class="coverage"><tr><td class="red covered100">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_Token.html">ShintenScript.Token</a></td><td class="right">8</td><td class="right">7</td><td class="right">15</td><td class="right">55</td><td title="LineCoverage: 8/15" class="right">53.3%</td><td><table class="coverage"><tr><td class="green covered53">&nbsp;</td><td class="red covered47">&nbsp;</td></tr></table></td><td class="right">9</td><td class="right">18</td><td class="right" title="9/18">50%</td><td><table class="coverage"><tr><td class="green covered50">&nbsp;</td><td class="red covered50">&nbsp;</td></tr></table></td></tr>
<tr><th>ShintenScriptTest</th><th class="right">52</th><th class="right">0</th><th class="right">52</th><th class="right">67</th><th title="LineCoverage: 52/52" class="right">100%</th><th><table class="coverage"><tr><td class="green covered100">&nbsp;</td></tr></table></th><th class="right">0</th><th class="right">0</th><th class="right" title="-"></th><th><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></th></tr>
<tr><td><a href="ShintenScriptTest_LexerTests.html">ShintenScriptTest.LexerTests</a></td><td class="right">52</td><td class="right">0</td><td class="right">52</td><td class="right">67</td><td title="LineCoverage: 52/52" class="right">100%</td><td><table class="coverage"><tr><td class="green covered100">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_Parser.html">ShintenScript.Parser</a></td><td class="right">73</td><td class="right">13</td><td class="right">86</td><td class="right">146</td><td title="LineCoverage: 73/86" class="right">84.8%</td><td><table class="coverage"><tr><td class="green covered85">&nbsp;</td><td class="red covered15">&nbsp;</td></tr></table></td><td class="right">23</td><td class="right">28</td><td class="right" title="23/28">82.1%</td><td><table class="coverage"><tr><td class="green covered82">&nbsp;</td><td class="red covered18">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_ParsingException.html">ShintenScript.ParsingException</a></td><td class="right">0</td><td class="right">3</td><td class="right">3</td><td class="right">16</td><td title="LineCoverage: 0/3" class="right">0%</td><td><table class="coverage"><tr><td class="red covered100">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_SSType.html">ShintenScript.SSType</a></td><td class="right">3</td><td class="right">0</td><td class="right">3</td><td class="right">15</td><td title="LineCoverage: 3/3" class="right">100%</td><td><table class="coverage"><tr><td class="green covered100">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_Token.html">ShintenScript.Token</a></td><td class="right">8</td><td class="right">7</td><td class="right">15</td><td class="right">56</td><td title="LineCoverage: 8/15" class="right">53.3%</td><td><table class="coverage"><tr><td class="green covered53">&nbsp;</td><td class="red covered47">&nbsp;</td></tr></table></td><td class="right">9</td><td class="right">18</td><td class="right" title="9/18">50%</td><td><table class="coverage"><tr><td class="green covered50">&nbsp;</td><td class="red covered50">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScript_TypeException.html">ShintenScript.TypeException</a></td><td class="right">0</td><td class="right">3</td><td class="right">3</td><td class="right">16</td><td title="LineCoverage: 0/3" class="right">0%</td><td><table class="coverage"><tr><td class="red covered100">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><th>ShintenScriptTest</th><th class="right">212</th><th class="right">7</th><th class="right">219</th><th class="right">350</th><th title="LineCoverage: 212/219" class="right">96.8%</th><th><table class="coverage"><tr><td class="green covered97">&nbsp;</td><td class="red covered3">&nbsp;</td></tr></table></th><th class="right">9</th><th class="right">16</th><th class="right" title="9/16">56.2%</th><th><table class="coverage"><tr><td class="green covered56">&nbsp;</td><td class="red covered44">&nbsp;</td></tr></table></th></tr>
<tr><td><a href="ShintenScriptTest_EvaluationTest.html">ShintenScriptTest.EvaluationTest</a></td><td class="right">41</td><td class="right">0</td><td class="right">41</td><td class="right">91</td><td title="LineCoverage: 41/41" class="right">100%</td><td><table class="coverage"><tr><td class="green covered100">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScriptTest_LexerTests.html">ShintenScriptTest.LexerTests</a></td><td class="right">60</td><td class="right">0</td><td class="right">60</td><td class="right">79</td><td title="LineCoverage: 60/60" class="right">100%</td><td><table class="coverage"><tr><td class="green covered100">&nbsp;</td></tr></table></td><td class="right">0</td><td class="right">0</td><td class="right" title="-"></td><td><table class="coverage"><tr><td class="gray covered100">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScriptTest_ParserTest.html">ShintenScriptTest.ParserTest</a></td><td class="right">102</td><td class="right">7</td><td class="right">109</td><td class="right">153</td><td title="LineCoverage: 102/109" class="right">93.5%</td><td><table class="coverage"><tr><td class="green covered94">&nbsp;</td><td class="red covered6">&nbsp;</td></tr></table></td><td class="right">7</td><td class="right">14</td><td class="right" title="7/14">50%</td><td><table class="coverage"><tr><td class="green covered50">&nbsp;</td><td class="red covered50">&nbsp;</td></tr></table></td></tr>
<tr><td><a href="ShintenScriptTest_ScaffoldLexer.html">ShintenScriptTest.ScaffoldLexer</a></td><td class="right">9</td><td class="right">0</td><td class="right">9</td><td class="right">27</td><td title="LineCoverage: 9/9" class="right">100%</td><td><table class="coverage"><tr><td class="green covered100">&nbsp;</td></tr></table></td><td class="right">2</td><td class="right">2</td><td class="right" title="2/2">100%</td><td><table class="coverage"><tr><td class="green covered100">&nbsp;</td></tr></table></td></tr>
</tbody>
</table>
</coverage-info>
<div class="footer">Generated by: ReportGenerator 4.7.1.0<br />1/27/2023 - 10:02:10 AM<br /><a href="https://github.com/danielpalme/ReportGenerator">GitHub</a> | <a href="http://www.palmmedia.de">www.palmmedia.de</a></div></div></div>
<div class="footer">Generated by: ReportGenerator 4.7.1.0<br />2/2/2023 - 2:01:44 PM<br /><a href="https://github.com/danielpalme/ReportGenerator">GitHub</a> | <a href="http://www.palmmedia.de">www.palmmedia.de</a></div></div></div>
<script type="text/javascript">/* <![CDATA[ */ /* Chartist.js 0.11.0
* Copyright © 2017 Gion Kunz
* Free to use under either the WTFPL license or the MIT license.
@ -663,14 +679,30 @@ var assemblies = [
{
"name": "ShintenScript",
"classes": [
{ "name": "ShintenScript.Lexer", "rp": "ShintenScript_Lexer.html", "cl": 105, "ucl": 40, "cal": 145, "tl": 217, "ct": "LineCoverage", "mc": "-", "cb": 35, "tb": 50, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeAdd", "rp": "ShintenScript_ASTNodeAdd.html", "cl": 4, "ucl": 1, "cal": 5, "tl": 20, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeBinaryNumericOperator", "rp": "ShintenScript_ASTNodeBinaryNumericOperator.html", "cl": 20, "ucl": 5, "cal": 25, "tl": 53, "ct": "LineCoverage", "mc": "-", "cb": 4, "tb": 10, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeBlock", "rp": "ShintenScript_ASTNodeBlock.html", "cl": 33, "ucl": 30, "cal": 63, "tl": 96, "ct": "LineCoverage", "mc": "-", "cb": 5, "tb": 12, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeComparison", "rp": "ShintenScript_ASTNodeComparison.html", "cl": 5, "ucl": 86, "cal": 91, "tl": 134, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 64, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeDivide", "rp": "ShintenScript_ASTNodeDivide.html", "cl": 4, "ucl": 1, "cal": 5, "tl": 20, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeLiteral", "rp": "ShintenScript_ASTNodeLiteral.html", "cl": 18, "ucl": 3, "cal": 21, "tl": 46, "ct": "LineCoverage", "mc": "-", "cb": 10, "tb": 20, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeMultiply", "rp": "ShintenScript_ASTNodeMultiply.html", "cl": 4, "ucl": 1, "cal": 5, "tl": 20, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeNegative", "rp": "ShintenScript_ASTNodeNegative.html", "cl": 12, "ucl": 4, "cal": 16, "tl": 37, "ct": "LineCoverage", "mc": "-", "cb": 1, "tb": 2, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ASTNodeSubtract", "rp": "ShintenScript_ASTNodeSubtract.html", "cl": 4, "ucl": 1, "cal": 5, "tl": 20, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.Lexer", "rp": "ShintenScript_Lexer.html", "cl": 132, "ucl": 20, "cal": 152, "tl": 228, "ct": "LineCoverage", "mc": "-", "cb": 51, "tb": 62, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.LexingException", "rp": "ShintenScript_LexingException.html", "cl": 0, "ucl": 3, "cal": 3, "tl": 16, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.Token", "rp": "ShintenScript_Token.html", "cl": 8, "ucl": 7, "cal": 15, "tl": 55, "ct": "LineCoverage", "mc": "-", "cb": 9, "tb": 18, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.Parser", "rp": "ShintenScript_Parser.html", "cl": 73, "ucl": 13, "cal": 86, "tl": 146, "ct": "LineCoverage", "mc": "-", "cb": 23, "tb": 28, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.ParsingException", "rp": "ShintenScript_ParsingException.html", "cl": 0, "ucl": 3, "cal": 3, "tl": 16, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.SSType", "rp": "ShintenScript_SSType.html", "cl": 3, "ucl": 0, "cal": 3, "tl": 15, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.Token", "rp": "ShintenScript_Token.html", "cl": 8, "ucl": 7, "cal": 15, "tl": 56, "ct": "LineCoverage", "mc": "-", "cb": 9, "tb": 18, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScript.TypeException", "rp": "ShintenScript_TypeException.html", "cl": 0, "ucl": 3, "cal": 3, "tl": 16, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
]},
{
"name": "ShintenScriptTest",
"classes": [
{ "name": "ShintenScriptTest.LexerTests", "rp": "ShintenScriptTest_LexerTests.html", "cl": 52, "ucl": 0, "cal": 52, "tl": 67, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScriptTest.EvaluationTest", "rp": "ShintenScriptTest_EvaluationTest.html", "cl": 41, "ucl": 0, "cal": 41, "tl": 91, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScriptTest.LexerTests", "rp": "ShintenScriptTest_LexerTests.html", "cl": 60, "ucl": 0, "cal": 60, "tl": 79, "ct": "LineCoverage", "mc": "-", "cb": 0, "tb": 0, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScriptTest.ParserTest", "rp": "ShintenScriptTest_ParserTest.html", "cl": 102, "ucl": 7, "cal": 109, "tl": 153, "ct": "LineCoverage", "mc": "-", "cb": 7, "tb": 14, "lch": [], "bch": [], "hc": [] },
{ "name": "ShintenScriptTest.ScaffoldLexer", "rp": "ShintenScriptTest_ScaffoldLexer.html", "cl": 9, "ucl": 0, "cal": 9, "tl": 27, "ct": "LineCoverage", "mc": "-", "cb": 2, "tb": 2, "lch": [], "bch": [], "hc": [] },
]},
];

12
ExecutionContext.cs Normal file
View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ExecutionContext
{
}
}

16
IASTNode.cs Normal file
View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public interface IASTNode
{
bool Const();
SSType Type();
//IASTNode OptPass1();
object CreateFunction();
}
}

View File

@ -20,6 +20,7 @@ namespace ShintenScript
("*=", Token.Type.ASTERISKASSIGN),
("/=", Token.Type.SLASHASSIGN),
(";", Token.Type.SEMICOLON),
("+", Token.Type.PLUS),
("-", Token.Type.MINUS),
("*", Token.Type.ASTERISK),
@ -51,7 +52,7 @@ namespace ShintenScript
Token current = new Token { type = Token.Type.NULL };
Token ParseOne()
public virtual Token ParseOne()
{
if (EOF())
return new Token { type = Token.Type.EOF };
@ -77,7 +78,7 @@ namespace ShintenScript
{
StringBuilder identifierBuilder = new StringBuilder();
while (char.IsLetterOrDigit(code[index]))
while (!EOF() && char.IsLetterOrDigit(code[index]))
{
identifierBuilder.Append(code[index]);
index++;
@ -97,10 +98,15 @@ namespace ShintenScript
if (char.IsNumber(code[index]))
{
StringBuilder numberBuilder = new StringBuilder();
bool seenLetter = false;
while (char.IsLetterOrDigit(code[index]) || code[index] == '.' || (code[index] == '-' && (code[index - 1] == 'e' || code[index - 1] == 'E')))
while (!EOF() && (char.IsLetterOrDigit(code[index]) || code[index] == '.' || (!seenLetter && code[index] == '-' && (code[index - 1] == 'e' || code[index - 1] == 'E'))))
{
numberBuilder.Append(code[index]);
if (char.IsLetter(code[index]) && code[index] != 'e' && code[index] != 'E')
seenLetter = true;
index++;
}
@ -187,24 +193,29 @@ namespace ShintenScript
if (types.Length == 1)
{
throw new LexingException($"Encountered {token.type} when expecting {types[0]}");
throw new ParsingException($"Encountered {token.type} when expecting {types[0]}");
}
else if (types.Length == 2)
{
throw new LexingException($"Encountered {token.type} when expecting one of {types[0]} or {types[1]}");
throw new ParsingException($"Encountered {token.type} when expecting one of {types[0]} or {types[1]}");
}
else
{
string list = string.Join(", ", types.Take(types.Length - 1));
throw new LexingException($"Encountered {token.type} when expecting one of {list}, or {types[types.Length - 1]}");
throw new ParsingException($"Encountered {token.type} when expecting one of {list}, or {types[types.Length - 1]}");
}
}
public IEnumerable<Token> TokenStream()
{
while (!EOF())
while (true)
{
yield return Pop();
Token token = Pop();
if (token.type == Token.Type.EOF)
break;
yield return token;
}
}

146
Parser.cs Normal file
View File

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class Parser
{
protected readonly Lexer lexer;
public Parser(Lexer lexer)
{
this.lexer = lexer;
}
public IASTNode ParseExpression()
{
if (lexer.Accept(Token.Type.SEMICOLON))
{
lexer.Pop();
return new ASTNodeLiteral(null);
}
return ParseExpressionAssignLevel();
}
public IASTNode ParseExpressionAssignLevel()
{
IASTNode lhs = ParseExpressionComparisonLevel();
return lhs;
}
public IASTNode ParseExpressionComparisonLevel()
{
List<IASTNode> nodes = new List<IASTNode>();
List<Token.Type> comparisons = new List<Token.Type>();
nodes.Add(ParseExpressionAdditiveLevel());
while (lexer.Accept(Token.Type.EQ, Token.Type.NE, Token.Type.LT, Token.Type.GT, Token.Type.LE, Token.Type.GE))
{
comparisons.Add(lexer.Pop().type);
nodes.Add(ParseExpressionAdditiveLevel());
}
if (nodes.Count == 1)
return nodes[0];
return new ASTNodeComparison(nodes, comparisons);
}
public IASTNode ParseExpressionAdditiveLevel()
{
IASTNode lhs = ParseExpressionMultiplicativeLevel();
if (lexer.Accept(Token.Type.PLUS, Token.Type.MINUS))
{
bool isAddition = lexer.Pop().type == Token.Type.PLUS;
IASTNode rhs = ParseExpressionAdditiveLevel();
if (isAddition)
return new ASTNodeAdd(lhs, rhs);
else
return new ASTNodeSubtract(lhs, rhs);
}
return lhs;
}
public IASTNode ParseExpressionMultiplicativeLevel()
{
IASTNode lhs = ParseExpressionWithPrefix();
if (lexer.Accept(Token.Type.ASTERISK, Token.Type.SLASH))
{
bool isMultiplication = lexer.Pop().type == Token.Type.ASTERISK;
IASTNode rhs = ParseExpressionMultiplicativeLevel();
if (isMultiplication)
return new ASTNodeMultiply(lhs, rhs);
else
return new ASTNodeDivide(lhs, rhs);
}
return lhs;
}
public IASTNode ParseExpressionWithPrefix()
{
if (lexer.Accept(Token.Type.PLUS))
lexer.Pop();
if (lexer.Accept(Token.Type.MINUS))
{
lexer.Pop();
return new ASTNodeNegative(ParseExpressionPrimary());
}
return ParseExpressionPrimary();
}
public IASTNode ParseExpressionPrimary()
{
if (lexer.Accept(Token.Type.NUMBER))
return new ASTNodeLiteral(lexer.Pop().data);
if (lexer.Accept(Token.Type.LPAREN))
return ParseExpressionParenthesised();
if (lexer.Accept(Token.Type.LBRACE))
return ParseBlock();
throw new ParsingException("Expected expression");
}
public IASTNode ParseExpressionParenthesised()
{
lexer.Expect(Token.Type.LPAREN);
IASTNode expression = ParseExpression();
lexer.Expect(Token.Type.RPAREN);
return expression;
}
public IASTNode ParseBlock()
{
lexer.Expect(Token.Type.LBRACE);
List<IASTNode> statements = new List<IASTNode>();
while (!lexer.Accept(Token.Type.RBRACE))
{
statements.Add(ParseExpression());
}
lexer.Pop();
if (statements.Count == 0)
return new ASTNodeLiteral(null);
return new ASTNodeBlock(statements);
}
}
}

16
ParsingException.cs Normal file
View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class ParsingException : Exception
{
public ParsingException(string message) : base(message)
{
}
}
}

15
SSType.cs Normal file
View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class SSType
{
public static readonly SSType none = new SSType();
public static readonly SSType real = new SSType();
public static readonly SSType boolean = new SSType();
}
}

View File

@ -41,10 +41,25 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ASTNodeAdd.cs" />
<Compile Include="ASTNodeBinaryNumericOperator.cs" />
<Compile Include="ASTNodeBlock.cs" />
<Compile Include="ASTNodeComparison.cs" />
<Compile Include="ASTNodeDivide.cs" />
<Compile Include="ASTNodeLiteral.cs" />
<Compile Include="ASTNodeMultiply.cs" />
<Compile Include="ASTNodeNegative.cs" />
<Compile Include="ASTNodeSubtract.cs" />
<Compile Include="ExecutionContext.cs" />
<Compile Include="IASTNode.cs" />
<Compile Include="Lexer.cs" />
<Compile Include="LexingException.cs" />
<Compile Include="Parser.cs" />
<Compile Include="ParsingException.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SSType.cs" />
<Compile Include="Token.cs" />
<Compile Include="TypeException.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,91 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ShintenScript;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScriptTest
{
[TestClass]
public class EvaluationTest
{
[TestMethod]
public void TestBlock()
{
IASTNode ast = new ASTNodeBlock(new List<IASTNode>()
{
new ASTNodeLiteral(1f),
new ASTNodeLiteral(true),
new ASTNodeLiteral(5f)
});
Assert.AreEqual(SSType.real, ast.Type());
Func<ExecutionContext, float> func = (Func<ExecutionContext, float>)ast.CreateFunction();
Assert.AreEqual(5f, func(null));
}
[TestMethod]
public void TestAdd()
{
IASTNode ast = new ASTNodeAdd(new ASTNodeLiteral(2f), new ASTNodeLiteral(3f));
Assert.AreEqual(SSType.real, ast.Type());
Func<ExecutionContext, float> func = (Func<ExecutionContext, float>)ast.CreateFunction();
Assert.AreEqual(5f, func(null));
}
[TestMethod]
public void TestMul()
{
IASTNode ast = new ASTNodeMultiply(new ASTNodeLiteral(2f), new ASTNodeLiteral(3f));
Assert.AreEqual(SSType.real, ast.Type());
Func<ExecutionContext, float> func = (Func<ExecutionContext, float>)ast.CreateFunction();
Assert.AreEqual(6f, func(null));
}
[TestMethod]
public void TestSub()
{
IASTNode ast = new ASTNodeSubtract(new ASTNodeLiteral(2f), new ASTNodeLiteral(3f));
Assert.AreEqual(SSType.real, ast.Type());
Func<ExecutionContext, float> func = (Func<ExecutionContext, float>)ast.CreateFunction();
Assert.AreEqual(-1f, func(null));
}
[TestMethod]
public void TestDiv()
{
IASTNode ast = new ASTNodeDivide(new ASTNodeLiteral(7f), new ASTNodeLiteral(2f));
Assert.AreEqual(SSType.real, ast.Type());
Func<ExecutionContext, float> func = (Func<ExecutionContext, float>)ast.CreateFunction();
Assert.AreEqual(3.5f, func(null));
}
[TestMethod]
public void TestNegative()
{
IASTNode ast = new ASTNodeNegative(new ASTNodeLiteral(7f));
Assert.AreEqual(SSType.real, ast.Type());
Func<ExecutionContext, float> func = (Func<ExecutionContext, float>)ast.CreateFunction();
Assert.AreEqual(-7f, func(null));
}
}
}

View File

@ -19,6 +19,7 @@ namespace ShintenScriptTest
1.5e6
2.4e-6
;
( ) { }
+ - * /
> < == >= <= !=
@ -36,6 +37,7 @@ namespace ShintenScriptTest
new Token { type = Token.Type.NUMBER, data = 1.5e6f },
new Token { type = Token.Type.NUMBER, data = 2.4e-6f },
new Token { type = Token.Type.SEMICOLON },
new Token { type = Token.Type.LPAREN },
new Token { type = Token.Type.RPAREN },
new Token { type = Token.Type.LBRACE },
@ -59,8 +61,18 @@ namespace ShintenScriptTest
new Token { type = Token.Type.IF },
new Token { type = Token.Type.WHILE },
new Token { type = Token.Type.FN },
}));
}
new Token { type = Token.Type.EOF },
[TestMethod]
public void TestHexExponent()
{
Lexer lexer = new Lexer(@"0xcafe-1337");
Assert.IsTrue(lexer.TokenStream().SequenceEqual(new Token[] {
new Token { type = Token.Type.NUMBER, data = 51966f },
new Token { type = Token.Type.MINUS },
new Token { type = Token.Type.NUMBER, data = 1337f },
}));
}
}

View File

@ -0,0 +1,153 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ShintenScript;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScriptTest
{
[TestClass]
public class ParserTest
{
[TestMethod]
public void TestBlock()
{
Lexer lexer = new ScaffoldLexer(new Token[]
{
new Token { type = Token.Type.LBRACE },
new Token { type = Token.Type.NUMBER, data = 1f },
new Token { type = Token.Type.NUMBER, data = 2f },
new Token { type = Token.Type.NUMBER, data = 3f },
new Token { type = Token.Type.RBRACE },
});
IASTNode ast = new Parser(lexer).ParseExpression();
if (ast is ASTNodeBlock block)
{
Assert.AreEqual(3, block.statements.Count);
Assert.IsInstanceOfType(block.statements[0], typeof(ASTNodeLiteral));
Assert.IsInstanceOfType(block.statements[1], typeof(ASTNodeLiteral));
Assert.IsInstanceOfType(block.statements[2], typeof(ASTNodeLiteral));
}
else Assert.Fail();
}
[TestMethod]
public void TestComparison()
{
Lexer lexer = new ScaffoldLexer(new Token[]
{
new Token { type = Token.Type.NUMBER, data = 1f },
new Token { type = Token.Type.LE },
new Token { type = Token.Type.NUMBER, data = 2f },
new Token { type = Token.Type.NE },
new Token { type = Token.Type.NUMBER, data = 3f },
});
IASTNode ast = new Parser(lexer).ParseExpression();
if (ast is ASTNodeComparison comp)
{
Assert.AreEqual(3, comp.nodes.Count);
Assert.AreEqual(2, comp.comparisons.Count);
Assert.IsInstanceOfType(comp.nodes[0], typeof(ASTNodeLiteral));
Assert.IsInstanceOfType(comp.nodes[1], typeof(ASTNodeLiteral));
Assert.IsInstanceOfType(comp.nodes[2], typeof(ASTNodeLiteral));
Assert.AreEqual(Token.Type.LE, comp.comparisons[0]);
Assert.AreEqual(Token.Type.NE, comp.comparisons[1]);
}
else Assert.Fail();
}
[TestMethod]
public void TestDivision()
{
Lexer lexer = new ScaffoldLexer(new Token[]
{
new Token { type = Token.Type.NUMBER, data = 1f },
new Token { type = Token.Type.SLASH },
new Token { type = Token.Type.NUMBER, data = 2f },
});
IASTNode ast = new Parser(lexer).ParseExpression();
Assert.IsInstanceOfType(ast, typeof(ASTNodeDivide));
}
[TestMethod]
public void TestMinus()
{
Lexer lexer = new ScaffoldLexer(new Token[]
{
new Token { type = Token.Type.MINUS },
new Token { type = Token.Type.NUMBER, data = 2f },
new Token { type = Token.Type.MINUS },
new Token { type = Token.Type.NUMBER, data = 3f },
});
IASTNode ast = new Parser(lexer).ParseExpression();
if (ast is ASTNodeSubtract sub)
{
Assert.IsInstanceOfType(sub.lhs, typeof(ASTNodeNegative));
Assert.IsInstanceOfType(sub.rhs, typeof(ASTNodeLiteral));
}
else Assert.Fail();
}
[TestMethod]
public void TestOrderOfOperations()
{
Lexer lexer = new ScaffoldLexer(new Token[]
{
new Token { type = Token.Type.NUMBER, data = 1f },
new Token { type = Token.Type.PLUS },
new Token { type = Token.Type.NUMBER, data = 2f },
new Token { type = Token.Type.ASTERISK },
new Token { type = Token.Type.NUMBER, data = 3f },
});
IASTNode ast = new Parser(lexer).ParseExpression();
if (ast is ASTNodeAdd add)
{
Assert.IsInstanceOfType(add.lhs, typeof(ASTNodeLiteral));
if (add.rhs is ASTNodeMultiply mul)
{
Assert.IsInstanceOfType(mul.lhs, typeof(ASTNodeLiteral));
Assert.IsInstanceOfType(mul.rhs, typeof(ASTNodeLiteral));
}
else Assert.Fail();
}
else Assert.Fail();
lexer = new ScaffoldLexer(new Token[]
{
new Token { type = Token.Type.NUMBER, data = 1f },
new Token { type = Token.Type.ASTERISK },
new Token { type = Token.Type.NUMBER, data = 2f },
new Token { type = Token.Type.PLUS },
new Token { type = Token.Type.NUMBER, data = 3f },
});
ast = new Parser(lexer).ParseExpression();
if (ast is ASTNodeAdd add2)
{
Assert.IsInstanceOfType(add2.rhs, typeof(ASTNodeLiteral));
if (add2.lhs is ASTNodeMultiply mul)
{
Assert.IsInstanceOfType(mul.lhs, typeof(ASTNodeLiteral));
Assert.IsInstanceOfType(mul.rhs, typeof(ASTNodeLiteral));
}
else Assert.Fail();
}
else Assert.Fail();
}
}
}

View File

@ -0,0 +1,27 @@
using ShintenScript;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScriptTest
{
class ScaffoldLexer : Lexer
{
Queue<Token> tokenQueue;
public ScaffoldLexer(IEnumerable<Token> queue) : base("")
{
tokenQueue = new Queue<Token>(queue);
}
public override Token ParseOne()
{
if (tokenQueue.Count == 0)
return new Token { type = Token.Type.EOF };
return tokenQueue.Dequeue();
}
}
}

View File

@ -49,8 +49,11 @@
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="EvaluationTest.cs" />
<Compile Include="LexerTests.cs" />
<Compile Include="ParserTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ScaffoldLexer.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@ -1,6 +1,6 @@
LexerTests
Tests in group: 1
Total Duration: 31ms
Tests in group: 13
Total Duration: 28ms
Outcomes
1 Passed
13 Passed

View File

@ -15,6 +15,7 @@ namespace ShintenScript
IDENTIFIER,
NUMBER,
SEMICOLON,
LPAREN, RPAREN, LBRACE, RBRACE,
PLUS, MINUS, ASTERISK, SLASH,
GT, LT, EQ, GE, LE, NE,

16
TypeException.cs Normal file
View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShintenScript
{
public class TypeException : Exception
{
public TypeException(string message) : base(message)
{
}
}
}