Repository: sjbarag/brs (js)

Issue:
Support "%" type declaration character
In Brightscript numbers with 10 or more digits are treated with the [following logic](https://developer.roku.com/docs/references/brightscript/language/expressions-variables-types.md#literals-constants):

> 1. If a constant contains 10 or more digits, or if D is used in the exponent, that number is double precision. Adding a # declaration character also forces a constant to be double precision.
> 2.  If the number is not double-precision, and if it contains a decimal point, then the number is float. If the number is expressed in exponential notation with E preceding the exponent, the number is float.
> 3. If neither of the above is true of the constant, then it is an integer.

but you can also force long numbers to be casted as integers (as long as they fit in 32 bits) by using the `%` character. 

```
Brightscript Debugger> myvar = 1234567890%
Brightscript Debugger> print getInterface(myvar, "ifDouble")
invalid
Brightscript Debugger> print getinterface(myvar, "ifInt")
<Interface: ifInt>
```

However `%` isn't supported in brs
```
brs> myvar = 1234567890%
REPL(1,18-19): Unexpected character '%'
brs> myvar = 1234567890
brs> print getInterface(myvar, "ifDouble")
<Interface: ifDouble>
brs> print getinterface(myvar, "ifInt")
invalid
```

Pull request description:
fix(lexer): Add support for explicit integer literals
 this PR resolves #627 issue
what were done:

- added support for `%` suffix in integer literals
- added unit test for imlementation(lexer)
- added e2e test.

Patch:
diff --git a/src/brsTypes/components/RoString.ts b/src/brsTypes/components/RoString.ts
index e537c04..f0d6d28 100644
--- a/src/brsTypes/components/RoString.ts
+++ b/src/brsTypes/components/RoString.ts
@@ -46,6 +46,7 @@ export class RoString extends BrsComponent implements BrsValue, Comparable, Unbo
                 this.decodeUri,
                 this.encodeUriComponent,
                 this.decodeUriComponent,
+                this.isEmpty,
             ],
             ifToStr: [this.toStr],
         });
@@ -414,6 +415,17 @@ export class RoString extends BrsComponent implements BrsValue, Comparable, Unbo
         },
     });
 
+    /** returns whether string is empty or not */
+    private isEmpty = new Callable("isEmpty", {
+        signature: {
+            args: [],
+            returns: ValueKind.Boolean,
+        },
+        impl: (_interpreter) => {
+            return BrsBoolean.from(this.intrinsic.value.length === 0);
+        },
+    });
+
     /**
      * Encode the specified string with escape sequences for reserved Uniform Resource Identifier
      * (URI) characters.
diff --git a/src/lexer/Lexer.ts b/src/lexer/Lexer.ts
index d95712b..1ea9591 100644
--- a/src/lexer/Lexer.ts
+++ b/src/lexer/Lexer.ts
@@ -543,6 +543,9 @@ export class Lexer {
                 addToken(Lexeme.LongInteger, Int64.fromString(asString));
                 return;
             } else {
+                if (designator === "%") {
+                    advance();
+                } // consume the "%"
                 // otherwise, it's a regular integer
                 addToken(Lexeme.Integer, Int32.fromString(asString));
                 return;


Test patch:
diff --git a/test/brsTypes/components/RoString.test.js b/test/brsTypes/components/RoString.test.js
index c3314db..e9ab020 100644
--- a/test/brsTypes/components/RoString.test.js
+++ b/test/brsTypes/components/RoString.test.js
@@ -637,6 +637,22 @@ describe("RoString", () => {
                 );
             });
         });
+
+        describe("isEmpty", () => {
+            it("check if empty string is empty", () => {
+                let s = new RoString(new BrsString(""));
+                let len = s.getMethod("isEmpty");
+                expect(len).toBeInstanceOf(Callable);
+                expect(len.call(interpreter)).toBe(BrsBoolean.True);
+            });
+
+            it("check if filled string is not empty", () => {
+                let s = new RoString(new BrsString("<3"));
+                let len = s.getMethod("isEmpty");
+                expect(len).toBeInstanceOf(Callable);
+                expect(len.call(interpreter)).toBe(BrsBoolean.False);
+            });
+        });
     });
 
     describe("ifToStr", () => {
diff --git a/test/e2e/BrsComponents.test.js b/test/e2e/BrsComponents.test.js
index 35d5557..648bef9 100644
--- a/test/e2e/BrsComponents.test.js
+++ b/test/e2e/BrsComponents.test.js
@@ -185,6 +185,8 @@ describe("end to end brightscript functions", () => {
             "b", // split("/")[1]
             "%F0%9F%90%B6", // dog emoji, uri-encoded
             "🐶", // uri-encoded dog emoji, decoded
+            "true", // isEmpty for empty string
+            "false", // isEmpty for filled string
         ]);
     });
 
diff --git a/test/e2e/Syntax.test.js b/test/e2e/Syntax.test.js
index 77c84fa..d8a289d 100644
--- a/test/e2e/Syntax.test.js
+++ b/test/e2e/Syntax.test.js
@@ -30,6 +30,7 @@ describe("end to end syntax", () => {
             "true",
             "false",
             "5",
+            "6",
             "7",
             "30",
             "40",
diff --git a/test/e2e/resources/components/roString.brs b/test/e2e/resources/components/roString.brs
index 0f4c98b..d80e4ab 100644
--- a/test/e2e/resources/components/roString.brs
+++ b/test/e2e/resources/components/roString.brs
@@ -26,4 +26,6 @@ sub main()
     u = "🐶"
     print u.encodeUriComponent() ' => %F0%9F%90%B6
     print "%F0%9F%90%B6".decodeUriComponent() ' => 🐶
+    print "".isEmpty() ' => true
+    print "<3".isEmpty() ' => false
 end sub
\ No newline at end of file
diff --git a/test/e2e/resources/printLiterals.brs b/test/e2e/resources/printLiterals.brs
index cddded4..514d340 100644
--- a/test/e2e/resources/printLiterals.brs
+++ b/test/e2e/resources/printLiterals.brs
@@ -3,6 +3,7 @@ print true
 print false
 
 print 5   ' a 32-bit integer
+print 6%  ' a 32-bit expicit integer
 print 7&  ' a 64-bit integer
 print 3e1 ' a float
 ? 4d1     ' a double
diff --git a/test/lexer/Lexer.test.js b/test/lexer/Lexer.test.js
index 04f7b74..0aa89df 100644
--- a/test/lexer/Lexer.test.js
+++ b/test/lexer/Lexer.test.js
@@ -244,6 +244,12 @@ describe("lexer", () => {
             expect(i.literal).toEqual(new Int32(255));
         });
 
+        it("supports '%' suffix", () => {
+            let i = Lexer.scan("123%").tokens[0];
+            expect(i.kind).toBe(Lexeme.Integer);
+            expect(i.literal).toEqual(new Int32(123));
+        });
+
         it("falls back to a regular integer", () => {
             let i = Lexer.scan("123").tokens[0];
             expect(i.kind).toBe(Lexeme.Integer);