SLING-7701 - [HTL] Add support for negative numbers
diff --git a/src/main/antlr4/org/apache/sling/scripting/sightly/impl/parser/expr/generated/SightlyLexer.g4 b/src/main/antlr4/org/apache/sling/scripting/sightly/impl/parser/expr/generated/SightlyLexer.g4
index 255fb2a..429dfb4 100644
--- a/src/main/antlr4/org/apache/sling/scripting/sightly/impl/parser/expr/generated/SightlyLexer.g4
+++ b/src/main/antlr4/org/apache/sling/scripting/sightly/impl/parser/expr/generated/SightlyLexer.g4
@@ -74,16 +74,17 @@
 
 // tokens
 
-ID  :	('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_'|':')*
+ID  : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_'|':')*
     ;
 
-INT :	'0'..'9'+
+INT : '-'? '1'..'9'+ '0'..'9'*
+    | '0'
     ;
 
 FLOAT
-    :   ('0'..'9')+ '.' ('0'..'9')+ EXPONENT?
-//    |   '.' ('0'..'9')+ EXPONENT?  --> conflicts with a.2 notation
-    |   ('0'..'9')+ EXPONENT
+    :   '-'? '1'..'9'+ '0'..'9'* '.' '0'..'9'+ EXPONENT?
+    |   '-'? '0.' '0'..'9'+ EXPONENT?
+    |   '-'? '1'..'9'+ '0'..'9'* EXPONENT
     ;
 
 COMMENT: '<!--/*' .*? '*/-->' -> skip;
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/NumericConstant.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/NumericConstant.java
index d03056c..cee38a0 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/NumericConstant.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/NumericConstant.java
@@ -80,8 +80,12 @@
     }
 
     private Number parseNumber(String s) {
-        if (s.contains(".")) {
-            return Double.parseDouble(s);
+        if (s.contains(".") || s.contains("e") || s.contains("E")) {
+            double interim = Double.parseDouble(s);
+            if (interim == 0) {
+                return 0.0;
+            }
+            return interim;
         }
         return Long.parseLong(s);
     }
diff --git a/src/test/java/org/apache/sling/scripting/sightly/impl/TestUtils.java b/src/test/java/org/apache/sling/scripting/sightly/impl/TestUtils.java
deleted file mode 100644
index bcc014a..0000000
--- a/src/test/java/org/apache/sling/scripting/sightly/impl/TestUtils.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ******************************************************************************/
-package org.apache.sling.scripting.sightly.impl;
-
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-
-import org.apache.sling.scripting.sightly.compiler.CompilationUnit;
-
-/**
- * Testing static utilities that simplify writing tests for the HTL compiler.
- */
-public class TestUtils {
-
-    public static CompilationUnit readScriptFromClasspath(final String scriptResource) {
-        InputStream stream = TestUtils.class.getResourceAsStream(scriptResource);
-        final Reader reader = new InputStreamReader(stream);
-        return new CompilationUnit() {
-            @Override
-            public String getScriptName() {
-                return scriptResource;
-            }
-
-            @Override
-            public Reader getScriptReader() {
-                return reader;
-            }
-        };
-    }
-}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyCompilerTest.java b/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyCompilerTest.java
index e861b5f..7d95e1d 100644
--- a/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyCompilerTest.java
+++ b/src/test/java/org/apache/sling/scripting/sightly/impl/compiler/SightlyCompilerTest.java
@@ -18,13 +18,16 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.compiler;
 
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
 import java.util.List;
 
 import org.apache.sling.scripting.sightly.compiler.CompilationResult;
 import org.apache.sling.scripting.sightly.compiler.CompilationUnit;
 import org.apache.sling.scripting.sightly.compiler.CompilerMessage;
 import org.apache.sling.scripting.sightly.compiler.SightlyCompiler;
-import org.apache.sling.scripting.sightly.impl.TestUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.powermock.api.mockito.PowerMockito;
@@ -42,7 +45,7 @@
 
     @Test
     public void testEmptyExpression() {
-        CompilationResult result = compile("/empty-expression.html");
+        CompilationResult result = compileFile("/empty-expression.html");
         assertTrue("Didn't expect any warnings or errors.", result.getErrors().size() == 0 && result.getWarnings().size() == 0);
     }
 
@@ -55,7 +58,7 @@
     }
 
     private void testMissingExplicitContext(String script) {
-        CompilationResult result = compile(script);
+        CompilationResult result = compileFile(script);
         List<CompilerMessage> warnings = result.getWarnings();
         assertTrue(script + ": Expected compilation warnings.", warnings.size() == 1);
         CompilerMessage warningMessage = warnings.get(0);
@@ -91,7 +94,7 @@
     @Test
     public void testSensitiveAttributes() {
         String script = "/sensitive-attributes.html";
-        CompilationResult result = compile(script);
+        CompilationResult result = compileFile(script);
         List<CompilerMessage> warnings = result.getWarnings();
         assertTrue("Expected compilation warnings.", warnings.size() == 2);
         CompilerMessage _1stWarning = warnings.get(0);
@@ -110,15 +113,72 @@
     @Test
     public void testErrorReporting1() {
         String script = "/error-1.html";
-        CompilationResult result = compile(script);
+        CompilationResult result = compileFile(script);
         List<CompilerMessage> errors = result.getErrors();
         assertTrue("Expected compilation errors.", errors.size() == 1);
         CompilerMessage error = errors.get(0);
         assertEquals("Error is not reported at the expected line.", 18, error.getLine());
     }
 
-    private CompilationResult compile(String file) {
-        CompilationUnit compilationUnit = TestUtils.readScriptFromClasspath(file);
+    @Test
+    public void testNumberParsing() {
+        // integers
+        int integerTestRange = 20;
+        for (int i = -1 * integerTestRange; i < integerTestRange; i++) {
+            assertEquals(0, compileSource("${" + i + "}").getErrors().size());
+        }
+
+        // doubles
+        double  doubleTestRange = 20.00;
+        for (double i = -1.00 * doubleTestRange; i < doubleTestRange; i+= 0.1) {
+            assertEquals(0, compileSource("${" + i + "}").getErrors().size());
+        }
+
+        assertEquals(0, compileSource("${-0.0}").getErrors().size());
+        assertEquals(0, compileSource("${-0.000}").getErrors().size());
+        assertEquals(1, compileSource("${-00.0}").getErrors().size());
+        assertEquals(1, compileSource("${00.0}").getErrors().size());
+        assertEquals(1, compileSource("${00}").getErrors().size());
+        assertEquals(1, compileSource("${-0}").getErrors().size());
+        assertEquals(1, compileSource("${01}").getErrors().size());
+        assertEquals(0, compileSource("${0.1e-2}").getErrors().size());
+        assertEquals(0, compileSource("${0.1e+2}").getErrors().size());
+        assertEquals(1, compileSource("${00.1e-2}").getErrors().size());
+        assertEquals(1, compileSource("${0e-2}").getErrors().size());
+        assertEquals(1, compileSource("${01e-2}").getErrors().size());
+        assertEquals(0, compileSource("${1e-2}").getErrors().size());
+        assertEquals(0, compileSource("${1e+2}").getErrors().size());
+    }
+
+    private CompilationResult compileFile(final String file) {
+        InputStream stream = this.getClass().getResourceAsStream(file);
+        final Reader reader = new InputStreamReader(stream);
+        CompilationUnit compilationUnit = new CompilationUnit() {
+            @Override
+            public String getScriptName() {
+                return file;
+            }
+
+            @Override
+            public Reader getScriptReader() {
+                return reader;
+            }
+        };
+        return compiler.compile(compilationUnit);
+    }
+
+    private CompilationResult compileSource(final String source) {
+        CompilationUnit compilationUnit = new CompilationUnit() {
+            @Override
+            public String getScriptName() {
+                return "NO_NAME";
+            }
+
+            @Override
+            public Reader getScriptReader() {
+                return new StringReader(source);
+            }
+        };
         return compiler.compile(compilationUnit);
     }