Skip to content

Commit

Permalink
#119 - Add support of ternary operator
Browse files Browse the repository at this point in the history
  • Loading branch information
sys27 committed Aug 16, 2020
1 parent 8203fcc commit ae7f9c7
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 4 deletions.
2 changes: 2 additions & 0 deletions xFunc Grammar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ statement = unaryAssign
/ def
/ undef
/ if
/ ternary
/ for
/ while
/ exp
Expand All @@ -19,6 +20,7 @@ def = ('def' / 'define') '(' assignmentKey ',' exp ')'
undef = ('undef' / 'undefine') '(' assignmentKey ')'

if = 'if' '(' conditional ',' exp (',' exp)* ')'
ternary = conditional '?' exp ':' exp
for = 'for' '(' statement ',' exp ',' conditional ',' statement ')'
while = 'while' '(' exp ',' conditional ')'

Expand Down
3 changes: 3 additions & 0 deletions xFunc.Maths/Parser.ExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,5 +291,8 @@ private IExpression CreateUnaryMinus(IExpression operand)

private IExpression CreateMultiplication(params IExpression[] arguments)
=> new Mul(arguments);

private IExpression CreateTernary(IExpression condition, IExpression then, IExpression @else)
=> new If(condition, then, @else);
}
}
34 changes: 34 additions & 0 deletions xFunc.Maths/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private IExpression Statement(TokenReader tokenReader)
Def(tokenReader) ??
Undef(tokenReader) ??
If(tokenReader) ??
Ternary(tokenReader) ??
For(tokenReader) ??
While(tokenReader) ??
Expression(tokenReader);
Expand Down Expand Up @@ -246,6 +247,39 @@ private IExpression If(TokenReader tokenReader)
return CreateFromKeyword(@if, condition, then);
}

private IExpression Ternary(TokenReader tokenReader)
{
var scope = tokenReader.CreateScope();

var condition = ConditionalOperator(tokenReader);
if (condition == null)
{
tokenReader.Rollback(scope);

return null;
}

if (!tokenReader.Symbol(SymbolToken.QuestionMark))
{
tokenReader.Rollback(scope);

return null;
}

var then = Expression(tokenReader) ??
throw new ParseException(Resource.TernaryThenParseException);

if (!tokenReader.Symbol(SymbolToken.Colon))
throw new ParseException(Resource.TernaryColonParseException);

var @else = Expression(tokenReader) ??
throw new ParseException(Resource.TernaryElseParseException);

tokenReader.Commit();

return CreateTernary(condition, then, @else);
}

private IExpression For(TokenReader tokenReader)
{
var @for = tokenReader.Keyword(KeywordToken.For);
Expand Down
18 changes: 18 additions & 0 deletions xFunc.Maths/Resources/Resource.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions xFunc.Maths/Resources/Resource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,13 @@
<data name="ValueIsNotInteger" xml:space="preserve">
<value>The value is not an integer.</value>
</data>
<data name="TernaryThenParseException" xml:space="preserve">
<value>Cannot parse 'then' statement of ternary operator.</value>
</data>
<data name="TernaryElseParseException" xml:space="preserve">
<value>Cannot parse 'else' statement of ternary operator.</value>
</data>
<data name="TernaryColonParseException" xml:space="preserve">
<value>The ':' symbol is expected 'then' expression.</value>
</data>
</root>
9 changes: 9 additions & 0 deletions xFunc.Maths/Resources/Resource.ru.resx
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,13 @@
<data name="ValueIsNotInteger" xml:space="preserve">
<value>Значение не является целым числом.</value>
</data>
<data name="TernaryElseParseException" xml:space="preserve">
<value>'else' в тернарном операторе не может быть распарсено.</value>
</data>
<data name="TernaryThenParseException" xml:space="preserve">
<value>'then' в тернарном операторе не может быть распарсено.</value>
</data>
<data name="TernaryColonParseException" xml:space="preserve">
<value>Ожидается символ ':' после выражения 'then'.</value>
</data>
</root>
4 changes: 2 additions & 2 deletions xFunc.Maths/Tokenization/Lexer.OperatorToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ private IToken CreateOperatorToken(ref ReadOnlyMemory<char> function)
{
var span = function.Span;
var first = span[0];
var second = span.Length >= 2 ? (char?)span[1] : null;
var third = span.Length >= 3 ? (char?)span[2] : null;
var second = span.Length >= 2 ? span[1] : default;
var third = span.Length >= 3 ? span[2] : default;

var (token, size) = (first, second, third) switch
{
Expand Down
6 changes: 4 additions & 2 deletions xFunc.Maths/Tokenization/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public IEnumerable<IToken> Tokenize(string function)
while (memory.Length > 0)
{
var result = SkipWhiteSpaces(ref memory) ??
CreateSymbol(ref memory) ??
CreateNumberToken(ref memory) ??
CreateIdToken(ref memory) ??
CreateOperatorToken(ref memory);
CreateOperatorToken(ref memory) ??
CreateSymbol(ref memory);

if (result == null)
throw new TokenizeException(string.Format(CultureInfo.InvariantCulture, Resource.NotSupportedSymbol, memory.Span[0]));
Expand Down Expand Up @@ -83,6 +83,8 @@ private IToken CreateSymbol(ref ReadOnlyMemory<char> function)
',' => Comma,
'∠' => Angle,
'°' => Degree,
':' => Colon,
'?' => QuestionMark,
_ => null,
};

Expand Down
10 changes: 10 additions & 0 deletions xFunc.Maths/Tokenization/Tokens/SymbolToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,15 @@ private SymbolToken(char symbol)
/// Gets the '°' token.
/// </summary>
public static SymbolToken Degree { get; } = new SymbolToken('°');

/// <summary>
/// Gets the ':' token.
/// </summary>
public static SymbolToken Colon { get; } = new SymbolToken(':');

/// <summary>
/// Gets the '?' token.
/// </summary>
public static SymbolToken QuestionMark { get; } = new SymbolToken('?');
}
}
18 changes: 18 additions & 0 deletions xFunc.Maths/Tokenization/TokensBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,24 @@ public TokensBuilder Degree()
return Symbol(SymbolToken.Degree);
}

/// <summary>
/// Adds colon symbol token.
/// </summary>
/// <returns>The current instance of builder.</returns>
public TokensBuilder Colon()
{
return Symbol(SymbolToken.Colon);
}

/// <summary>
/// Adds question mark symbol token.
/// </summary>
/// <returns>The current instance of builder.</returns>
public TokensBuilder QuestionMark()
{
return Symbol(SymbolToken.QuestionMark);
}

/// <summary>
/// Adds variable token.
/// </summary>
Expand Down
61 changes: 61 additions & 0 deletions xFunc.Tests/ParserTests/IfTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.

using xFunc.Maths.Expressions;
using xFunc.Maths.Expressions.LogicalAndBitwise;
using xFunc.Maths.Expressions.Programming;
using xFunc.Maths.Tokenization.Tokens;
using Xunit;
Expand Down Expand Up @@ -204,5 +205,65 @@ public void IfMissingClose()

ParseErrorTest(tokens);
}

[Fact]
public void TernaryTest()
{
var tokens = Builder()
.True()
.QuestionMark()
.Number(1)
.Colon()
.Operation(OperatorToken.Minus)
.Number(1)
.Tokens;

var exp = parser.Parse(tokens);
var expected = new If(
new Bool(true),
new Number(1),
new UnaryMinus(new Number(1)));

Assert.Equal(expected, exp);
}

[Fact]
public void TernaryElseMissingTest()
{
var tokens = Builder()
.True()
.QuestionMark()
.Number(1)
.Colon()
.Tokens;

ParseErrorTest(tokens);
}

[Fact]
public void TernaryIfMissingTest()
{
var tokens = Builder()
.True()
.QuestionMark()
.Colon()
.Operation(OperatorToken.Minus)
.Number(1)
.Tokens;

ParseErrorTest(tokens);
}

[Fact]
public void TernaryColonMissingTest()
{
var tokens = Builder()
.True()
.QuestionMark()
.Number(1)
.Tokens;

ParseErrorTest(tokens);
}
}
}
16 changes: 16 additions & 0 deletions xFunc.Tests/Tokenization/LexerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,22 @@ public void IfElseNegativeTest()
Assert.Equal(expected, tokens.ToList());
}

[Fact]
public void TernaryOperatorTest()
{
var tokens = lexer.Tokenize("True ? 1 : -1");
var expected = Builder()
.True()
.QuestionMark()
.Number(1)
.Colon()
.Operation(OperatorToken.Minus)
.Number(1)
.Tokens;

Assert.Equal(expected, tokens.ToList());
}

[Fact]
public void ForTest()
{
Expand Down

0 comments on commit ae7f9c7

Please sign in to comment.