diff --git a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateSynthesisRule.java b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateSynthesisRule.java index ffc336a12..fd7285136 100644 --- a/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateSynthesisRule.java +++ b/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentTemplateSynthesisRule.java @@ -479,17 +479,17 @@ class AsmFragmentTemplateSynthesisRule { synths.add(new AsmFragmentTemplateSynthesisRule("(.*)vw(.)c1(.*)", null, null , "$1vd$2c1$3", null, null)); synths.add(new AsmFragmentTemplateSynthesisRule("(.*)vw(.)c2(.*)", null, null , "$1vd$2c2$3", null, null)); - /* Removed pending optimization! + /* Removed awaiting optimization // Rewrite constant unsigned word values to constant signed dword values synths.add(new AsmFragmentTemplateSynthesisRule("(.*)vwuc1(.*)", null, null , "$1vdsc1$2", null, null)); synths.add(new AsmFragmentTemplateSynthesisRule("(.*)vwuc2(.*)", null, null , "$1vdsc2$2", null, null)); */ - /* + /* Removed awaiting optimization // Rewrite any zeropage pointer as an unsigned word zeropage values - synths.add(new AsmFragmentTemplateSynthesisRule("(.*)p..z(.)(.*)", null, null , "$1vwuz$2$3", null, null)); + synths.add(new AsmFragmentTemplateSynthesisRule("(.*)p..z(.)(.*)", ".*p..z._deref.*", null , "$1vwuz$2$3", null, null)); // Rewrite any constant pointer as an constant unsigned word - synths.add(new AsmFragmentTemplateSynthesisRule("(.*)p..c(.)(.*)", null, null , "$1vwuc$2$3", null, null)); + synths.add(new AsmFragmentTemplateSynthesisRule("(.*)p..c(.)(.*)", ".*p..z._deref.*", null , "$1vwuc$2$3", null, null)); */ // Synthesize some constant pointers as constant words (remove when the above section can be included) diff --git a/src/main/java/dk/camelot64/kickc/model/statements/StatementSource.java b/src/main/java/dk/camelot64/kickc/model/statements/StatementSource.java index c4fd04b6e..63df1d555 100644 --- a/src/main/java/dk/camelot64/kickc/model/statements/StatementSource.java +++ b/src/main/java/dk/camelot64/kickc/model/statements/StatementSource.java @@ -2,6 +2,7 @@ package dk.camelot64.kickc.model.statements; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.misc.Interval; /** Contains information about the source of a program statement */ @@ -15,9 +16,13 @@ public class StatementSource { @Override public String toString() { - CharStream stream = context.getStart().getInputStream(); - Interval interval = new Interval(context.getStart().getStartIndex(), context.getStop().getStopIndex()); - String sourceMessage = "File "+stream.getSourceName()+"\nLine "+context.getStart().getLine()+ "\n" +stream.getText(interval); - return sourceMessage; + Token contextStart = context.getStart(); + if(contextStart != null) { + CharStream stream = contextStart.getInputStream(); + Interval interval = new Interval(contextStart.getStartIndex(), context.getStop().getStopIndex()); + return "File " + stream.getSourceName() + "\nLine " + contextStart.getLine() + "\n" + stream.getText(interval); + } else { + return ""; + } } } diff --git a/src/main/java/dk/camelot64/kickc/model/symbols/Scope.java b/src/main/java/dk/camelot64/kickc/model/symbols/Scope.java index 268d42561..a9fb835fe 100644 --- a/src/main/java/dk/camelot64/kickc/model/symbols/Scope.java +++ b/src/main/java/dk/camelot64/kickc/model/symbols/Scope.java @@ -1,5 +1,6 @@ package dk.camelot64.kickc.model.symbols; +import dk.camelot64.kickc.model.CompileError; import dk.camelot64.kickc.model.Program; import dk.camelot64.kickc.model.Registers; import dk.camelot64.kickc.model.VariableRegisterWeights; @@ -97,7 +98,7 @@ public abstract class Scope implements Symbol { public Symbol add(Symbol symbol) { if(symbols.get(symbol.getLocalName()) != null) { - throw new RuntimeException("Symbol already declared " + symbol.getLocalName()); + throw new CompileError("Symbol already declared " + symbol.getLocalName()); } symbols.put(symbol.getLocalName(), symbol); return symbol; diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java b/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java index b46f5161e..86a833bc1 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java @@ -299,7 +299,12 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor { public Object visitDeclVariable(KickCParser.DeclVariableContext ctx) { SymbolType type = (SymbolType) visit(ctx.typeDecl()); String varName = ctx.NAME().getText(); - VariableUnversioned lValue = getCurrentSymbols().addVariable(varName, type); + VariableUnversioned lValue; + try { + lValue = getCurrentSymbols().addVariable(varName, type); + } catch(CompileError e) { + throw new CompileError(e.getMessage(), new StatementSource(ctx)); + } // Add directives addDirectives(type, lValue, ctx.directive()); // Array / String variables are implicitly constant @@ -669,7 +674,11 @@ public class Pass0GenerateStatementSequence extends KickCBaseVisitor { Variable lValue; if(forDeclCtx.typeDecl() != null) { SymbolType type = (SymbolType) visit(forDeclCtx.typeDecl()); - lValue = getCurrentSymbols().addVariable(varName, type); + try { + lValue = getCurrentSymbols().addVariable(varName, type); + } catch(CompileError e) { + throw new CompileError(e.getMessage(), new StatementSource(forDeclCtx)); + } // Add directives addDirectives(type, lValue, forDeclCtx.directive()); } else { diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java index a2a360398..b2e5cbb4d 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass2ConstantIdentification.java @@ -68,7 +68,8 @@ public class Pass2ConstantIdentification extends Pass2SsaOptimization { throw new CompileError( "Constant variable has a non-matching type \n variable: " + variable.toString(getProgram()) + "\n value: (" + valueType.toString() + ") " + constVal.calculateLiteral(getScope()) + - "\n value definition: " + constVal.toString(getProgram())); + "\n value definition: " + constVal.toString(getProgram()) + ); } } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass4AssertNoCpuClobber.java b/src/main/java/dk/camelot64/kickc/passes/Pass4AssertNoCpuClobber.java index 2e0353147..eda09a609 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass4AssertNoCpuClobber.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass4AssertNoCpuClobber.java @@ -4,6 +4,7 @@ import dk.camelot64.kickc.asm.AsmClobber; import dk.camelot64.kickc.asm.AsmProgram; import dk.camelot64.kickc.asm.AsmSegment; import dk.camelot64.kickc.model.*; +import dk.camelot64.kickc.model.statements.StatementSource; import dk.camelot64.kickc.model.values.RValue; import dk.camelot64.kickc.model.values.VariableRef; import dk.camelot64.kickc.model.statements.Statement; @@ -110,16 +111,17 @@ public class Pass4AssertNoCpuClobber extends Pass2Base { // Alive and not assigned to - clobber not allowed! if(clobberRegisters.contains(aliveVarRegister)) { if(verbose) { - getLog().append("Error! Alive variable " + aliveVar + " register " + aliveVarRegister + " clobbered by the ASM generated by statement " + statement); + throw new CompileError( + "Error! Generated ASM has register clobber problem. " + + "Alive variable "+aliveVar + " register " + aliveVarRegister + " clobbered by the ASM generated by statement "+statement.toString(getProgram(), true), + statement.getSource() + ); } clobberProblem = true; } } } } - if(verbose && clobberProblem) { - getLog().append(asm.toString(true)); - } return clobberProblem; } diff --git a/src/test/java/dk/camelot64/kickc/test/TestFragments.java b/src/test/java/dk/camelot64/kickc/test/TestFragments.java index a67407a34..ee6093ee5 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestFragments.java +++ b/src/test/java/dk/camelot64/kickc/test/TestFragments.java @@ -142,7 +142,7 @@ public class TestFragments { } /** - * Test that a specific fragment can be succesfully loaded/synthesized + * Test that a specific fragment can be succesfully loaded/synthesized * @param signature The fragment signature */ private void testFragmentExists(String signature) { diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 16972bae4..d6f34240a 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -44,6 +44,11 @@ public class TestPrograms { AsmFragmentTemplateUsages.logUsages(log, false, false, false, false, false, false); } + @Test + public void testNoLocalScope() throws IOException, URISyntaxException { + assertError("nolocalscope", "Symbol already declared"); + } + @Test public void testTypeMix() throws IOException, URISyntaxException { compileAndCompare("type-mix"); @@ -288,7 +293,7 @@ public class TestPrograms { @Test public void testUnrollInfinite() throws IOException, URISyntaxException { - assertError("unroll-infinite", "Loop cannot be unrolled."); + assertError("unroll-infinite", "Loop cannot be unrolled.", false); } @Test @@ -1143,7 +1148,7 @@ public class TestPrograms { @Test public void testNoReturn() throws IOException, URISyntaxException { - assertError("noreturn", "Method must end with a return statement"); + assertError("noreturn", "Method must end with a return statement", false); } @Test @@ -1158,7 +1163,7 @@ public class TestPrograms { @Test public void testInvalidConstType() throws IOException, URISyntaxException { - assertError("invalid-consttype", "Constant variable has a non-matching type"); + assertError("invalid-consttype", "Constant variable has a non-matching type", false); } @Test @@ -1173,12 +1178,12 @@ public class TestPrograms { @Test public void testArrayLengthMismatch() throws IOException, URISyntaxException { - assertError("array-length-mismatch", "Array length mismatch"); + assertError("array-length-mismatch", "Array length mismatch", false); } @Test public void testStringLengthMismatch() throws IOException, URISyntaxException { - assertError("string-length-mismatch", "Array length mismatch"); + assertError("string-length-mismatch", "Array length mismatch", false); } @Test @@ -1188,17 +1193,17 @@ public class TestPrograms { @Test public void testRegisterClobber() throws IOException, URISyntaxException { - assertError("register-clobber", "CLOBBER ERROR"); + assertError("register-clobber", "register clobber problem", false); } @Test public void testRecursionError() throws IOException, URISyntaxException { - assertError("recursion-error", "Recursion"); + assertError("recursion-error", "Recursion", false); } @Test public void testRecursionComplexError() throws IOException, URISyntaxException { - assertError("recursion-error-complex", "Recursion"); + assertError("recursion-error-complex", "Recursion", false); } @Test @@ -1223,7 +1228,7 @@ public class TestPrograms { @Test public void testNoInlineInterrupt() throws IOException, URISyntaxException { - assertError("no-inlineinterrupt", "Interrupts cannot be inlined"); + assertError("no-inlineinterrupt", "Interrupts cannot be inlined", false); } @Test @@ -1233,12 +1238,12 @@ public class TestPrograms { @Test public void testNoParamInterrupt() throws IOException, URISyntaxException { - assertError("no-paraminterrupt", "Interrupts cannot have parameters."); + assertError("no-paraminterrupt", "Interrupts cannot have parameters.", false); } @Test public void testNoReturnInterrupt() throws IOException, URISyntaxException { - assertError("no-returninterrupt", "Interrupts cannot return anything."); + assertError("no-returninterrupt", "Interrupts cannot return anything.", false); } @Test @@ -1247,12 +1252,20 @@ public class TestPrograms { } private void assertError(String kcFile, String expectError) throws IOException, URISyntaxException { + assertError(kcFile, expectError, true); + } + + private void assertError(String kcFile, String expectError, boolean expectLineNumber) throws IOException, URISyntaxException { try { compileAndCompare(kcFile); } catch(CompileError e) { System.out.println("Got error: " + e.getMessage()); // expecting error! assertTrue("Error message expected '" + expectError + "' - was:" + e.getMessage(), e.getMessage().contains(expectError)); + if(expectLineNumber) { + // expecting line number! + assertTrue("Error message expected line number - was:" + e.getMessage(), e.getMessage().contains("Line")); + } return; } fail("Expected compile error."); diff --git a/src/test/kc/nolocalscope.kc b/src/test/kc/nolocalscope.kc new file mode 100644 index 000000000..3ba6b43eb --- /dev/null +++ b/src/test/kc/nolocalscope.kc @@ -0,0 +1,10 @@ +// Illustrates problem with not introducing local scopes inside loops etc + +import "print" + +void main() { + for (byte i: 0..5) + print_ln(); + for (byte i: 0..5) + print_str_ln(" xxxxx@"); +} \ No newline at end of file