Skip to content

Commit

Permalink
added alternative for loops (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
Walperr authored Dec 2, 2023
1 parent 4be232b commit 069c9eb
Show file tree
Hide file tree
Showing 11 changed files with 483 additions and 62 deletions.
97 changes: 97 additions & 0 deletions LanguageInterpreter/Execution/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,103 @@ public Result<SyntaxException, object> Evaluate(CancellationToken token)
}
}

public override object? VisitForTo(ForToExpression expression, CancellationToken token)
{
if (token.IsCancellationRequested)
{
_errors.Add(new InterpreterException("Operation was cancelled", default));
return null;
}

var varValue = Visit(expression.Variable, token);

if (varValue is null)
return null;

var countValue = Visit(expression.Count, token);

if (countValue is null)
return null;

var variable = GetVariable(expression, expression.Variable.NameToken.Lexeme);

if (variable is null || !variable.IsDeclared)
{
_errors.Add(new UndeclaredVariableException(expression.Variable.NameToken.Lexeme, expression.Range));
return null;
}

var end = (double) countValue;

object value = Empty.Instance;

Func<bool> predicate = expression.DownToken is null
? () => (double) variable.Value! < end
: () => (double) variable.Value! >= end;

while (predicate())
{
var result = Visit(expression.Body, token);

if (result is null)
return null;

value = result;

if (expression.DownToken is null)
variable.SetValue((double) variable.Value! + 1);
else
variable.SetValue((double) variable.Value! - 1);
}

return value;
}

public override object? VisitForIn(ForInExpression expression, CancellationToken token)
{
if (token.IsCancellationRequested)
{
_errors.Add(new InterpreterException("Operation was cancelled", default));
return null;
}

var varValue = Visit(expression.Variable, token);

if (varValue is null)
return null;

var arrayValue = Visit(expression.Collection, token);

if (arrayValue is null)
return null;

var array = (Array) arrayValue;

var variable = GetVariable(expression, expression.Variable.NameToken.Lexeme);

if (variable is null || !variable.IsDeclared)
{
_errors.Add(new UndeclaredVariableException(expression.Variable.NameToken.Lexeme, expression.Range));
return null;
}

object value = Empty.Instance;

foreach (var element in array)
{
variable.SetValue(element);

var result = Visit(expression.Body, token);

if (result is null)
return null;

value = result;
}

return value;
}

public override object? VisitIf(IfExpression expression, CancellationToken token)
{
if (token.IsCancellationRequested)
Expand Down
77 changes: 77 additions & 0 deletions LanguageInterpreter/Execution/TypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,83 @@ private Result<SyntaxException, Type> Resolve(ExpressionBase expression, Cancell
return Visit(expression.Body, token);
}

public override Type? VisitForTo(ForToExpression expression, CancellationToken token)
{
if (token.IsCancellationRequested)
{
_errors.Add(new InterpreterException("Operation was cancelled", default));
return null;
}

var varType = Visit(expression.Variable, token);

if (varType is null)
return null;

if (varType != typeof(double))
{
_errors.Add(new ExpectedOtherTypeException(expression.Variable, varType, typeof(double)));
return null;
}

var countType = Visit(expression.Count, token);

if (countType is null)
return null;

if (countType != typeof(double))
{
_errors.Add(new ExpectedOtherTypeException(expression.Count, varType, typeof(double)));
return null;
}

return Visit(expression.Body, token);
}

public override Type? VisitForIn(ForInExpression expression, CancellationToken token)
{
if (token.IsCancellationRequested)
{
_errors.Add(new InterpreterException("Operation was cancelled", default));
return null;
}

var varType = Visit(expression.Variable, token);

if (varType is null)
return null;

var arrayType = Visit(expression.Collection, token);
if (arrayType is null)
return null;

if (!arrayType.IsAssignableTo(typeof(Array)))
{
_errors.Add(new InterpreterException("Expected array", expression.Collection.Range));
return null;
}

if (arrayType == typeof(double[]) && varType != typeof(double))
{
_errors.Add(new ExpectedOtherTypeException(expression.Variable, varType, typeof(double)));
return null;
}

if (arrayType == typeof(string[]) && varType != typeof(string))
{
_errors.Add(new ExpectedOtherTypeException(expression.Variable, varType, typeof(string)));
return null;
}

if (arrayType == typeof(bool[]) && varType != typeof(bool))
{
_errors.Add(new ExpectedOtherTypeException(expression.Variable, varType, typeof(bool)));
return null;
}

return Visit(expression.Body, token);
}

public override Type? VisitIf(IfExpression expression, CancellationToken token)
{
if (token.IsCancellationRequested)
Expand Down
33 changes: 33 additions & 0 deletions LanguageParser.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public void CanParseForExpressions()
var texts = new[]
{
"for (;;) {}", "for (number i = 0; i <= 20; i = i + 1) callFunc()", "for (; true; step()) {}",
"for string i in [1,2,3] {}", "for number i = 0 to 100 {}", "for number i = 100 down to 10 {}",
"for (;;) {};", "for (number i = 0; i <= 20; i = i + 1) callFunc();", "for (; true; step()) {};"
};

Expand Down Expand Up @@ -765,6 +766,38 @@ public void CanUsePredefinedFunctionsAndVariablesInLoops()
Assert.NotNull(result.Value);
Assert.Null(result.Error);
}

[Fact]
public void CanEvaluateForLoops()
{
var printFunction = new Function("print",
new[] { new Variable("text", typeof(object)) },
args => _testOutputHelper.WriteLine(args.First().ToString()));

var interpreter = InterpreterBuilder.CreateBuilder()
.WithPredefinedFunction(printFunction)
.Build();

Assert.NotNull(interpreter);

const string text =
"for number i = 0 to 10 print(i) for number i = 10 down to 1 print(i) for string s in ['hello', 'hi', 'world', 'some string'] print(s) for number i in [2, 3, 6, 7] print(i) for bool b in [true, false, true, true, false, false] print(b)";

_testOutputHelper.WriteLine(text);
_testOutputHelper.WriteLine("\nresult:\n");

interpreter.Initialize(text);

if (interpreter.HasErrors)
_testOutputHelper.WriteLine(interpreter.Error.Message);

Assert.False(interpreter.HasErrors);

var result = interpreter.Interpret(CancellationToken.None);

Assert.NotNull(result.Value);
Assert.Null(result.Error);
}

[Fact]
public void CanCalculateDivision()
Expand Down
22 changes: 21 additions & 1 deletion LanguageParser/Common/SyntaxKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ public enum SyntaxKind
/// </summary>
For,
/// <summary>
/// to keyword
/// </summary>
To,
/// <summary>
/// down keyword
/// </summary>
Down,
/// <summary>
/// in keyword
/// </summary>
In,
/// <summary>
/// while keyword
/// </summary>
While,
Expand Down Expand Up @@ -264,14 +276,22 @@ public enum SyntaxKind
/// </summary>
WhileExpression,
/// <summary>
/// repeat expression until (b)
/// repeat n times expression
/// </summary>
RepeatExpression,
/// <summary>
/// for (a; b; c) expression
/// </summary>
ForExpression,
/// <summary>
/// for i in array expression
/// </summary>
ForInExpression,
/// <summary>
/// for i = a to b expression
/// </summary>
ForToExpression,
/// <summary>
/// { expression; expression; ... }
/// </summary>
ScopeExpression,
Expand Down
43 changes: 43 additions & 0 deletions LanguageParser/Expressions/ForInExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using LanguageParser.Common;
using LanguageParser.Interfaces;
using LanguageParser.Lexer;
using LanguageParser.Visitors;

namespace LanguageParser.Expressions;

public sealed class ForInExpression : ExpressionBase
{
public Token ForToken { get; }
public VariableExpression Variable { get; }
public Token InToken { get; }
public ExpressionBase Collection { get; }
public ExpressionBase Body { get; }

internal ForInExpression(Token forToken, VariableExpression variable, Token inToken, ExpressionBase collection, ExpressionBase body) : base(SyntaxKind.ForInExpression)
{
ForToken = forToken;
Variable = variable;
InToken = inToken;
Collection = collection;
Body = body;
}

public override IEnumerable<ISyntaxElement> GetAllElements()
{
yield return ForToken;
yield return Variable;
yield return InToken;
yield return Collection;
yield return Body;
}

public override void Visit(ExpressionVisitor visitor)
{
visitor.VisitForIn(this);
}

public override T Visit<T, TState>(ExpressionVisitor<T, TState> visitor, TState state)
{
return visitor.VisitForIn(this, state);
}
}
47 changes: 47 additions & 0 deletions LanguageParser/Expressions/ForToExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using LanguageParser.Common;
using LanguageParser.Interfaces;
using LanguageParser.Lexer;
using LanguageParser.Visitors;

namespace LanguageParser.Expressions;

public sealed class ForToExpression : ExpressionBase
{
public Token ForToken { get; }
public VariableExpression Variable { get; }
public Token? DownToken { get; }
public Token ToToken { get; }
public ExpressionBase Count { get; }
public ExpressionBase Body { get; }

internal ForToExpression(Token forToken, VariableExpression variable, Token? downToken, Token toToken, ExpressionBase count, ExpressionBase body) : base(SyntaxKind.ForToExpression)
{
ForToken = forToken;
Variable = variable;
DownToken = downToken;
ToToken = toToken;
Count = count;
Body = body;
}

public override IEnumerable<ISyntaxElement> GetAllElements()
{
yield return ForToken;
yield return Variable;
if (DownToken is not null)
yield return DownToken;
yield return ToToken;
yield return Count;
yield return Body;
}

public override void Visit(ExpressionVisitor visitor)
{
visitor.VisitForTo(this);
}

public override T Visit<T, TState>(ExpressionVisitor<T, TState> visitor, TState state)
{
return visitor.VisitForTo(this, state);
}
}
3 changes: 3 additions & 0 deletions LanguageParser/Lexer/Syntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public static string GetLexemeForToken(SyntaxKind kind)
SyntaxKind.Times => "times",
SyntaxKind.While => "while",
SyntaxKind.For => "for",
SyntaxKind.To => "to",
SyntaxKind.Down => "down",
SyntaxKind.In => "in",
SyntaxKind.Number => "number",
SyntaxKind.String => "string",
SyntaxKind.Bool => "bool",
Expand Down
Loading

0 comments on commit 069c9eb

Please sign in to comment.