Skip to content

Commit

Permalink
Improved numbers support
Browse files Browse the repository at this point in the history
  • Loading branch information
Pogromca-SCP committed Feb 27, 2024
1 parent 2f1d6bb commit de2dda5
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 326 deletions.
184 changes: 95 additions & 89 deletions SLCommandScript.Core.UnitTests/Language/LexerTests.cs

Large diffs are not rendered by default.

275 changes: 127 additions & 148 deletions SLCommandScript.Core.UnitTests/Language/ParserTests.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion SLCommandScript.Core.UnitTests/ScriptUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class ScriptUtilsTests
private static readonly string[][] _errorPaths = [
["xd", "Command 'xd' was not found"],
["[ bc if bc", "Missing closing square bracket for directive"],
["[", "Directive body is invalid"]
["[", "No directive keywords were used"]
];
#endregion

Expand Down
2 changes: 1 addition & 1 deletion SLCommandScript.Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class Constants
/// <summary>
/// Contains current project version.
/// </summary>
public const string ProjectVersion = "0.7.0";
public const string ProjectVersion = "0.8.0";

/// <summary>
/// Contains project author.
Expand Down
64 changes: 55 additions & 9 deletions SLCommandScript.Core/Language/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class Lexer
/// </summary>
/// <param name="ch">Character to check.</param>
/// <returns><see langword="true" /> if character is a whitespace, <see langword="false" /> otherwise.</returns>
public static bool IsWhiteSpace(char ch) => char.IsWhiteSpace(ch) || ch == '\0';
public static bool IsWhiteSpace(char ch) => ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r' || ch == '\0';

/// <summary>
/// Checks if provided character is a digit.
Expand Down Expand Up @@ -498,6 +498,18 @@ private bool ScanToken()
case '\\':
LineExtend();
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
Number(ch);
break;
case ' ':
case '\r':
case '\t':
Expand All @@ -522,14 +534,16 @@ private bool ScanToken()
/// Adds new token to tokens list using current token as value.
/// </summary>
/// <param name="type">Type of token to add.</param>
private void AddToken(TokenType type) => AddToken(type, Source.Substring(_start, _current - _start));
/// <param name="value">Numeric token value to assign.</param>
private void AddToken(TokenType type, int value = 0) => AddToken(type, Source.Substring(_start, _current - _start), value);

/// <summary>
/// Adds new token to tokens list using specific value.
/// </summary>
/// <param name="type">Type of token to add.</param>
/// <param name="text">Token value to assign.</param>
private void AddToken(TokenType type, string text) => _tokens.Add(new(type, text, Line));
/// <param name="value">Numeric token value to assign.</param>
private void AddToken(TokenType type, string text, int value = 0) => _tokens.Add(new(type, text, value));

/// <summary>
/// Merges current token with prefix if it exists.
Expand Down Expand Up @@ -612,7 +626,7 @@ private void Guard()
}
else if (Match('?'))
{
AddToken(TokenType.ScopeGuard, null);
AddToken(TokenType.ScopeGuard);
action = Identifier;
}

Expand Down Expand Up @@ -675,6 +689,38 @@ private void LineExtend()
Text(true);
}

/// <summary>
/// Processes numeric tokens.
/// </summary>
/// <param name="ch">Initial digit character.</param>
private void Number(char ch)
{
if (_hasMissingPerms)
{
SkipUntilGuard();
return;
}

var value = ch - '0';

while (IsDigit(Current))
{
value *= 10;
value += Source[_current] - '0';
++_current;
}

var isPercentage = Match('%');

if (IsWhiteSpace(Current))
{
AddToken(isPercentage ? TokenType.Percentage : TokenType.Number, value);
return;
}

Text(true);
}

/// <summary>
/// Processes text based tokens.
/// </summary>
Expand Down Expand Up @@ -939,7 +985,7 @@ private TokenType Inject1TokenArg(Lexer lexer, int startedAt, bool isVarText)

if (isEnd)
{
AddToken(token.Type, token.Value);
AddToken(token.Type, token.Value, token.NumericValue);
return TokenType.None;
}

Expand All @@ -964,7 +1010,7 @@ private TokenType Inject2TokensArg(Lexer lexer, int startedAt, bool isVarText)
if (IsWhiteSpace(lexer.Source[0]))
{
AddToken(isVarText ? TokenType.Variable : TokenType.Text, GetTextWithPrefix(startedAt));
AddToken(token.Type, token.Value);
AddToken(token.Type, token.Value, token.NumericValue);
}
else
{
Expand All @@ -974,14 +1020,14 @@ private TokenType Inject2TokensArg(Lexer lexer, int startedAt, bool isVarText)
}
else
{
AddToken(token.Type, token.Value);
AddToken(token.Type, token.Value, token.NumericValue);
}

var lastToken = lexer._tokens[1];

if (IsWhiteSpace(Current) || IsWhiteSpace(lexer.Source[lexer.Source.Length - 1]))
{
AddToken(lastToken.Type, lastToken.Value);
AddToken(lastToken.Type, lastToken.Value, lastToken.NumericValue);
return TokenType.None;
}

Expand Down Expand Up @@ -1024,7 +1070,7 @@ private TokenType InjectNTokensArg(Lexer lexer, int startedAt, bool isVarText)

foreach (var token in tokens)
{
AddToken(token.Type, token.Value);
AddToken(token.Type, token.Value, token.NumericValue);
}

if (isEnd)
Expand Down
74 changes: 7 additions & 67 deletions SLCommandScript.Core/Language/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private Expr Directive()

if (body is null)
{
ErrorMessage ??= "Directive body is invalid";
ErrorMessage ??= "No directive keywords were used";
return null;
}

Expand Down Expand Up @@ -368,17 +368,13 @@ private Expr ForRandom(Expr body)
++_current;
var limit = new RandomSettings(1);

if (Check(TokenType.Text))
if (!IsAtEnd && (_tokens[_current].Type == TokenType.Number || _tokens[_current].Type == TokenType.Percentage))
{
limit = _tokens[_current].Value.Length > 0 && _tokens[_current].Value[_tokens[_current].Value.Length - 1] == '%' ? new(ParsePercent()) : new(ParseNumber());
limit = _tokens[_current].Type == TokenType.Percentage ? new(_tokens[_current].NumericValue / 100.0f) : new(_tokens[_current].NumericValue);

if (!limit.IsValid)
{
if (limit.IsEmpty)
{
ErrorMessage = "Limit of random elements must be greater than 0";
}

ErrorMessage = "Limit of random elements must be greater than 0";
return null;
}

Expand Down Expand Up @@ -416,19 +412,13 @@ private DelayExpr Delay(Expr body)
return null;
}

if (!Check(TokenType.Text))
{
ErrorMessage = "Delay duration is missing";
return null;
}

var duration = ParseNumber();

if (duration < 0)
if (!Check(TokenType.Number))
{
ErrorMessage = "Delay duration is missing or is not a number";
return null;
}

var duration = _tokens[_current].NumericValue;
++_current;
string name = null;

Expand Down Expand Up @@ -513,55 +503,5 @@ private IIterable GetIterable()

return iter;
}

/// <summary>
/// Attempts to parse a percent from current token.
/// </summary>
/// <returns>Parsed percent or -1 if something went wrong.</returns>
private float ParsePercent()
{
var result = 0;
var token = _tokens[_current].Value;
var end = token.Length - 1;

for (var i = 0; i < end; ++i)
{
var ch = token[i];

if (ch < '0' || ch > '9')
{
ErrorMessage = $"Expected '{_tokens[_current].Value}' to be a percentage";
return -1;
}

result *= 10;
result += ch - '0';
}

return result / 100.0f;
}

/// <summary>
/// Attempts to parse a number from current token.
/// </summary>
/// <returns>Parsed number or -1 if something went wrong.</returns>
private int ParseNumber()
{
var result = 0;

foreach (var ch in _tokens[_current].Value)
{
if (ch < '0' || ch > '9')
{
ErrorMessage = $"Expected '{_tokens[_current].Value}' to be a number";
return -1;
}

result *= 10;
result += ch - '0';
}

return result;
}
#endregion
}
14 changes: 4 additions & 10 deletions SLCommandScript.Core/Language/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
/// </summary>
/// <param name="type">Token type to set.</param>
/// <param name="value">Value related to this token.</param>
/// <param name="line">Line number where the token was found.</param>
public readonly struct Token(TokenType type, string value, int line)
/// <param name="numeric">Numeric value related to this token.</param>
public readonly struct Token(TokenType type, string value, int numeric = 0)
{
/// <summary>
/// Contains type of the token.
Expand All @@ -19,18 +19,12 @@ public readonly struct Token(TokenType type, string value, int line)
public string Value { get; } = value ?? string.Empty;

/// <summary>
/// Contains line number where the token is located.
/// Contains a numeric value assigned to this token.
/// </summary>
public int Line { get; } = line;
public int NumericValue { get; } = numeric;

/// <summary>
/// Creates new token structure.
/// </summary>
public Token() : this(TokenType.None, null, 0) {}

/// <summary>
/// Converts this token into a human readable string.
/// </summary>
/// <returns>Human readable representation of this token.</returns>
public override string ToString() => $"{Type} '{Value}' at line {Line}";
}
2 changes: 2 additions & 0 deletions SLCommandScript.Core/Language/TokenType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public enum TokenType : byte
ScopeGuard,
Variable,
Text,
Number,
Percentage,
If,
Else,
Foreach,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class FileScriptCommandBaseTests
private static readonly string[][] _errorPaths = [
["xd", "Command 'xd' was not found\nat test.slcs:1"],
[null, "Cannot read script from file 'test.slcs'"],
["[", "Directive body is invalid\nat test.slcs:1"]
["[", "No directive keywords were used\nat test.slcs:1"]
];
#endregion

Expand Down

0 comments on commit de2dda5

Please sign in to comment.