Skip to content

Commit bea4bcd

Browse files
committed
Fixed hex num representation #128
1 parent a055a82 commit bea4bcd

11 files changed

+110
-20
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# 3.0.0
22

3+
- Fixed potential misinterpretation of fractions in hex numbers (#128)
4+
- Fixed usage of capital `X` or `B` for hex or binary representations
35
- Improved GC allocations (#81)
46
- Added wrapping of `Task` in `Future` (#64)
7+
- Added octal representation (e.g., `0o123`) for numbers
58
- Added JSX syntax (#120)
69
- Added default `jsx` and `html` function (#120)
710
- Added events to `Engine` to handle uncaught errors (#121)

doc/syntax.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ A number is the usual suspect:
2828
```
2929
binary_character ::= '0' | '1'
3030
hex_character ::= digit | [a - f] | [A - F]
31+
octal_character ::= [0 - 7]
3132
binary ::= '0' ('b' | 'B') binary_character+
3233
hex ::= '0' ('x' | 'X') hex_character+
34+
octal ::= '0' ('o' | 'O') octal_character+
3335
float ::= digit+ (. digit* (('e' | 'E') sign? digit+)?)?
34-
number ::= float | binary | hex
36+
number ::= float | binary | hex | octal
3537
```
3638

3739
Boolean primitive values are given by keywords:

src/Mages.Core.Tests/NumberTests.cs

+29
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Mages.Core.Source;
44
using Mages.Core.Tokens;
55
using NUnit.Framework;
6+
using System.Linq;
67

78
[TestFixture]
89
public class NumberTests
@@ -174,6 +175,20 @@ public void NumberScannerHex()
174175
var result = tokenizer.Next(scanner);
175176
Assert.IsInstanceOf<NumberToken>(result);
176177
Assert.AreEqual(26, ((NumberToken)result).Value);
178+
Assert.IsFalse(((NumberToken)result).Errors?.Any() ?? false);
179+
}
180+
181+
[Test]
182+
public void NumberScannerHexWithFractionInvalid_Issue128()
183+
{
184+
var source = "0x1a.1";
185+
var scanner = new StringScanner(source);
186+
Assert.IsTrue(scanner.MoveNext());
187+
var tokenizer = new NumberTokenizer();
188+
var result = tokenizer.Next(scanner);
189+
Assert.IsInstanceOf<NumberToken>(result);
190+
Assert.AreEqual(26, ((NumberToken)result).Value);
191+
Assert.IsTrue(((NumberToken)result).Errors?.Any() ?? false);
177192
}
178193

179194
[Test]
@@ -186,6 +201,20 @@ public void NumberScannerBinary()
186201
var result = tokenizer.Next(scanner);
187202
Assert.IsInstanceOf<NumberToken>(result);
188203
Assert.AreEqual(99, ((NumberToken)result).Value);
204+
Assert.IsFalse(((NumberToken)result).Errors?.Any() ?? false);
205+
}
206+
207+
[Test]
208+
public void NumberScannerBinaryWithFractionInvalid_Issue128()
209+
{
210+
var source = "0b01100011.1";
211+
var scanner = new StringScanner(source);
212+
Assert.IsTrue(scanner.MoveNext());
213+
var tokenizer = new NumberTokenizer();
214+
var result = tokenizer.Next(scanner);
215+
Assert.IsInstanceOf<NumberToken>(result);
216+
Assert.AreEqual(99, ((NumberToken)result).Value);
217+
Assert.IsTrue(((NumberToken)result).Errors?.Any() ?? false);
189218
}
190219

191220
[Test]

src/Mages.Core/Ast/Expressions/ConstantExpression.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ public static ConstantExpression From(Object value, ITextRange range)
5252
}
5353
else if (value is Double d)
5454
{
55-
return new NumberConstant(d, range, Enumerable.Empty<ParseError>());
55+
return new NumberConstant(d, range, []);
5656
}
5757
else if (value is String s)
5858
{
59-
return new StringConstant(s, range, Enumerable.Empty<ParseError>());
59+
return new StringConstant(s, range, []);
6060
}
6161

6262
throw new InvalidOperationException();

src/Mages.Core/Ast/Walkers/SymbolTreeWalker.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public IEnumerable<VariableExpression> FindAllReferences(VariableExpression symb
7171

7272
if (!_collector.TryGetValue(symbol, out references))
7373
{
74-
return Enumerable.Empty<VariableExpression>();
74+
return [];
7575
}
7676

7777
return references;

src/Mages.Core/ErrorCode.cs

+5
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,9 @@ public enum ErrorCode
237237
/// </summary>
238238
[Description("A JSX closing tag ( '</foo>' ) can only exist after an opening tag with the elements name ('<foo>' ).")]
239239
JsxElementNotOpened,
240+
/// <summary>
241+
/// See description.
242+
/// </summary>
243+
[Description("The seen dot operator is misplaced and cannot work for alternative number representations.")]
244+
DotUnexpected,
240245
}

src/Mages.Core/Source/CharacterTable.cs

+25
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,16 @@ static class CharacterTable
212212
/// </summary>
213213
public const Int32 One = 0x31;
214214

215+
/// <summary>
216+
/// The number 7.
217+
/// </summary>
218+
public const Int32 Seven = 0x37;
219+
220+
/// <summary>
221+
/// The letter E.
222+
/// </summary>
223+
public const Int32 BigB = 0x42;
224+
215225
/// <summary>
216226
/// The letter E.
217227
/// </summary>
@@ -222,6 +232,16 @@ static class CharacterTable
222232
/// </summary>
223233
public const Int32 BigI = 0x49;
224234

235+
/// <summary>
236+
/// The letter O.
237+
/// </summary>
238+
public const Int32 BigO = 0x4F;
239+
240+
/// <summary>
241+
/// The letter X.
242+
/// </summary>
243+
public const Int32 BigX = 0x58;
244+
225245
/// <summary>
226246
/// The letter a.
227247
/// </summary>
@@ -252,6 +272,11 @@ static class CharacterTable
252272
/// </summary>
253273
public const Int32 SmallN = 0x6e;
254274

275+
/// <summary>
276+
/// The letter o.
277+
/// </summary>
278+
public const Int32 SmallO = 0x6f;
279+
255280
/// <summary>
256281
/// The letter r.
257282
/// </summary>

src/Mages.Core/Tokens/InterpolatedToken.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
sealed class InterpolatedToken(String content, List<List<IToken>> parts, IEnumerable<ParseError> errors, TextPosition start, TextPosition end) : IToken
88
{
9-
private static readonly IEnumerable<ParseError> NoErrors = Enumerable.Empty<ParseError>();
9+
private static readonly IEnumerable<ParseError> NoErrors = [];
1010

1111
private readonly String _content = content;
1212
private readonly TextPosition _start = start;

src/Mages.Core/Tokens/NumberToken.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
sealed class NumberToken(Double value, IEnumerable<ParseError> errors, TextPosition start, TextPosition end) : IToken
99
{
10-
private static readonly IEnumerable<ParseError> NoErrors = Enumerable.Empty<ParseError>();
10+
private static readonly IEnumerable<ParseError> NoErrors = [];
1111

1212
private readonly Double _value = value;
1313
private readonly TextPosition _start = start;
@@ -26,8 +26,5 @@ sealed class NumberToken(Double value, IEnumerable<ParseError> errors, TextPosit
2626

2727
public TextPosition End => _end;
2828

29-
public override String ToString()
30-
{
31-
return $"Number / {_start} -- {_end} / '{_value}'";
32-
}
29+
public override String ToString() => $"Number / {_start} -- {_end} / '{_value}'";
3330
}

src/Mages.Core/Tokens/NumberTokenizer.cs

+38-9
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,18 @@ public IToken Zero()
6262

6363
var current = _scanner.Current;
6464

65-
if (current == CharacterTable.SmallX)
65+
if (current == CharacterTable.SmallX || current == CharacterTable.BigX)
6666
{
6767
return Hex();
6868
}
69-
else if (current == CharacterTable.SmallB)
69+
else if (current == CharacterTable.SmallB || current == CharacterTable.BigB)
7070
{
7171
return Binary();
7272
}
73+
else if (current == CharacterTable.SmallO || current == CharacterTable.BigO)
74+
{
75+
return Octal();
76+
}
7377
else if (current.IsDigit() || current == CharacterTable.FullStop)
7478
{
7579
return Digit();
@@ -143,7 +147,26 @@ private IToken Binary()
143147
weight *= 2;
144148
}
145149

146-
return Final();
150+
return FinalForAltInteger();
151+
}
152+
153+
private IToken Octal()
154+
{
155+
var numbers = new List<Int32>();
156+
var weight = 1;
157+
158+
while (_scanner.MoveNext() && _scanner.Current.IsInRange(CharacterTable.Zero, CharacterTable.Seven))
159+
{
160+
numbers.Add(_scanner.Current - CharacterTable.Zero);
161+
}
162+
163+
for (var i = numbers.Count - 1; i >= 0; --i)
164+
{
165+
AddValue(1UL, (UInt64)(numbers[i] * weight));
166+
weight *= 8;
167+
}
168+
169+
return FinalForAltInteger();
147170
}
148171

149172
private IToken Hex()
@@ -162,7 +185,7 @@ private IToken Hex()
162185
weight *= 16;
163186
}
164187

165-
return Final();
188+
return FinalForAltInteger();
166189
}
167190

168191
private IToken Decimal()
@@ -236,6 +259,16 @@ private IToken Exponent()
236259
return Final();
237260
}
238261

262+
private IToken FinalForAltInteger()
263+
{
264+
if (_scanner.Current == CharacterTable.FullStop)
265+
{
266+
AddError(ErrorCode.DotUnexpected, _scanner.Position.ToRange());
267+
}
268+
269+
return Final();
270+
}
271+
239272
private IToken Final()
240273
{
241274
_scanner.MoveBack();
@@ -244,11 +277,7 @@ private IToken Final()
244277

245278
private void AddError(ErrorCode code, ITextRange range)
246279
{
247-
if (_errors == null)
248-
{
249-
_errors = [];
250-
}
251-
280+
_errors ??= [];
252281
_errors.Add(new ParseError(code, range));
253282
}
254283

src/Mages.Core/Tokens/StringToken.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
sealed class StringToken(String content, IEnumerable<ParseError> errors, TextPosition start, TextPosition end) : IToken
88
{
9-
private static readonly IEnumerable<ParseError> NoErrors = Enumerable.Empty<ParseError>();
9+
private static readonly IEnumerable<ParseError> NoErrors = [];
1010

1111
private readonly String _content = content;
1212
private readonly TextPosition _start = start;

0 commit comments

Comments
 (0)