mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1383813d2 | ||
|
|
3bd2064c3a | ||
|
|
f95f568319 | ||
|
|
5eb873b722 | ||
|
|
3f181b0ede | ||
|
|
d6c8fc0c61 | ||
|
|
709ced1bca | ||
|
|
3b16d08246 | ||
|
|
b9a6deea25 | ||
|
|
099514a2c0 | ||
|
|
ed02b84f7d | ||
|
|
7d6e67e251 | ||
|
|
4aa364925d |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
---
|
||||
description: Startup workflow to load project context
|
||||
---
|
||||
|
||||
1. Read the file `QWEN.md` in the project root to understand the project structure, module descriptions, and development commands.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Agent Instructions for Prog8 Project
|
||||
|
||||
## Required Startup Actions
|
||||
|
||||
When starting a new session with this project, you **MUST** perform these actions before proceeding with any user requests:
|
||||
|
||||
1. **Read QWEN.md** - Execute: `read /home/irmen/Projects/prog8/QWEN.md`
|
||||
- This file contains critical project context including:
|
||||
- Project overview (Prog8 compiler for 8-bit retro systems)
|
||||
- Module descriptions for all 20+ modules
|
||||
- Gradle commands and build instructions
|
||||
- Prog8 compiler usage examples
|
||||
- Development shortcuts
|
||||
|
||||
2. **Check startup workflow** - Execute: `read /home/irmen/Projects/prog8/.agent/workflows/startup.md`
|
||||
- This file may contain additional startup instructions
|
||||
|
||||
## Why This Matters
|
||||
|
||||
The prog8 project is a complex compiler with:
|
||||
- Multiple code generation backends (6502, IR, Virtual Machine)
|
||||
- 20+ interconnected modules
|
||||
- Specific build and development workflows
|
||||
- Proprietary language syntax and conventions
|
||||
|
||||
Without reading QWEN.md first, you will lack essential context needed to:
|
||||
- Understand module relationships
|
||||
- Use correct gradle commands
|
||||
- Reference the right files
|
||||
- Follow project conventions
|
||||
|
||||
**DO NOT PROCEED** with any user requests until you've read QWEN.md!
|
||||
@@ -0,0 +1,86 @@
|
||||
# Qwen Assistant Memory for prog8 Project
|
||||
|
||||
This file contains project-specific information that helps the Qwen assistant understand and work effectively with the prog8 codebase.
|
||||
|
||||
## Project Overview
|
||||
- This project is a compiler for the Prog8 programming language.
|
||||
- Prog8 is a programming language primarily targeting 8-bit retro systems with the 6502 CPU, such as the Commodore 64, Commodore 128, and Commander X16.
|
||||
- The compiler has a 6502 code generator backend, and an IR code generator.
|
||||
- The IR code is meant to be used in a new machine specific code generator backend (primarily 6502 but maybe 68000 as well later)
|
||||
- The compiler includes a simple 'virtual machine' that can execute the IR code directly via interpretation.
|
||||
- Prog8 source files have .p8 extension
|
||||
- Prog8 source files are a "module" that can contain 1 ore more "blocks". They can also import other modules, from internal library files or from source files on the filesystem.
|
||||
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
|
||||
|
||||
## Prog8 language feature hints
|
||||
- an overview of the language features can be found in the documentation file docs/source/introduction.rst
|
||||
- the syntax and grammar is specified in an ANTLR4 grammar file found in the parser directory
|
||||
- the syntax for boolean logical operators is 'and', 'or', 'xor', 'not'. Bitwise operators are '&', '|', '^', '~', and '<<','>>' for bit shifting left and right respectively. All operators are documents in docs/source/programming.rst
|
||||
- note that Prog8 does have a 4-byte signed long type and 5-byte "Microsoft" floating point support
|
||||
- module imports are done using "%import modulename"
|
||||
|
||||
## Module Descriptions
|
||||
- `beanshell` - EXPERIMENTAL/UNFINISHED Contains BeanShell integration for scripting capabilities within the compiler
|
||||
- `benchmark-c` - C implementations for performance comparison and benchmarking
|
||||
- `benchmark-program` - Benchmark programs to test compiler output performance
|
||||
- `codeCore` - Core code generation utilities and shared components
|
||||
- `codeGenCpu6502` - 6502 CPU-specific code generator backend (generates assembly for 6502-based systems)
|
||||
- `codeGenExperimental` - Experimental code generators that are under development
|
||||
- `codeGenIntermediate` - Intermediate representation (IR) code generator
|
||||
- `codeGenVirtual` - Virtual machine code generator backend
|
||||
- `codeOptimizers` - Optimization passes for improving generated code efficiency
|
||||
- `compiler` - Main compiler executable and top-level compiler logic
|
||||
- `compilerAst` - complicated Abstract Syntax Tree (AST) where most optimizations also run on, is later transformed into the simpleAst
|
||||
- `docs` - Documentation files for the Prog8 language and compiler
|
||||
- `examples` - Example Prog8 programs demonstrating language features
|
||||
- `intermediate` - Components related to the intermediate representation (IR) of Prog8 programs
|
||||
- `languageServer` - Language Server Protocol implementation for IDE integration
|
||||
- `parser` - ANTLR4 Parser implementation for the Prog8 language syntax
|
||||
- `scripts` - Utility scripts for development, testing, and deployment
|
||||
- `simpleAst` - Simplified AST that is used to run the code generator backends from
|
||||
- `syntax-files` - Syntax definition files for editors and IDEs
|
||||
- `virtualmachine` - Virtual machine implementation that can execute IR code
|
||||
|
||||
## Key Information
|
||||
- never read the files and directories that are ignored via the .aiignore and .gitignore files
|
||||
- Current development focus areas: compiler optimizations, new language features, backend improvements
|
||||
- Important project conventions: Kotlin for compiler implementation, modular architecture, IR-based compilation
|
||||
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
|
||||
|
||||
### Gradle Commands
|
||||
- use the system installed gradle command instead of the gradle wrapper.
|
||||
- `gradle build` - Full build of the compiler including running the full test suite
|
||||
- `gradle clean` - Clean build artifacts
|
||||
- `gradle compileKotlin` - Compile only the Kotlin source code
|
||||
- `gradle installdist` - Create the compiler JARs and executable file
|
||||
- `gradle installshadowdist` - Create the single "fat" compiler JAR and executable file
|
||||
|
||||
### Development Shortcuts
|
||||
- `gradle test --tests "*TestName*"` - Run specific test classes
|
||||
- `gradle build --refresh-dependencies` - Refresh dependencies during development
|
||||
|
||||
### Prog8 Compiler Usage
|
||||
- the prog8c compiler executable can be found in the compiler/build/install/prog8c/bin folder (this is already added to the shell's path)
|
||||
- the `-emu` switch can be used to directly execute the resulting program in an emulator after successful compilation.
|
||||
- `prog8c -target cx16 input.p8` - Compile a Prog8 source file "input.p8" for the CommanderX16 target
|
||||
- `prog8c -target cx16 -check input.p8` - Quickly check a Prog8 source file "input.p8" for compiler errors, no output binary is produced
|
||||
- `prog8c -target cx16 -emu input.p8` - Compile and execute a prog8 file in the CommanderX16 emulator
|
||||
- `prog8c -target c64 -emu input.p8` - Compile and execute a prog8 file in the Commodore-64 emulator
|
||||
- `prog8c -target virtual input.p8` - Compile a prog8 file for the IR/Virtual machine target
|
||||
- `prog8c -target virtual -emu input.p8` - Compile and directly execute a prog8 file in the Virtual Machine
|
||||
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
|
||||
- `x16emu -scale 2 -prg input.prg` - Just load an existing compiled program in the CommanderX16 emulator
|
||||
- `x64sc input.prg` - run an existing compiled program in the Commodore-64 emulator (Note: You can safely ignore errors and warnings from this emulator)
|
||||
|
||||
### Compilation Output Files
|
||||
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
|
||||
- `*.asm` - Generated assembly code from the Prog8 source
|
||||
- `*.list` - Generated full assembly listing file from the Prog8 source
|
||||
- `*.p8ir` - Intermediate representation file, can be executed in the Virtual Machine
|
||||
- `*.vice-mon-list` - Vice emulator monitor list file for debugging
|
||||
|
||||
## Working Notes
|
||||
[Any ongoing notes about the project that would be helpful for the assistant to remember]
|
||||
|
||||
## TODO Items
|
||||
The file docs/source/todo.rst contains a comprehensive list of things that still have to be fixed, implemented, or optimized.
|
||||
@@ -52,18 +52,18 @@ val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDa
|
||||
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
|
||||
val BaseDataType.isLong get() = this == BaseDataType.LONG
|
||||
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
|
||||
val BaseDataType.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
|
||||
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
|
||||
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
|
||||
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
|
||||
val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
|
||||
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
|
||||
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
|
||||
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
|
||||
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
|
||||
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
|
||||
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
|
||||
|
||||
@@ -307,14 +307,14 @@ class DataType private constructor(val base: BaseDataType, val sub: BaseDataType
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(base) {
|
||||
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
|
||||
BaseDataType.UBYTE -> targetType.base in arrayOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.BYTE -> targetType.base in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
|
||||
BaseDataType.WORD -> targetType.base in arrayOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in arrayOf(BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UBYTE -> targetType.base in setOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
|
||||
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
|
||||
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package prog8.code.core
|
||||
|
||||
val AssociativeOperators = arrayOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not associative because of Shortcircuit/McCarthy evaluation
|
||||
val ComparisonOperators = arrayOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = arrayOf("and", "or", "xor", "not", "in")
|
||||
val BitwiseOperators = arrayOf("&", "|", "^", "~")
|
||||
val PrefixOperators = arrayOf("+", "-", "~", "not")
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not associative because of Shortcircuit/McCarthy evaluation
|
||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
|
||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||
val PrefixOperators = setOf("+", "-", "~", "not")
|
||||
|
||||
fun invertedComparisonOperator(operator: String) =
|
||||
when (operator) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import prog8.code.sanitize
|
||||
import prog8.code.source.SourceCode
|
||||
import java.nio.file.InvalidPathException
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.toPath
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
override fun toString(): String = "[$file: line $line col ${startCol}-${endCol}]"
|
||||
@@ -21,8 +22,8 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
|
||||
if(SourceCode.isLibraryResource(file))
|
||||
return "$file:$line:$startCol:"
|
||||
return try {
|
||||
val path = Path(file).sanitize().toString()
|
||||
"file://$path:$line:$startCol:"
|
||||
val path = Path(file).sanitize().toUri().toString()
|
||||
"$path:$line:$startCol:"
|
||||
} catch(_: InvalidPathException) {
|
||||
// this can occur on Windows when the source origin contains "invalid" characters such as ':'
|
||||
"file://$file:$line:$startCol:"
|
||||
|
||||
@@ -19,7 +19,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
|
||||
val flt = num.toDouble()
|
||||
if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE)
|
||||
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $flt")
|
||||
if (flt == 0.0)
|
||||
return zero
|
||||
|
||||
@@ -40,7 +40,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
|
||||
|
||||
return when {
|
||||
exponent < 0 -> zero // underflow, use zero instead
|
||||
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
|
||||
exponent > 255 -> throw InternalCompilerException("floating point overflow: $flt")
|
||||
exponent == 0 -> zero
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
|
||||
@@ -332,8 +332,8 @@ class AsmGen6502Internal (
|
||||
private var generatedLabelSequenceNumber: Int
|
||||
) {
|
||||
|
||||
internal val optimizedByteMultiplications = arrayOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||
internal val optimizedWordMultiplications = arrayOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
|
||||
internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||
internal val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
private val zeropage = options.compTarget.zeropage
|
||||
private val allocator = VariableAllocator(symbolTable, options, errors)
|
||||
|
||||
@@ -584,7 +584,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
|
||||
}
|
||||
else if(" cmp #0" in second || "\tcmp #0" in second) {
|
||||
// there are many instructions that modify A and set the bits...
|
||||
for(instr in arrayOf("lda", "ora", "and", "eor", "adc", "sbc", "asl", "cmp", "inc a", "lsr", "pla", "rol", "ror", "txa", "tya")) {
|
||||
for(instr in setOf("lda", "ora", "and", "eor", "adc", "sbc", "asl", "cmp", "inc a", "lsr", "pla", "rol", "ror", "txa", "tya")) {
|
||||
if(" $instr" in first || "\t$instr" in first) {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lda $variable | lsr a | bcc + | ora #$80 |+ | sta $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> throw AssemblyError("weird node $what")
|
||||
}
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
@@ -573,7 +573,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #$80 | sta $variable+1 |+ ")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> throw AssemblyError("weird node $what")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
@@ -593,7 +593,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
sta $variable+3
|
||||
+""")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> throw AssemblyError("weird node $what")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@@ -628,7 +628,14 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
else -> {
|
||||
TODO("ror ptr-expression ${what.position}")
|
||||
asmgen.out(" php") // save Carry
|
||||
asmgen.assignExpressionToVariable(what.address, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
plp
|
||||
ror a
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,7 +643,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> throw AssemblyError("weird node $what")
|
||||
}
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
@@ -657,7 +664,34 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" ror $variable+1 | ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
is PtPointerDeref -> {
|
||||
asmgen.out(" php") // save Carry
|
||||
val (zpPtrVar, offset) = ptrgen.deref(what, false)
|
||||
asmgen.out("""
|
||||
ldy #$offset+1
|
||||
lda ($zpPtrVar),y
|
||||
plp
|
||||
ror a
|
||||
sta ($zpPtrVar),y
|
||||
dey
|
||||
lda ($zpPtrVar),y
|
||||
ror a
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" php") // save Carry
|
||||
asmgen.assignExpressionToVariable(what, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
asmgen.out("""
|
||||
ldy #1
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
plp
|
||||
ror a
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
dey
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
ror a
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
@@ -668,7 +702,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" ror $variable+3 | ror $variable+2 | ror $variable+1 | ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> {
|
||||
TODO("ror long on expression $what")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@@ -700,7 +736,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lda $variable | cmp #$80 | rol a | sta $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> throw AssemblyError("weird node $what")
|
||||
}
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
@@ -719,7 +755,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> throw AssemblyError("weird node $what")
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
@@ -737,7 +773,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
inc $variable
|
||||
+""")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> throw AssemblyError("weird node $what")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@@ -771,7 +807,14 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.storeAIntoZpPointerVar(sourceName, false)
|
||||
}
|
||||
else -> {
|
||||
TODO("rol ptr-expression ${what.position}")
|
||||
asmgen.out(" php") // save Carry
|
||||
asmgen.assignExpressionToVariable(what.address, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
plp
|
||||
rol a
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -779,7 +822,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" rol $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> throw AssemblyError("weird node $what")
|
||||
}
|
||||
}
|
||||
BaseDataType.UWORD -> {
|
||||
@@ -800,7 +843,34 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" rol $variable | rol $variable+1")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
is PtPointerDeref -> {
|
||||
asmgen.out(" php") // save Carry
|
||||
val (zpPtrVar, offset) = ptrgen.deref(what, false)
|
||||
asmgen.out("""
|
||||
ldy #$offset
|
||||
lda ($zpPtrVar),y
|
||||
plp
|
||||
rol a
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
rol a
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" php") // save Carry
|
||||
asmgen.assignExpressionToVariable(what, "P8ZP_SCRATCH_PTR", DataType.UWORD)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
plp
|
||||
rol a
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
rol a
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
BaseDataType.LONG -> {
|
||||
@@ -811,7 +881,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" rol $variable | rol $variable+1 | rol $variable+2 | rol $variable+3")
|
||||
}
|
||||
else -> throw AssemblyError("weird node")
|
||||
else -> {
|
||||
TODO("rol long on expression $what")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@@ -2361,7 +2433,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
if(arg is PtArrayIndexer && resultRegister in arrayOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
|
||||
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
|
||||
// just read the msb byte out of the word array
|
||||
if(arg.splitWords) {
|
||||
if(arg.variable==null)
|
||||
@@ -2474,7 +2546,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
else -> throw AssemblyError("invalid reg")
|
||||
}
|
||||
} else {
|
||||
if(arg is PtArrayIndexer && resultRegister in arrayOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
|
||||
if(arg is PtArrayIndexer && resultRegister in setOf(null, RegisterOrPair.A, RegisterOrPair.Y, RegisterOrPair.X)) {
|
||||
// just read the lsb byte out of the word array
|
||||
if(arg.variable==null)
|
||||
TODO("support for ptr indexing ${arg.position}")
|
||||
|
||||
@@ -204,7 +204,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
|
||||
return when(arg) {
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
|
||||
if (arg.name in setOf("lsb", "msb", "lsw", "msw"))
|
||||
usesOtherRegistersWhileEvaluating(arg.args[0])
|
||||
else if (arg.name == "mkword" || arg.name == "mklong2")
|
||||
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
|
||||
|
||||
@@ -1528,7 +1528,7 @@ $shortcutLabel:""")
|
||||
}
|
||||
|
||||
private fun inplacemodificationRegisterAwithVariable(operator: String, variable: String, signed: Boolean) {
|
||||
if(operator in "+-" && variable in arrayOf("#1", "#$1", "#$01", "#%1", "#%00000001")) {
|
||||
if(operator in "+-" && variable in setOf("#1", "#$1", "#$01", "#%1", "#%00000001")) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
|
||||
if(operator=="+")
|
||||
asmgen.out(" inc a")
|
||||
|
||||
@@ -373,7 +373,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
"isspace" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in arrayOf(32, 13, 9, 10, 141, 160), func.position)
|
||||
NumericLiteral.fromBoolean(char in setOf(32, 13, 9, 10, 141, 160), func.position)
|
||||
}
|
||||
"isprint" -> {
|
||||
val char = args[0].number.toInt()
|
||||
|
||||
@@ -3,7 +3,7 @@ plugins {
|
||||
kotlin("jvm")
|
||||
// id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||
// id("io.github.goooler.shadow") version "8.1.8"
|
||||
id("com.gradleup.shadow") version "9.2.2"
|
||||
id("com.gradleup.shadow") version "9.3.1"
|
||||
id("com.peterabeles.gversion") version "1.10.3"
|
||||
}
|
||||
|
||||
|
||||
@@ -651,6 +651,30 @@ _try_iso
|
||||
}}
|
||||
}
|
||||
|
||||
sub str2long(str string) -> long {
|
||||
; -- convert a decimal string (terminated with a zero byte) into a long. Clobbers R0
|
||||
long result = 0
|
||||
bool @nozp negative = string[0] == '-'
|
||||
if negative or string[0]=='+'
|
||||
string++
|
||||
alias digit_char = cx16.r0L
|
||||
repeat {
|
||||
digit_char = @(string)
|
||||
if digit_char in '0' to '9' {
|
||||
result = (result<<1) + (result<<3) ; multiply by 10
|
||||
result += digit_char - '0' ; add digit
|
||||
} else {
|
||||
break ; Invalid character, stop processing
|
||||
}
|
||||
string++
|
||||
}
|
||||
|
||||
if negative
|
||||
return -result
|
||||
else
|
||||
return result
|
||||
}
|
||||
|
||||
asmsub bin2uword(str string @AY) -> uword @AY {
|
||||
; -- binary string (with or without '%') to uword.
|
||||
; stops parsing at the first character that's not a 0 or 1. (except leading %)
|
||||
|
||||
@@ -423,6 +423,7 @@ io_error:
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
; Returns true if the file is successfully opened and readable.
|
||||
; No need to check status(), unlike f_open_w() !
|
||||
; NOTE: may not work correctly for empty files. Try to avoid empty files on CBM DOS systems.
|
||||
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
|
||||
; if you're going to read from it yourself instead of using f_read()!
|
||||
f_close()
|
||||
@@ -1076,6 +1077,7 @@ io_error:
|
||||
sub exists(str filename) -> bool {
|
||||
; -- returns true if the given file exists on the disk, otherwise false
|
||||
; DON'T use this if you already have a file open with f_open!
|
||||
; NOTE: may not work correctly for empty files. Try to avoid empty files on CBM DOS systems.
|
||||
; NOTE: doesn't clear the dos error status and message, you'll have to read/clear that yourself (with status() for example)
|
||||
if f_open(filename) {
|
||||
f_close()
|
||||
|
||||
@@ -432,6 +432,7 @@ io_error:
|
||||
sub exists(str filename) -> bool {
|
||||
; -- returns true if the given file exists on the disk, otherwise false
|
||||
; DON'T use this if you already have a file open with f_open!
|
||||
; NOTE: may not work correctly for empty files. Try to avoid empty files on CBM DOS systems.
|
||||
; NOTE: doesn't clear the dos error status and message, you'll have to read/clear that yourself (with status() for example)
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
|
||||
@@ -331,6 +331,7 @@ close_end:
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
; Returns true if the file is successfully opened and readable.
|
||||
; No need to check status(), unlike f_open_w() !
|
||||
; NOTE: may not work correctly for empty files. Try to avoid empty files on CBM DOS systems.
|
||||
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
|
||||
; if you're going to read from it yourself instead of using f_read()!
|
||||
f_close()
|
||||
@@ -703,6 +704,7 @@ io_error:
|
||||
sub exists(str filename) -> bool {
|
||||
; -- returns true if the given file exists on the disk, otherwise false
|
||||
; DON'T use this if you already have a file open with f_open!
|
||||
; NOTE: may not work correctly for empty files. Try to avoid empty files on CBM DOS systems.
|
||||
; NOTE: doesn't clear the dos error status and message, you'll have to read/clear that yourself (with status() for example)
|
||||
if f_open(filename) {
|
||||
f_close()
|
||||
|
||||
@@ -249,7 +249,7 @@ sub str2uword(str string) -> uword {
|
||||
}
|
||||
|
||||
sub str2word(str string) -> word {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; -- returns the signed word value of the string number argument
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%ir {{
|
||||
@@ -299,6 +299,30 @@ sub hex2long(str string) -> long {
|
||||
}
|
||||
}
|
||||
|
||||
sub str2long(str string) -> long {
|
||||
; -- convert a decimal string (terminated with a zero byte) into a long. Clobbers R0
|
||||
long result = 0
|
||||
bool @nozp negative = string[0] == '-'
|
||||
if negative or string[0]=='+'
|
||||
string++
|
||||
alias digit_char = cx16.r0L
|
||||
repeat {
|
||||
digit_char = @(string)
|
||||
if digit_char in '0' to '9' {
|
||||
result = (result<<1) + (result<<3) ; multiply by 10
|
||||
result += digit_char - '0' ; add digit
|
||||
} else {
|
||||
break ; Invalid character, stop processing
|
||||
}
|
||||
string++
|
||||
}
|
||||
|
||||
if negative
|
||||
return -result
|
||||
else
|
||||
return result
|
||||
}
|
||||
|
||||
sub bin2uword(str string) -> uword {
|
||||
; -- binary string (with or without '%') to uword.
|
||||
; stops parsing at the first character that's not a 0 or 1. (except leading %)
|
||||
|
||||
@@ -62,7 +62,7 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
|
||||
)
|
||||
|
||||
internal fun builtinFunctionReturnType(function: String): InferredTypes.InferredType {
|
||||
if(function in arrayOf("set_carry", "set_irqd", "clear_carry", "clear_irqd"))
|
||||
if(function in setOf("set_carry", "set_irqd", "clear_carry", "clear_irqd"))
|
||||
return InferredTypes.InferredType.void()
|
||||
|
||||
val func = BuiltinFunctions.getValue(function)
|
||||
|
||||
@@ -27,7 +27,7 @@ internal class ErrorReporter(val colors: IConsoleColors): IErrorReporter {
|
||||
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
if(suggestImport)
|
||||
err("undefined symbol: ${symbol.joinToString(".")} (maybe you forgot to import the module that defines ${symbol.first()}?)", position)
|
||||
err("undefined symbol: ${symbol.joinToString(".")} (maybe you forgot to import a module that defines ${symbol.first()}?)", position)
|
||||
else
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
@@ -312,6 +312,10 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(jump)
|
||||
}
|
||||
|
||||
override fun visit(alias: Alias) {
|
||||
throw FatalAstException("all Alias nodes should have been removed from the AST ${alias.position}")
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if(block.name.startsWith('_'))
|
||||
errors.err("identifiers cannot start with an underscore", block.position)
|
||||
@@ -1221,14 +1225,14 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.string in arrayOf("enable_floats", "force_output", "no_sysinit", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused", "romable")}.any { !it })
|
||||
else if(directive.args.map{it.string in setOf("enable_floats", "force_output", "no_sysinit", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused", "romable")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
if(directive.parent is Block) {
|
||||
if(directive.args.any {it.string !in arrayOf("force_output", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused")})
|
||||
if(directive.args.any {it.string !in setOf("force_output", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused")})
|
||||
err("using an option that is not valid for blocks")
|
||||
}
|
||||
if(directive.parent is Module) {
|
||||
if(directive.args.any {it.string !in arrayOf("enable_floats", "no_sysinit", "no_symbol_prefixing", "ignore_unused", "romable")})
|
||||
if(directive.args.any {it.string !in setOf("enable_floats", "no_sysinit", "no_symbol_prefixing", "ignore_unused", "romable")})
|
||||
err("using an option that is not valid for modules")
|
||||
}
|
||||
if(directive.args.any { it.string=="verafxmuls" } && compilerOptions.compTarget.name != Cx16Target.NAME)
|
||||
@@ -1247,7 +1251,7 @@ internal class AstChecker(private val program: Program,
|
||||
for(arg in directive.args) {
|
||||
val target = directive.definingScope.lookup(arg.string!!.split('.'))
|
||||
if(target==null)
|
||||
errors.err("undefined symbol: ${arg.string}", arg.position)
|
||||
errors.undefined(listOf(arg.string!!), position = arg.position)
|
||||
else if (target !is Subroutine)
|
||||
errors.err("jmptable entry can only be a subroutine: ${arg.string}", arg.position)
|
||||
}
|
||||
@@ -1591,7 +1595,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(leftDt.isBool || rightDt.isBool ||
|
||||
(expr.left as? TypecastExpression)?.expression?.inferType(program)?.isBool==true ||
|
||||
(expr.right as? TypecastExpression)?.expression?.inferType(program)?.isBool==true) {
|
||||
if(expr.operator in arrayOf("<", "<=", ">", ">=")) {
|
||||
if(expr.operator in setOf("<", "<=", ">", ">=")) {
|
||||
errors.err("can't use boolean operand with this comparison operator", expr.position)
|
||||
}
|
||||
// for now, don't enforce bool type with only logical operators...
|
||||
@@ -2186,7 +2190,7 @@ internal class AstChecker(private val program: Program,
|
||||
if (deref.inferType(program).isUnknown) {
|
||||
val symbol = deref.definingScope.lookup(deref.chain.take(1))
|
||||
if(symbol==null)
|
||||
errors.err("undefined symbol: ${deref.chain[0]}", deref.position)
|
||||
errors.undefined(deref.chain.take(1), position=deref.position)
|
||||
else
|
||||
errors.err("unable to determine type of dereferenced pointer expression", deref.position)
|
||||
}
|
||||
|
||||
@@ -84,6 +84,12 @@ internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, err
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
|
||||
// remove all alias nodes, every aliased identifier should have been replaced in the previous step
|
||||
return listOf(IAstModification.Remove(alias, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
walker.visit(this)
|
||||
@@ -241,11 +247,13 @@ internal fun IdentifierReference.checkFunctionOrLabelExists(program: Program, st
|
||||
else
|
||||
errors.err("cannot call that: ${this.nameInSource.joinToString(".")}", this.position)
|
||||
}
|
||||
is Alias, is StructDecl, is StructFieldRef -> {
|
||||
is StructFieldRef, is StructDecl, is Alias -> {
|
||||
return targetStatement
|
||||
}
|
||||
null -> {
|
||||
errors.undefined(this.nameInSource, this.firstTarget(program.builtinFunctions)==null, this.position)
|
||||
val alias = definingScope.lookup(this.nameInSource.take(1))
|
||||
if(alias !is Alias || alias.target.targetStatement(program.builtinFunctions)==null)
|
||||
errors.undefined(this.nameInSource, this.firstTarget(program.builtinFunctions)==null, this.position)
|
||||
}
|
||||
else -> errors.err("cannot call that: ${this.nameInSource.joinToString(".")}", this.position)
|
||||
}
|
||||
|
||||
@@ -41,14 +41,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
errors.err("invalid number of arguments: expected $expected but got $numArgs", pos)
|
||||
}
|
||||
|
||||
override fun visit(alias: Alias) {
|
||||
if(alias.target.targetStatement(program.builtinFunctions)==null)
|
||||
errors.err("undefined symbol: ${alias.target.nameInSource.joinToString(".") }", alias.target.position)
|
||||
|
||||
if(alias.alias == alias.target.nameInSource.first())
|
||||
errors.err("alias references itself", alias.position)
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null) {
|
||||
@@ -233,7 +225,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
private fun visitFunctionCall(call: IFunctionCall) {
|
||||
fun check(target: Statement?, aliasDepth: Int) {
|
||||
fun check(target: Statement?) {
|
||||
when (target) {
|
||||
is Subroutine -> {
|
||||
val expectedNumberOfArgs: Int = target.parameters.size
|
||||
@@ -275,12 +267,8 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
errors.err("wrong address variable datatype, expected uword", call.target.position)
|
||||
}
|
||||
is Alias -> {
|
||||
if(aliasDepth>1000) {
|
||||
errors.err("circular alias", target.position)
|
||||
} else {
|
||||
val actualtarget = target.target.targetStatement(program.builtinFunctions)
|
||||
check(actualtarget, aliasDepth + 1)
|
||||
}
|
||||
val actualtarget = target.target.targetStatement(program.builtinFunctions)
|
||||
check(actualtarget)
|
||||
}
|
||||
is StructDecl, is StructFieldRef -> {}
|
||||
null -> {} // symbol error is given elsewhere
|
||||
@@ -288,6 +276,6 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
}
|
||||
}
|
||||
|
||||
check(call.target.targetStatement(program.builtinFunctions), 0)
|
||||
check(call.target.targetStatement(program.builtinFunctions))
|
||||
}
|
||||
}
|
||||
@@ -341,11 +341,51 @@ class AstPreprocessor(val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(alias: Alias, parent: Node): Iterable<IAstModification> {
|
||||
// shortcut aliases that point to aliases (remove alias chains)
|
||||
val tgt = alias.target.targetStatement(program.builtinFunctions)
|
||||
if(tgt is Block) {
|
||||
errors.err("cannot alias blocks", alias.target.position)
|
||||
|
||||
if(tgt==null) {
|
||||
errors.undefined(alias.target.nameInSource, position = alias.target.position)
|
||||
} else {
|
||||
if(alias.alias == alias.target.nameInSource.first()) {
|
||||
errors.err("alias loop", alias.position)
|
||||
} else if(tgt is Alias) {
|
||||
var chainedAlias = alias
|
||||
var chainedTargetName = alias.target
|
||||
var maxhops = 100
|
||||
while(true) {
|
||||
val tgt2 = chainedAlias.target.targetStatement(program.builtinFunctions)
|
||||
if (tgt2 is Alias) {
|
||||
chainedAlias = tgt2
|
||||
chainedTargetName = tgt2.target
|
||||
}
|
||||
else {
|
||||
val tgt2 = chainedTargetName.targetStatement(program.builtinFunctions) as? INamedStatement
|
||||
val replacement = if(tgt2!=null) {
|
||||
if(tgt2 is BuiltinFunctionPlaceholder) {
|
||||
val unscopedTarget = IdentifierReference(listOf(tgt2.name), alias.position)
|
||||
Alias(alias.alias, unscopedTarget, alias.position)
|
||||
} else if(tgt2.scopedName != chainedTargetName.nameInSource) {
|
||||
val scopedTarget = IdentifierReference(tgt2.scopedName, alias.position)
|
||||
Alias(alias.alias, scopedTarget, alias.position)
|
||||
} else {
|
||||
Alias(alias.alias, chainedTargetName, alias.position)
|
||||
}
|
||||
} else {
|
||||
Alias(alias.alias, chainedTargetName, alias.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(alias, replacement, parent))
|
||||
}
|
||||
maxhops--
|
||||
if(maxhops==0) {
|
||||
errors.err("alias loop", alias.position)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
@@ -23,16 +23,11 @@ internal class CodeDesugarer(val program: Program, private val target: ICompilat
|
||||
// - pointer[word] replaced by @(pointer+word)
|
||||
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
|
||||
// - flatten chained assignments
|
||||
// - remove alias nodes
|
||||
// - convert on..goto/call to jumpaddr array and separate goto/call
|
||||
// - replace implicit pointer dereference chains (a.b.c.d) with explicit ones (a^^.b^^.c^^.d)
|
||||
// - replace ptr^^ by @(ptr) if ptr is just an uword.
|
||||
// - replace p1^^ = p2^^ by memcopy.
|
||||
|
||||
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(alias, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
|
||||
val label = program.makeLabel("after", breakStmt.position)
|
||||
@@ -281,8 +276,6 @@ _after:
|
||||
val indexExpr = arrayIndexedExpression.indexer.indexExpr
|
||||
val arrayVar = arrayIndexedExpression.plainarrayvar!!.targetVarDecl()
|
||||
if(arrayVar!=null && (arrayVar.datatype.isUnsignedWord || arrayVar.datatype.isPointer)) {
|
||||
if(parent is IFunctionCall && parent.target.nameInSource.singleOrNull() in InplaceModifyingBuiltinFunctions)
|
||||
return noModifications
|
||||
val wordIndex = TypecastExpression(indexExpr, DataType.UWORD, true, indexExpr.position)
|
||||
val address = BinaryExpression(
|
||||
arrayIndexedExpression.plainarrayvar!!.copy(),
|
||||
@@ -572,10 +565,6 @@ _after:
|
||||
val firstDt = firstTarget?.datatype
|
||||
if (firstDt?.isPointer == true) {
|
||||
// the a.b.c.d can be a pointer dereference chain ending in a struct field; a^^.b^^.c^^.d
|
||||
// don't change aliases!
|
||||
if(parent is Alias)
|
||||
return noModifications
|
||||
|
||||
val chain = mutableListOf(identifier.nameInSource[0])
|
||||
var struct = firstDt.subType
|
||||
for(name in identifier.nameInSource.drop(1)) {
|
||||
|
||||
+33
-59
@@ -126,18 +126,8 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
|
||||
val target = alias.target.targetStatement()
|
||||
if(target is Alias) {
|
||||
// shortcut the alias that refers to another alias
|
||||
val newAlias = Alias(alias.alias, target.target, alias.position)
|
||||
return listOf(IAstModification.ReplaceNode(alias, newAlias, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
val target = identifier.targetStatement()
|
||||
val target = identifier.targetStatement(program.builtinFunctions)
|
||||
|
||||
if(target is StructFieldRef) {
|
||||
// replace a.b.c.d by a^^.b^^.c^^.d
|
||||
@@ -153,56 +143,45 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
// maybe the first component of the scoped name is an alias?
|
||||
val tgt2 = identifier.definingScope.lookup(identifier.nameInSource.take(1)) as? Alias
|
||||
if(tgt2!=null && parent !is Alias) {
|
||||
if(tgt2.target.targetStatement() !is Alias) {
|
||||
if(tgt2.target.targetStatement(program.builtinFunctions) !is Alias) {
|
||||
val actual = IdentifierReference(tgt2.target.nameInSource + identifier.nameInSource.drop(1), identifier.position)
|
||||
return listOf(IAstModification.ReplaceNode(identifier, actual, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't replace an identifier in an Alias or when the alias points to another alias (that will be resolved first elsewhere)
|
||||
if(target is Alias && parent !is Alias) {
|
||||
val targetStatement = target.target.targetStatement()
|
||||
if (targetStatement !is Alias) {
|
||||
val replacement: IdentifierReference =
|
||||
when (targetStatement) {
|
||||
is StructFieldRef -> {
|
||||
// replace with target struct field reference
|
||||
target.target.copy(position = identifier.position)
|
||||
}
|
||||
is VarDecl -> {
|
||||
val scoped = if (targetStatement.names.isNotEmpty()) {
|
||||
// points to a vardecl of multiple names in 1 statement
|
||||
(targetStatement.parent as INamedStatement).scopedName + target.target.nameInSource.last()
|
||||
} else {
|
||||
(targetStatement as INamedStatement).scopedName
|
||||
}
|
||||
require(scoped.last() != "<multiple>")
|
||||
IdentifierReference(scoped, identifier.position)
|
||||
}
|
||||
else -> {
|
||||
// replace with scoped identifier
|
||||
val scoped = (targetStatement as INamedStatement).scopedName
|
||||
require(scoped.last() != "<multiple>")
|
||||
IdentifierReference(scoped, identifier.position)
|
||||
}
|
||||
if(target is Alias && parent !is Alias) { // don't replace an identifier in an Alias
|
||||
val targetStatement = target.target.targetStatement(program.builtinFunctions)
|
||||
val replacement: IdentifierReference =
|
||||
when (targetStatement) {
|
||||
is StructFieldRef -> {
|
||||
// replace with target struct field reference
|
||||
target.target.copy(position = identifier.position)
|
||||
}
|
||||
is VarDecl -> {
|
||||
val scoped = if (targetStatement.names.isNotEmpty()) {
|
||||
// points to a vardecl of multiple names in 1 statement
|
||||
(targetStatement.parent as INamedStatement).scopedName + target.target.nameInSource.last()
|
||||
} else {
|
||||
(targetStatement as INamedStatement).scopedName
|
||||
}
|
||||
require(scoped.last() != "<multiple>")
|
||||
IdentifierReference(scoped, identifier.position)
|
||||
}
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
// replace with unscoped identifier
|
||||
IdentifierReference(listOf(targetStatement.name), identifier.position)
|
||||
}
|
||||
else -> {
|
||||
// replace with scoped identifier
|
||||
val scoped = (targetStatement as INamedStatement).scopedName
|
||||
require(scoped.last() != "<multiple>")
|
||||
IdentifierReference(scoped, identifier.position)
|
||||
}
|
||||
}
|
||||
|
||||
return listOf(IAstModification.ReplaceNode(identifier, replacement, parent))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(identifier, replacement, parent))
|
||||
}
|
||||
|
||||
// experimental code to be able to alias blocks too:
|
||||
// if(target is INamedStatement) {
|
||||
// if (identifier.nameInSource != target.scopedName) {
|
||||
// val blockAlias = identifier.definingScope.lookup(identifier.nameInSource.take(1))
|
||||
// if(blockAlias is Alias) {
|
||||
// val newname = mutableListOf(blockAlias.target.nameInSource.single())
|
||||
// newname.addAll(identifier.nameInSource.drop(1))
|
||||
// return listOf(IAstModification.ReplaceNode(identifier, IdentifierReference(newname, position = identifier.position), parent))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -212,7 +191,7 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
val rightIndex = expr.right as? ArrayIndexedExpression
|
||||
if (leftIdent != null && rightIndex != null) {
|
||||
// maybe recombine IDENTIFIER . ARRAY[IDX] --> COMBINEDIDENTIFIER[IDX]
|
||||
val leftTarget = leftIdent.targetStatement()
|
||||
val leftTarget = leftIdent.targetStatement(program.builtinFunctions)
|
||||
if(leftTarget==null || leftTarget !is StructDecl) {
|
||||
if(rightIndex.plainarrayvar!=null) {
|
||||
val combinedName = leftIdent.nameInSource + rightIndex.plainarrayvar!!.nameInSource
|
||||
@@ -228,17 +207,12 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(deref: ArrayIndexedPtrDereference, parent: Node): Iterable<IAstModification> {
|
||||
// TODO handle aliases?
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(deref: PtrDereference, parent: Node): Iterable<IAstModification> {
|
||||
// handle aliases
|
||||
// maybe the first component of the dereference chain is an alias?
|
||||
val tgt2 = deref.definingScope.lookup(deref.chain.take(1)) as? Alias
|
||||
if(tgt2!=null && parent !is Alias) {
|
||||
if(tgt2.target.targetStatement() !is Alias) {
|
||||
if(tgt2.target.targetStatement(program.builtinFunctions) !is Alias) {
|
||||
val unaliased = PtrDereference(tgt2.target.nameInSource + deref.chain.drop(1), deref.derefLast, deref.position)
|
||||
return listOf(IAstModification.ReplaceNode(deref, unaliased, parent))
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
|
||||
is ChainedAssignment -> throw FatalAstException("ChainedAssignment should have been flattened")
|
||||
is Assignment -> transform(statement)
|
||||
is Block -> transform(statement)
|
||||
is Alias -> throw FatalAstException("alias should have been desugared")
|
||||
is Alias -> throw FatalAstException("alias should have been removed")
|
||||
is Break -> throw FatalAstException("break should have been replaced by Goto")
|
||||
is Continue -> throw FatalAstException("continue should have been replaced by Goto")
|
||||
is BuiltinFunctionPlaceholder -> throw FatalAstException("BuiltinFunctionPlaceholder should not occur in Ast here")
|
||||
|
||||
@@ -360,7 +360,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
if (rightConstVal?.number == 1.0) {
|
||||
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
if(!dt.isLong && dt!=BaseDataType.UNDEFINED) {
|
||||
val right = NumericLiteral(dt, rightConstVal.number, rightConstVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
@@ -369,7 +369,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
else if (rightConstVal?.number == 0.0) {
|
||||
if (rightDt != leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
if(!dt.isLong && dt!=BaseDataType.UNDEFINED) {
|
||||
val right = NumericLiteral(dt, rightConstVal.number, rightConstVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
@@ -386,7 +386,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
if (rightConstVal?.number == 1.0) {
|
||||
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
if(!dt.isLong && dt!=BaseDataType.UNDEFINED) {
|
||||
val right = NumericLiteral(dt, rightConstVal.number, rightConstVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
@@ -395,7 +395,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
else if (rightConstVal?.number == 0.0) {
|
||||
if(rightDt!=leftDt && !(leftDt.isPointer && rightDt.isUnsignedWord)) {
|
||||
val dt = if(leftDt.isPointer) BaseDataType.UWORD else leftDt.base
|
||||
if(!dt.isLong) {
|
||||
if(!dt.isLong && dt!=BaseDataType.UNDEFINED) {
|
||||
val right = NumericLiteral(dt, rightConstVal.number, rightConstVal.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, right, expr))
|
||||
}
|
||||
|
||||
@@ -85,10 +85,10 @@ class TestNumbers: FunSpec({
|
||||
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03u, 0xb9u, 0x1du, 0x15u, 0x63u)
|
||||
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }.message shouldContain "mflpt range: 1.7014118346E38"
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }.message shouldContain "mflpt range: -1.7014118346E38"
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }.message shouldContain "mflpt range: 1.7014118347E38"
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }.message shouldContain "mflpt range: -1.7014118347E38"
|
||||
}
|
||||
|
||||
test("testMflpt5ToFloat") {
|
||||
|
||||
@@ -2432,7 +2432,7 @@ main {
|
||||
|
||||
sub alias1() {
|
||||
alias TheNode = structdefs.Node
|
||||
^^TheNode node = 20000
|
||||
^^TheNode @shared node = 20000
|
||||
node.value = 100
|
||||
}
|
||||
|
||||
@@ -2444,7 +2444,7 @@ main {
|
||||
|
||||
sub alias3() {
|
||||
alias TheNode = structdefs.Node
|
||||
^^TheNode node = 20000
|
||||
^^TheNode @shared node = 20000
|
||||
node++
|
||||
}
|
||||
|
||||
|
||||
@@ -802,12 +802,23 @@ main {
|
||||
notdefined -> bar = 1
|
||||
else -> bar = 2
|
||||
}
|
||||
|
||||
bool z = undefined != 0
|
||||
bool z2 = undefined == 0
|
||||
if undefined==0
|
||||
z= true
|
||||
if undefined!=0
|
||||
z= false
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors.size shouldBe 10
|
||||
errors.errors[1] shouldContain "undefined symbol"
|
||||
errors.errors[2] shouldContain "undefined symbol"
|
||||
errors.errors[4] shouldContain "undefined symbol"
|
||||
errors.errors[6] shouldContain "undefined symbol"
|
||||
errors.errors[8] shouldContain "undefined symbol"
|
||||
}
|
||||
|
||||
test("return unsigned values for signed results ok if value fits") {
|
||||
|
||||
@@ -146,10 +146,11 @@ main {
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, writeAssembly=false, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[0] shouldContain "references itself"
|
||||
errors.errors.size shouldBe 4
|
||||
errors.errors[0] shouldContain "alias loop"
|
||||
errors.errors[1] shouldContain "undefined symbol: xx.yy"
|
||||
errors.errors[2] shouldContain "references itself"
|
||||
errors.errors[2] shouldContain "alias loop"
|
||||
errors.errors[3] shouldContain "alias loop"
|
||||
}
|
||||
|
||||
test("alias scopes") {
|
||||
@@ -264,6 +265,36 @@ main {
|
||||
errors.errors[4] shouldContain "invalid number of arguments: expected 1 but got 0"
|
||||
errors.errors[5] shouldContain "invalid number of arguments: expected 2 but got 0"
|
||||
}
|
||||
|
||||
xtest("chained aliasing") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
alias s1 = testblock.sub1
|
||||
alias s2 = s1.sub2 ; TODO fix alias error
|
||||
alias vx = s2.var2 ; TODO fix alias error
|
||||
vx = 999
|
||||
|
||||
alias tb = testblock
|
||||
alias vv = tb.variable ; TODO fix alias error
|
||||
vv = 99
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
testblock {
|
||||
ubyte @shared variable
|
||||
|
||||
sub sub1() {
|
||||
sub sub2() {
|
||||
ubyte @shared var2
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors=errors) shouldNotBe null
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
context("strings") {
|
||||
|
||||
@@ -585,4 +585,25 @@ main {
|
||||
branches.size shouldBeLessThanOrEqual 1
|
||||
}
|
||||
}
|
||||
|
||||
xtest("deeply scoped variable references") {
|
||||
val src= $"""
|
||||
main {
|
||||
sub start() {
|
||||
main.sub1.sub2.sub3.variable = 100
|
||||
}
|
||||
|
||||
sub sub1() {
|
||||
sub sub2() {
|
||||
sub sub3() {
|
||||
ubyte @shared variable
|
||||
}
|
||||
}
|
||||
}
|
||||
}"""
|
||||
compileText(Cx16Target(), true, src, outputDir) shouldNotBe null
|
||||
val result = compileText(VMTarget(), true, src, outputDir)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
VmRunner().runProgram(virtfile.readText(), true) // TODO fix vm crash on deeply scoped symbol ref
|
||||
}
|
||||
})
|
||||
@@ -84,6 +84,13 @@ interface IStatementContainer {
|
||||
when(stmt) {
|
||||
is VarDecl -> if(stmt.name==name || stmt.names.contains(name)) return stmt
|
||||
is INamedStatement -> if(stmt.name==name) return stmt
|
||||
is When -> {
|
||||
stmt.choices.forEach {
|
||||
val found = it.statements.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
}
|
||||
is AnonymousScope -> {
|
||||
val found = stmt.searchSymbol(name)
|
||||
if(found!=null)
|
||||
@@ -94,37 +101,30 @@ interface IStatementContainer {
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is ConditionalBranch -> {
|
||||
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is ForLoop -> {
|
||||
val found = stmt.body.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is WhileLoop -> {
|
||||
val found = stmt.body.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is RepeatLoop -> {
|
||||
val found = stmt.body.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is WhileLoop -> {
|
||||
val found = stmt.body.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is UntilLoop -> {
|
||||
val found = stmt.body.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is When -> {
|
||||
stmt.choices.forEach {
|
||||
val found = it.statements.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is ConditionalBranch -> {
|
||||
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is Alias -> if(stmt.alias==name) return stmt
|
||||
else -> {
|
||||
|
||||
@@ -654,8 +654,7 @@ class NumericLiteral(val type: BaseDataType, // only numerical types allowed
|
||||
BaseDataType.LONG -> { /*no hard requirement, the range will be checked later to give a proper error message instead of a crash */ }
|
||||
BaseDataType.BOOL -> require(number==0.0 || number==1.0)
|
||||
BaseDataType.POINTER -> throw FatalAstException("pointer literals should not be created, should have been UWORD $position")
|
||||
else -> require(type.isNumericOrBool) {
|
||||
"numeric literal type should be numeric or bool: $type" }
|
||||
else -> require(type.isNumericOrBool) {"numeric literal type should be numeric or bool: $type at $position" }
|
||||
}
|
||||
if(type!=BaseDataType.FLOAT) {
|
||||
if(truncate(number) != number)
|
||||
@@ -1769,27 +1768,26 @@ class PtrDereference(
|
||||
fun resultType(dt: DataType?) = if(dt==null) InferredTypes.unknown() else InferredTypes.knownFor(if(derefLast) dt.dereference() else dt)
|
||||
|
||||
val target = definingScope.lookup(chain) ?: return InferredTypes.unknown()
|
||||
if(target is VarDecl)
|
||||
return resultType(target.datatype)
|
||||
if(target is StructFieldRef)
|
||||
return resultType(target.type)
|
||||
if(target is Alias)
|
||||
return target.target.inferType(program)
|
||||
|
||||
TODO("infertype $chain -> $target")
|
||||
return when (target) {
|
||||
is VarDecl -> resultType(target.datatype)
|
||||
is StructFieldRef -> resultType(target.type)
|
||||
is Alias -> target.target.inferType(program)
|
||||
else -> TODO("infertype $chain -> $target")
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) =
|
||||
throw FatalAstException("can't replace here")
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = chain.size==1 && chain==nameInSource
|
||||
fun isSamePointerDeref(other: Expression?): Boolean {
|
||||
if(other==null || other !is PtrDereference)
|
||||
return false
|
||||
if(derefLast != other.derefLast)
|
||||
return false
|
||||
if(chain != other.chain)
|
||||
return false
|
||||
return true
|
||||
return if(other==null || other !is PtrDereference)
|
||||
false
|
||||
else if(derefLast != other.derefLast)
|
||||
false
|
||||
else if(chain != other.chain)
|
||||
false
|
||||
else
|
||||
true
|
||||
}
|
||||
|
||||
fun firstTarget(): Statement? = definingScope.lookup(chain.take(1))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.1.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-c128.p8
|
||||
@@ -121,6 +121,7 @@ conv {
|
||||
internal_uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X
|
||||
internal_uword2hex (uword value @AY) clobbers (A,Y)
|
||||
str2byte (str string @AY) clobbers (Y) -> byte @A
|
||||
str2long (str string) -> long
|
||||
str2ubyte (str string @AY) clobbers (Y) -> ubyte @A
|
||||
str2uword (str string @AY) -> uword @AY
|
||||
str2word (str string @AY) -> word @AY
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.1.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-c64.p8
|
||||
@@ -121,6 +121,7 @@ conv {
|
||||
internal_uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X
|
||||
internal_uword2hex (uword value @AY) clobbers (A,Y)
|
||||
str2byte (str string @AY) clobbers (Y) -> byte @A
|
||||
str2long (str string) -> long
|
||||
str2ubyte (str string @AY) clobbers (Y) -> ubyte @A
|
||||
str2uword (str string @AY) -> uword @AY
|
||||
str2word (str string @AY) -> word @AY
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.1.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-cx16.p8
|
||||
@@ -159,6 +159,7 @@ conv {
|
||||
internal_uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X
|
||||
internal_uword2hex (uword value @AY) clobbers (A,Y)
|
||||
str2byte (str string @AY) clobbers (Y) -> byte @A
|
||||
str2long (str string) -> long
|
||||
str2ubyte (str string @AY) clobbers (Y) -> ubyte @A
|
||||
str2uword (str string @AY) -> uword @AY
|
||||
str2word (str string @AY) -> word @AY
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.1.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-pet32.p8
|
||||
@@ -121,6 +121,7 @@ conv {
|
||||
internal_uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X
|
||||
internal_uword2hex (uword value @AY) clobbers (A,Y)
|
||||
str2byte (str string @AY) clobbers (Y) -> byte @A
|
||||
str2long (str string) -> long
|
||||
str2ubyte (str string @AY) clobbers (Y) -> ubyte @A
|
||||
str2uword (str string @AY) -> uword @AY
|
||||
str2word (str string @AY) -> word @AY
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.1.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-virtual.p8
|
||||
@@ -94,6 +94,7 @@ conv {
|
||||
internal_str_ub (ubyte value, str out_ptr)
|
||||
internal_str_uw (uword value, str out_ptr)
|
||||
str2byte (str string) -> byte
|
||||
str2long (str string) -> long
|
||||
str2ubyte (str string) -> ubyte
|
||||
str2uword (str string) -> uword
|
||||
str2word (str string) -> word
|
||||
|
||||
@@ -463,7 +463,7 @@ conv
|
||||
Routines to convert strings to numbers or vice versa.
|
||||
|
||||
- numbers to strings, in various formats (binary, hex, decimal)
|
||||
- strings in decimal, hex and binary format into numbers (bytes, words)
|
||||
- strings in decimal, hex and binary format into numbers (bytes, words, longs)
|
||||
|
||||
Read the :source:`conv source code <compiler/res/prog8lib/conv.p8>`
|
||||
to see what's in there.
|
||||
@@ -586,6 +586,7 @@ to see what's in there. (Note: slight variations for different compiler targets)
|
||||
Error handling is peculiar on CBM dos systems (C64, C128, cx16, PET). Read the
|
||||
descriptions for the various methods in this library for details and tips.
|
||||
PET support is limited, not all routines are available for this system.
|
||||
Also there are some weird cases in CBM dos when dealing with empty (0-byte) files. It's best to avoid such files altogether.
|
||||
|
||||
|
||||
emudbg (cx16 only)
|
||||
|
||||
@@ -125,8 +125,10 @@ The ``alias`` statement makes it easier to refer to symbols from other places, a
|
||||
you from having to type the fully scoped name everytime you need to access that symbol.
|
||||
Aliases can be created in any scope except at the module level.
|
||||
An alias is created with ``alias <name> = <target>`` and then you can use ``<name>`` as if it were ``<target>``.
|
||||
It is possible to alias variables, labels and subroutines, but not whole blocks.
|
||||
The name has to be an unscoped identifier name, the target can be any symbol.
|
||||
It is possible to alias variables, labels and subroutines, and even whole blocks.words
|
||||
The name has to be an unscoped identifier name, the target can be any scoped or unscoped symbol.
|
||||
Please consider using aliases sparingly because it may lead to confusing code if you alias
|
||||
well-known block names for example.
|
||||
|
||||
|
||||
.. _blocks:
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
Known bugs:
|
||||
- fix chained aliasing errors see test "chained aliasing"
|
||||
- fix crash in ir loader/vm for deeply nested symbol reference, see test "deeply scoped variable references"
|
||||
|
||||
|
||||
Weird Heisenbug
|
||||
^^^^^^^^^^^^^^^
|
||||
- BUG: examples/cube3d-float crashes with div by zero error on C64 (works on cx16. ALready broken in v11, v10 still worked)
|
||||
@@ -10,10 +15,10 @@ Weird Heisenbug
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- make swap() a special syntax node with assigntarget arg types instead of a builtin function call (see swap-statement branch)
|
||||
- make builtin functions capable of returning multiple values, then make divmod() return the 2 results rather than accepting 2 extra variables as arguments
|
||||
- then also introduce lmh(longvalue) -or whatever sensible name- builtin function that returns the low, mid, hi (bank) bytes of a long.
|
||||
- and rewrite the divmod (and others?) function to just return the 2 results instead of taking target variables as arguments.
|
||||
- allow alias to blocks?
|
||||
- add a -profile option (for now X16 only) that instruments the start (and returns?) -of every prog8 subroutine with code that dumps to the X16 emulator debug console: name of sub, stack pointer (for call depth!), emudbg cycle count. Save/restore all used registers! Start of program must set cycle count to zero.
|
||||
- add @private to variables and subroutines declared in a scope to make them invisible from outside that scope?
|
||||
- when implementing unsigned longs: remove the (multiple) "TODO "hack" to allow unsigned long constants to be used as values for signed longs, without needing a cast"
|
||||
@@ -31,7 +36,6 @@ Future Things and Ideas
|
||||
- should we have a SourceStorageKind.POINTER? (there is one for TargetStorageKind...)
|
||||
- make memory mapped variables support more constant expressions such as: &uword MyHigh = &mylong1+2 (see github issue #192)
|
||||
- allow memory() to occur in array initializer (maybe needed for 2 dimensional arrays?) i.e. make it a constant (see github issue #192)
|
||||
- handle Alias in a general way in LiteralsToAutoVarsAndRecombineIdentifiers instead of replacing it scattered over multiple functions
|
||||
- allow the value of a memory mapped variable to be address-of another variable, not just a constant number
|
||||
- implement for loops with long loopvar over long range expression
|
||||
- Make all constants long by default? or not? (remove type name altogether), reduce to target type implictly if the actual value fits. Experiment is in branch 'long-consts'
|
||||
@@ -111,6 +115,7 @@ IR/VM
|
||||
|
||||
Libraries
|
||||
---------
|
||||
- diskio: make f_open (and f_open_w?) and exists to read the command channel for success status rather than depending on flaky ST
|
||||
- Add split-word array sorting routines to sorting module?
|
||||
- make a list of all floats.* routines that the compiler expects for full float support?
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ Arrays
|
||||
.. index:: pair: Data Types; Arrays
|
||||
|
||||
Arrays can be created from a list of booleans, bytes, words, floats, addresses of other variables
|
||||
(such as explicit address-of expressions, strings, or other array variables), and struct initializers.
|
||||
(such as explicit address-of expressions, strings, or other array variables), pointers, or struct initializers.
|
||||
The values in an array literal always have to be constants.
|
||||
A trailing comma is allowed, sometimes this is easier when copying values
|
||||
or when adding more stuff to the array later. Here are some examples of arrays::
|
||||
@@ -336,6 +336,7 @@ or when adding more stuff to the array later. Here are some examples of arrays::
|
||||
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to array of uwords)
|
||||
uword[] others = [names, array] ; array of pointers/addresses to other arrays
|
||||
bool[2] flags = [true, false] ; array of two boolean values (take up 1 byte each, like a byte array)
|
||||
^^float[3] values ; array of three pointers to floats
|
||||
|
||||
value = array[3] ; the fourth value in the array (index is 0-based)
|
||||
char = string[4] ; the fifth character (=byte) in the string
|
||||
|
||||
+152
-10
@@ -1,17 +1,159 @@
|
||||
%import textio
|
||||
%import conv
|
||||
%import strings
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
; Test the routine
|
||||
sub start() {
|
||||
bool ans
|
||||
if true {
|
||||
ans = false
|
||||
if (ans == true) {
|
||||
return
|
||||
} else {
|
||||
goto done
|
||||
}
|
||||
}
|
||||
done:
|
||||
str test_string1 = "12345"
|
||||
str test_string2 = "-98765"
|
||||
str test_string3 = "0"
|
||||
str test_string4 = "2147483647" ; Max positive 32-bit signed integer
|
||||
str test_string5 = "-2147483648" ; Min negative 32-bit signed integer
|
||||
str test_string6 = "42"
|
||||
str test_string7 = "1000000000" ; Large number
|
||||
str test_string8 = "" ; Empty string
|
||||
str test_string9 = "abc123" ; Invalid string
|
||||
str test_string10 = "255" ; Max unsigned byte
|
||||
str test_string11 = "127" ; Max signed byte
|
||||
str test_string12 = "-128" ; Min signed byte
|
||||
str test_string13 = "65535" ; Max unsigned word
|
||||
str test_string14 = "32767" ; Max signed word
|
||||
str test_string15 = "-32768" ; Min signed word
|
||||
str test_string16 = "100000" ; Just over 65535/10
|
||||
str test_string17 = "999999" ; Larger number
|
||||
str test_string18 = "+999999" ; Larger number with plus
|
||||
|
||||
long result1 = conv.str2long(test_string1)
|
||||
long result2 = conv.str2long(test_string2)
|
||||
long result3 = conv.str2long(test_string3)
|
||||
long result4 = conv.str2long(test_string4)
|
||||
long result5 = conv.str2long(test_string5)
|
||||
long result6 = conv.str2long(test_string6)
|
||||
long result7 = conv.str2long(test_string7)
|
||||
long result8 = conv.str2long(test_string8) ; This should return 0 for empty string
|
||||
long result9 = conv.str2long(test_string9) ; This should return 0 since it starts with non-digit
|
||||
long result10 = conv.str2long(test_string10) ; Max unsigned byte
|
||||
long result11 = conv.str2long(test_string11) ; Max signed byte
|
||||
long result12 = conv.str2long(test_string12) ; Min signed byte
|
||||
long result13 = conv.str2long(test_string13) ; Max unsigned word
|
||||
long result14 = conv.str2long(test_string14) ; Max signed word
|
||||
long result15 = conv.str2long(test_string15) ; Min signed word
|
||||
long result16 = conv.str2long(test_string16) ; Just over 65535/10
|
||||
long result17 = conv.str2long(test_string17) ; Larger number
|
||||
long result18 = conv.str2long(test_string18) ; Larger number with +
|
||||
|
||||
; Print results using the built-in textio library
|
||||
txt.print("str ")
|
||||
txt.print(test_string1)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result1)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string2)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result2)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string3)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result3)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string4)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result4)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string5)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result5)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string6)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result6)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string7)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result7)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string8)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result8)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string9)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result9)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string10)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result10)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string11)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result11)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string12)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result12)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string13)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result13)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string14)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result14)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string15)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result15)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string16)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result16)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string17)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result17)
|
||||
txt.nl()
|
||||
|
||||
txt.print("str ")
|
||||
txt.print(test_string18)
|
||||
txt.print(" = ")
|
||||
txt.print_l(result18)
|
||||
txt.nl()
|
||||
|
||||
; To make sure the program finishes properly
|
||||
return
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,4 +4,4 @@ org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
org.gradle.configuration-cache=false
|
||||
kotlin.code.style=official
|
||||
version=12.1
|
||||
version=12.1.1
|
||||
@@ -0,0 +1,748 @@
|
||||
# ANTLR4 Grammar Improvements for Prog8
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines recommended improvements to the `Prog8ANTLR.g4` grammar file, focusing on clearer parse errors, rule optimization, and maintainability.
|
||||
|
||||
## Current Issues Summary
|
||||
|
||||
The Prog8 ANTLR4 grammar has several areas that could be improved:
|
||||
|
||||
1. **Poor Error Recovery**: Uses `BailErrorStrategy` that fails immediately on first error
|
||||
2. **Generic Error Messages**: Users receive cryptic ANTLR default messages
|
||||
3. **Complex Expression Rule**: 25+ alternatives in a single left-recursive rule
|
||||
4. **Fragile Lexer Rules**: `NOT_IN` token depends on whitespace
|
||||
5. **Manual Keyword Handling**: Must explicitly list keywords that can be identifiers
|
||||
6. **"Cursed" Pointer Dereference**: Complex grammar that doesn't handle chaining well
|
||||
|
||||
---
|
||||
|
||||
## 1. Error Recovery & Reporting Improvements
|
||||
|
||||
### Current Issues
|
||||
- **BailErrorStrategy**: Fails immediately on first error with no recovery
|
||||
- **Generic error messages**: ANTLR default messages like "mismatched input" are unhelpful
|
||||
- **No rule-specific error messages**: Users get cryptic errors for common mistakes
|
||||
|
||||
### Recommended Improvements
|
||||
|
||||
#### Add Error Alternatives with Custom Messages
|
||||
|
||||
```antlr
|
||||
// Assignment rule with error detection
|
||||
assignment
|
||||
: assign_target '=' expression
|
||||
| assign_target '=' assignment
|
||||
| multi_assign_target '=' expression
|
||||
| assign_target '=' expression '=' expression+ // ERROR: chained assignment
|
||||
{ notifyErrorListeners("Cannot chain assignments. Use multiple statements instead."); }
|
||||
;
|
||||
```
|
||||
|
||||
#### Add Error Recovery Rules
|
||||
|
||||
```antlr
|
||||
statement
|
||||
: // ... valid statements
|
||||
| 'if' expression statement // Missing THEN or block
|
||||
{ notifyErrorListeners("Expected 'then' or '{' after if condition"); }
|
||||
| 'for' identifier 'in' // Missing expression
|
||||
{ notifyErrorListeners("Expected expression after 'in'"); }
|
||||
| 'while' '}' // Missing condition
|
||||
{ notifyErrorListeners("Expected condition after 'while'"); }
|
||||
;
|
||||
```
|
||||
|
||||
#### Improve Error Messages for Common Mistakes
|
||||
|
||||
```antlr
|
||||
vardecl
|
||||
: datatype (arrayindex | EMPTYARRAYSIG)? TAG* identifierlist
|
||||
| datatype EMPTYARRAYSG identifierlist
|
||||
{ if (!$datatype.text.equals("ubyte") && !$datatype.text.equals("byte"))
|
||||
notifyErrorListeners("Empty array syntax [] only valid for byte/ubyte types"); }
|
||||
;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Rule Optimization
|
||||
|
||||
### A. Expression Rule Refactoring
|
||||
|
||||
**Current Problem**: Massive left-recursive rule with 25+ alternatives (lines 202-233)
|
||||
|
||||
**Optimization**: Group by precedence using sub-rules:
|
||||
|
||||
```antlr
|
||||
expression
|
||||
: primaryExpression
|
||||
| expression postfixOperator
|
||||
| prefixOperator expression
|
||||
| expression multiplicativeOp expression
|
||||
| expression additiveOp expression
|
||||
| expression shiftOp expression
|
||||
| expression relationalOp expression
|
||||
| expression equalityOp expression
|
||||
| expression bitwiseAndOp expression
|
||||
| expression bitwiseXorOp expression
|
||||
| expression bitwiseOrOp expression
|
||||
| expression rangeOp expression
|
||||
| expression 'in' expression
|
||||
| expression 'not' 'in' expression
|
||||
| expression 'and' expression
|
||||
| expression 'or' expression
|
||||
| expression 'xor' expression
|
||||
| 'if' expression 'then' expression 'else' expression // if-expression
|
||||
;
|
||||
|
||||
// Operator groups
|
||||
multiplicativeOp: '*' | '/' | '%' ;
|
||||
additiveOp: '+' | '-' ;
|
||||
shiftOp: '<<' | '>>' ;
|
||||
relationalOp: '<' | '>' | '<=' | '>=' ;
|
||||
equalityOp: '==' | '!=' ;
|
||||
bitwiseAndOp: '&' ;
|
||||
bitwiseXorOp: '^' ;
|
||||
bitwiseOrOp: '|' ;
|
||||
rangeOp: 'to' | 'downto' ;
|
||||
postfixOperator: '++' | '--' ;
|
||||
prefixOperator: '+' | '-' | '~' ;
|
||||
```
|
||||
|
||||
### B. Statement Rule Grouping
|
||||
|
||||
**Current Problem**: 25 alternatives with no grouping, hard to extend (lines 98-124)
|
||||
|
||||
**Optimization**: Group by category:
|
||||
|
||||
```antlr
|
||||
statement
|
||||
: directive
|
||||
| declaration
|
||||
| controlFlow
|
||||
| assignment
|
||||
| jumpStatement
|
||||
| loopStatement
|
||||
| subroutine
|
||||
| inlineasm
|
||||
| labeldef
|
||||
| alias
|
||||
;
|
||||
|
||||
declaration
|
||||
: variabledeclaration
|
||||
| structdeclaration
|
||||
| subroutinedeclaration
|
||||
;
|
||||
|
||||
controlFlow
|
||||
: if_stmt
|
||||
| branch_stmt
|
||||
| whenstmt
|
||||
| ongoto
|
||||
;
|
||||
|
||||
jumpStatement
|
||||
: unconditionaljump
|
||||
| returnstmt
|
||||
| breakstmt
|
||||
| continuestmt
|
||||
;
|
||||
|
||||
loopStatement
|
||||
: forloop
|
||||
| whileloop
|
||||
| untilloop
|
||||
| repeatloop
|
||||
| unrollloop
|
||||
;
|
||||
```
|
||||
|
||||
### C. Directive Rule Simplification
|
||||
|
||||
**Current Problem**: Complex, ambiguous alternatives (line 156)
|
||||
|
||||
**Optimization**: Simplify structure:
|
||||
|
||||
```antlr
|
||||
directive
|
||||
: '%' name=UNICODEDNAME '!'? directiveArgs
|
||||
;
|
||||
|
||||
directiveArgs
|
||||
: '(' EOL? scoped_identifier (',' EOL? scoped_identifier)* ','? EOL? ')' // List
|
||||
| directiveArg (',' directiveArg)* // Regular args
|
||||
| // Empty
|
||||
;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Lexer Improvements
|
||||
|
||||
### A. NOT_IN Token Fix
|
||||
|
||||
**Current Problem**: Fragile lexer rule with whitespace dependence (line 72)
|
||||
```antlr
|
||||
NOT_IN: 'not' [ \t]+ 'in' [ \t] ;
|
||||
```
|
||||
|
||||
**Fix**: Move to parser rule:
|
||||
```antlr
|
||||
// Remove NOT_IN from lexer
|
||||
// In parser:
|
||||
expression
|
||||
: ...
|
||||
| left=expression 'not' 'in' right=expression #NotInExpression
|
||||
;
|
||||
```
|
||||
|
||||
### B. Identifier Rule Improvement
|
||||
|
||||
**Current Problem**: Must manually list keywords that can be identifiers (line 266)
|
||||
```antlr
|
||||
identifier: UNICODEDNAME | UNDERSCORENAME | ON | CALL | INLINE | STEP ;
|
||||
```
|
||||
|
||||
**Fix**: Use parser rule approach:
|
||||
```antlr
|
||||
// Instead of tokens, use a parser rule that matches any keyword as identifier
|
||||
identifier
|
||||
: UNICODEDNAME
|
||||
| UNDERSCORENAME
|
||||
| keywordAsIdentifier
|
||||
;
|
||||
|
||||
keywordAsIdentifier
|
||||
: 'on' | 'call' | 'inline' | 'step' | 'else' | 'then' | 'goto' | 'void' | 'struct'
|
||||
;
|
||||
```
|
||||
|
||||
### C. Float Number Rules Simplification
|
||||
|
||||
**Current Problem**: Complex, potentially ambiguous rules (lines 51-54)
|
||||
```antlr
|
||||
FLOAT_NUMBER : FNUMBER (('E'|'e') ('+' | '-')? DEC_INTEGER)? ;
|
||||
FNUMBER : FDOTNUMBER | FNUMDOTNUMBER ;
|
||||
FDOTNUMBER : '.' (DEC_DIGIT | '_')+ ;
|
||||
FNUMDOTNUMBER : DEC_DIGIT (DEC_DIGIT | '_')* FDOTNUMBER? ;
|
||||
```
|
||||
|
||||
**Fix**: Simplify:
|
||||
```antlr
|
||||
FLOAT_NUMBER
|
||||
: DEC_DIGIT (DEC_DIGIT | '_')* ('.' (DEC_DIGIT | '_')*)?
|
||||
(('E'|'e') ('+'|'-')? DEC_INTEGER)?
|
||||
| '.' (DEC_DIGIT | '_')+ (('E'|'e') ('+'|'-')? DEC_INTEGER)?
|
||||
;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Specific Problem Areas
|
||||
|
||||
### A. Pointer Dereference Cleanup
|
||||
|
||||
**Current Problem**: Described as "cursed mix" in comment, doesn't handle chaining well (lines 343-352)
|
||||
|
||||
**Current Grammar**:
|
||||
```antlr
|
||||
pointerdereference: (prefix=scoped_identifier '.')? derefchain ('.' field=identifier)? ;
|
||||
derefchain: singlederef ('.' singlederef)* ;
|
||||
singlederef: identifier arrayindex? POINTER ;
|
||||
```
|
||||
|
||||
**Improved Structure**:
|
||||
```antlr
|
||||
pointerdereference
|
||||
: pointerBase ('.' pointerElement)*
|
||||
;
|
||||
|
||||
pointerBase
|
||||
: scoped_identifier '.'? // Optional base with dot
|
||||
| // Or start with dereference
|
||||
;
|
||||
|
||||
pointerElement
|
||||
: identifier arrayindex? POINTER // array^^ or just pointer^^
|
||||
| identifier // field access
|
||||
;
|
||||
```
|
||||
|
||||
### B. Assignment Rule Simplification
|
||||
|
||||
**Current Problem**: Multiple ambiguous alternatives (line 182)
|
||||
```antlr
|
||||
assignment
|
||||
: (assign_target '=' expression)
|
||||
| (assign_target '=' assignment) // Chained assignment - problematic
|
||||
| (multi_assign_target '=' expression)
|
||||
;
|
||||
```
|
||||
|
||||
**Fix**: Remove chained assignment ambiguity:
|
||||
```antlr
|
||||
assignment
|
||||
: assign_target '=' expression
|
||||
| multi_assign_target '=' expression
|
||||
// Remove: assign_target '=' assignment // Too ambiguous
|
||||
;
|
||||
```
|
||||
|
||||
### C. Module Rule EOL Handling
|
||||
|
||||
**Current Problem**: Complex EOL* patterns everywhere (line 79)
|
||||
```antlr
|
||||
module: EOL* (module_element (EOL+ module_element)*)? EOL* EOF;
|
||||
```
|
||||
|
||||
**Note**: This is actually necessary due to comment/EOL interleaving (issue #47), but could be cleaner with a channel-based approach.
|
||||
|
||||
---
|
||||
|
||||
## 5. Missing Syntactic Validations
|
||||
|
||||
Add parser-level validations to catch errors early:
|
||||
|
||||
```antlr
|
||||
// Array type validation
|
||||
vardecl
|
||||
: datatype (arrayindex | EMPTYARRAYSIG)? TAG* identifierlist
|
||||
| datatype EMPTYARRAYSIG identifierlist
|
||||
{ if (!$datatype.text.equals("ubyte") && !$datatype.text.equals("byte"))
|
||||
notifyErrorListeners("Empty array syntax [] only valid for byte/ubyte types"); }
|
||||
;
|
||||
|
||||
// Function call validation
|
||||
functioncall_stmt
|
||||
: VOID? scoped_identifier '(' EOL? expression_list? EOL? ')'
|
||||
| VOID? scoped_identifier '(' EOL? expression_list? EOL? ')' '=' expression // ERROR: assignment in function call
|
||||
{ notifyErrorListeners("Cannot assign to function call. Use separate statement."); }
|
||||
;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation Priority
|
||||
|
||||
| Priority | Issue | Impact | Effort | Files to Modify |
|
||||
|----------|-------|--------|--------|------------------|
|
||||
| **High** | Expression rule refactoring | Maintainability, error quality | Medium | `Prog8ANTLR.g4` |
|
||||
| **High** | Add error recovery alternatives | User experience | Low | `Prog8ANTLR.g4` |
|
||||
| **Medium** | NOT_IN to parser rule | Robustness | Low | `Prog8ANTLR.g4` |
|
||||
| **Medium** | Statement rule grouping | Maintainability | Low | `Prog8ANTLR.g4` |
|
||||
| **Medium** | Identifier keyword handling | Completeness | Low | `Prog8ANTLR.g4` |
|
||||
| **Low** | Pointer dereference cleanup | Technical debt | Medium | `Prog8ANTLR.g4` |
|
||||
| **Low** | Float number simplification | Maintainability | Low | `Prog8ANTLR.g4` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Example: Improved Error Messages
|
||||
|
||||
### Before (Current)
|
||||
```
|
||||
line 5:8 mismatched input '=' expecting {<EOF>, EOL, ';', ...}
|
||||
line 10:3 mismatched input 'if' expecting {'{', 'then', ...}
|
||||
line 15:12 mismatched input 'on' expecting {UNICODEDNAME, ...}
|
||||
```
|
||||
|
||||
### After (With Custom Messages)
|
||||
```
|
||||
line 5:8 syntax error: Cannot chain assignments. Use multiple statements instead.
|
||||
line 10:3 syntax error: 'if' statement missing 'then' or '{' before condition body
|
||||
line 15:12 syntax error: Variable 'on' is a keyword. Use a different name or escape it.
|
||||
line 20:4 syntax error: Empty array syntax [] only valid for byte/ubyte types.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Testing Strategy
|
||||
|
||||
After implementing these changes:
|
||||
|
||||
1. **Regression Testing**: Ensure all existing test cases still pass
|
||||
2. **Error Message Testing**: Create test cases for each new error message
|
||||
3. **Edge Case Testing**: Test complex expressions, pointer dereferencing, etc.
|
||||
4. **Performance Testing**: Verify grammar changes don't significantly impact parsing speed
|
||||
|
||||
---
|
||||
|
||||
## 9. Migration Plan
|
||||
|
||||
1. **Phase 1**: Implement high-priority error recovery improvements
|
||||
2. **Phase 2**: Refactor expression and statement rules
|
||||
3. **Phase 3**: Fix lexer issues (NOT_IN, identifier handling)
|
||||
4. **Phase 4**: Clean up specific problem areas (pointers, assignments)
|
||||
5. **Phase 5**: Add comprehensive error validations
|
||||
|
||||
Each phase should include:
|
||||
- Grammar changes
|
||||
- Test updates
|
||||
- Documentation updates
|
||||
- Performance verification
|
||||
|
||||
---
|
||||
|
||||
## 10. Related Files to Update
|
||||
|
||||
- `/home/irmen/Projects/prog8/parser/src/main/antlr/Prog8ANTLR.g4` - Main grammar file
|
||||
- `/home/irmen/Projects/prog8/compilerAst/src/prog8/parser/Prog8Parser.kt` - Error handling
|
||||
- Test files in `/home/irmen/Projects/prog8/compiler/test/` - Update for new error messages
|
||||
- Documentation - Update language specification if grammar semantics change
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
These improvements will significantly enhance the Prog8 parser by:
|
||||
- Providing clearer, more helpful error messages
|
||||
- Making the grammar more maintainable and extensible
|
||||
- Fixing known parsing issues and ambiguities
|
||||
- Improving overall user experience for developers using the language
|
||||
|
||||
The changes are designed to be backward-compatible where possible, with careful attention to maintaining existing functionality while improving the parser's robustness and usability.
|
||||
|
||||
---
|
||||
|
||||
# Appendix: BailErrorStrategy Migration Guide
|
||||
|
||||
## What is BailErrorStrategy?
|
||||
|
||||
`BailErrorStrategy` is an ANTLR4 error handling strategy that **immediately stops parsing** when it encounters the first syntax error, rather than attempting to recover and continue parsing.
|
||||
|
||||
### Key Characteristics:
|
||||
|
||||
1. **Fail-Fast**: Stops on the first error - no recovery attempts
|
||||
2. **No Error Recovery**: Doesn't try to skip tokens or resynchronize
|
||||
3. **Throws Exceptions**: Immediately throws `InputMismatchException` or other parse errors
|
||||
4. **Simple but Limited**: Easy to implement but poor user experience
|
||||
|
||||
### Current Implementation in Prog8:
|
||||
|
||||
```kotlin
|
||||
// File: /home/irmen/Projects/prog8/compilerAst/src/prog8/parser/Prog8Parser.kt
|
||||
private object Prog8ErrorStrategy: BailErrorStrategy() {
|
||||
override fun recover(recognizer: Parser?, e: RecognitionException?) {
|
||||
fillIn(e, recognizer!!.context)
|
||||
reportError(recognizer, e)
|
||||
}
|
||||
|
||||
override fun recoverInline(recognizer: Parser?): Token {
|
||||
val e = InputMismatchException(recognizer)
|
||||
fillIn(e, recognizer!!.context)
|
||||
reportError(recognizer, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Problems with BailErrorStrategy:
|
||||
|
||||
1. **Single Error Only**: Users only see the first syntax error, not all issues
|
||||
2. **Poor IDE Integration**: IDEs can't highlight multiple errors simultaneously
|
||||
3. **Frustrating Workflow**: Fix one error, recompile, find next error
|
||||
4. **Limited Context**: No information about what might be expected
|
||||
|
||||
---
|
||||
|
||||
## Migration Plan: Replace BailErrorStrategy
|
||||
|
||||
### 1. **Replace Error Strategy**
|
||||
|
||||
**Current:**
|
||||
```kotlin
|
||||
parser.errorHandler = Prog8ErrorStrategy // BailErrorStrategy
|
||||
```
|
||||
|
||||
**New:**
|
||||
```kotlin
|
||||
parser.errorHandler = DefaultErrorStrategy() // Built-in recovery strategy
|
||||
```
|
||||
|
||||
### 2. **Implement Custom Error Listener**
|
||||
|
||||
**Create new error listener:**
|
||||
```kotlin
|
||||
private class Prog8ErrorListener(val src: SourceCode): BaseErrorListener() {
|
||||
private val errors = mutableListOf<ParseError>()
|
||||
|
||||
override fun syntaxError(recognizer: Recognizer<*, *>?,
|
||||
offendingSymbol: Any?,
|
||||
line: Int,
|
||||
charPositionInLine: Int,
|
||||
msg: String,
|
||||
e: RecognitionException?) {
|
||||
// Collect errors instead of throwing immediately
|
||||
val error = ParseError(msg, Position(src.origin, line, charPositionInLine+1, charPositionInLine+1), e ?: RuntimeException("parse error"))
|
||||
errors.add(error)
|
||||
}
|
||||
|
||||
fun getErrors(): List<ParseError> = errors.toList()
|
||||
fun hasErrors(): Boolean = errors.isNotEmpty()
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Update Parser Setup**
|
||||
|
||||
**Current:**
|
||||
```kotlin
|
||||
fun parseModule(src: SourceCode): Module {
|
||||
val antlrErrorListener = AntlrErrorListener(src)
|
||||
val lexer = Prog8ANTLRLexer(CharStreams.fromString(src.text, src.origin))
|
||||
lexer.removeErrorListeners()
|
||||
lexer.addErrorListener(antlrErrorListener)
|
||||
val tokens = CommonTokenStream(lexer)
|
||||
val parser = Prog8ANTLRParser(tokens)
|
||||
parser.errorHandler = Prog8ErrorStrategy // BailErrorStrategy
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(antlrErrorListener)
|
||||
|
||||
val parseTree = parser.module()
|
||||
// ... visitor pattern
|
||||
}
|
||||
```
|
||||
|
||||
**New:**
|
||||
```kotlin
|
||||
fun parseModule(src: SourceCode): Module {
|
||||
val errorListener = Prog8ErrorListener(src)
|
||||
val lexer = Prog8ANTLRLexer(CharStreams.fromString(src.text, src.origin))
|
||||
lexer.removeErrorListeners()
|
||||
lexer.addErrorListener(errorListener)
|
||||
val tokens = CommonTokenStream(lexer)
|
||||
val parser = Prog8ANTLRParser(tokens)
|
||||
parser.errorHandler = DefaultErrorStrategy() // Recovery strategy
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(errorListener)
|
||||
|
||||
val parseTree = parser.module()
|
||||
|
||||
// Check for errors after parsing
|
||||
if (errorListener.hasErrors()) {
|
||||
throw MultipleParseErrors(errorListener.getErrors())
|
||||
}
|
||||
|
||||
// ... visitor pattern
|
||||
}
|
||||
```
|
||||
|
||||
### 4. **Create Multiple Errors Exception**
|
||||
|
||||
```kotlin
|
||||
class MultipleParseErrors(val errors: List<ParseError>) : Exception() {
|
||||
override val message: String
|
||||
get() = "Found ${errors.size} parse errors:\n" +
|
||||
errors.joinToString("\n") { "${it.position}: ${it.message}" }
|
||||
}
|
||||
```
|
||||
|
||||
### 5. **Update Compiler Error Handling**
|
||||
|
||||
**Current in Compiler.kt:**
|
||||
```kotlin
|
||||
} catch (px: ParseError) {
|
||||
args.errors.printSingleError("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
||||
}
|
||||
```
|
||||
|
||||
**New:**
|
||||
```kotlin
|
||||
} catch (mpe: MultipleParseErrors) {
|
||||
// Report all parse errors
|
||||
mpe.errors.forEach { error ->
|
||||
args.errors.printSingleError("${error.position.toClickableStr()} parse error: ${error.message}".trim())
|
||||
}
|
||||
} catch (px: ParseError) {
|
||||
// Fallback for single errors
|
||||
args.errors.printSingleError("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Grammar Changes for Better Error Recovery
|
||||
|
||||
### 1. **Add Error Recovery Alternatives**
|
||||
|
||||
```antlr
|
||||
// Statement with error recovery
|
||||
statement
|
||||
: directive
|
||||
| ongoto
|
||||
| variabledeclaration
|
||||
| structdeclaration
|
||||
| assignment
|
||||
| augassignment
|
||||
| unconditionaljump
|
||||
| postincrdecr
|
||||
| functioncall_stmt
|
||||
| if_stmt
|
||||
| branch_stmt
|
||||
| subroutinedeclaration
|
||||
| inlineasm
|
||||
| returnstmt
|
||||
| forloop
|
||||
| whileloop
|
||||
| untilloop
|
||||
| repeatloop
|
||||
| unrollloop
|
||||
| whenstmt
|
||||
| breakstmt
|
||||
| continuestmt
|
||||
| labeldef
|
||||
| defer
|
||||
| alias
|
||||
// Error recovery alternatives
|
||||
| 'if' expression error=statement
|
||||
{ notifyErrorListeners("Expected 'then' or '{' after if condition"); }
|
||||
| 'for' identifier 'in' error=statement
|
||||
{ notifyErrorListeners("Expected expression after 'in'"); }
|
||||
| 'while' error=statement
|
||||
{ notifyErrorListeners("Expected condition after 'while'"); }
|
||||
| 'return' error=expression
|
||||
{ notifyErrorListeners("Invalid return expression"); }
|
||||
;
|
||||
```
|
||||
|
||||
### 2. **Add Synchronization Points**
|
||||
|
||||
```antlr
|
||||
// Block with synchronization
|
||||
block: identifier integerliteral? EOL? '{' EOL? (block_statement | EOL)* '}'
|
||||
| identifier integerliteral? EOL? '{' error=EOL? (block_statement | EOL)* '}'
|
||||
{ notifyErrorListeners("Error in block: " + $error.text); }
|
||||
;
|
||||
|
||||
// Module with synchronization
|
||||
module: EOL* (module_element (EOL+ module_element)*)? EOL* EOF
|
||||
| EOL* module_element error=EOL+ module_element* EOL* EOF
|
||||
{ notifyErrorListeners("Error between module elements"); }
|
||||
;
|
||||
```
|
||||
|
||||
### 3. **Add Error Tokens**
|
||||
|
||||
```antlr
|
||||
// Add to lexer
|
||||
ERROR_TOKEN: . -> skip ; // Skip unknown tokens
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits of Migration
|
||||
|
||||
### 1. **Multiple Error Reporting**
|
||||
- Users see all syntax errors at once
|
||||
- Better IDE integration with multiple error highlights
|
||||
- More efficient development workflow
|
||||
|
||||
### 2. **Better Error Context**
|
||||
- ANTLR's recovery provides context about expected tokens
|
||||
- Can suggest alternatives based on grammar
|
||||
- More precise error locations
|
||||
|
||||
### 3. **Improved User Experience**
|
||||
- Less frustrating compilation process
|
||||
- Better error messages with context
|
||||
- Ability to fix multiple issues in one iteration
|
||||
|
||||
---
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Basic Migration
|
||||
1. Replace `BailErrorStrategy` with `DefaultErrorStrategy`
|
||||
2. Update error listener to collect instead of throw
|
||||
3. Create `MultipleParseErrors` exception
|
||||
4. Update compiler error handling
|
||||
|
||||
### Phase 2: Grammar Improvements
|
||||
1. Add error recovery alternatives to key rules
|
||||
2. Add synchronization points for better recovery
|
||||
3. Improve error messages with context
|
||||
|
||||
### Phase 3: Advanced Features
|
||||
1. Add error suggestion logic
|
||||
2. Implement custom recovery strategies for specific patterns
|
||||
3. Add error severity levels (warning vs error)
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### 1. **Create Test Cases with Multiple Errors**
|
||||
```kotlin
|
||||
test("multiple parse errors") {
|
||||
val src = """
|
||||
sub main() {
|
||||
x = 1 + // Missing right operand
|
||||
if x > 0 // Missing then/block
|
||||
y = // Missing expression
|
||||
}
|
||||
"""
|
||||
// Should report all 3 errors, not just the first
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **Test Error Recovery**
|
||||
```kotlin
|
||||
test("error recovery continues parsing") {
|
||||
val src = """
|
||||
sub bad() { x = 1 + }
|
||||
sub good() { return 42 }
|
||||
"""
|
||||
// Should parse both subroutines and report error in first
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Test Synchronization**
|
||||
```kotlin
|
||||
test("block synchronization") {
|
||||
val src = """
|
||||
block1 {
|
||||
x = 1 + // Error in block1
|
||||
}
|
||||
block2 { // Should still parse block2
|
||||
y = 2
|
||||
}
|
||||
"""
|
||||
// Should recover and parse block2 correctly
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Potential Challenges
|
||||
|
||||
### 1. **Cascading Errors**
|
||||
- One syntax error might cause multiple subsequent errors
|
||||
- Need to filter or prioritize errors intelligently
|
||||
|
||||
### 2. **Performance Impact**
|
||||
- Error recovery has overhead
|
||||
- Need to benchmark parsing performance
|
||||
|
||||
### 3. **False Positives**
|
||||
- Recovery might parse invalid constructs
|
||||
- Need to validate AST after parsing
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Migrating from `BailErrorStrategy` to a recovery-based approach will:
|
||||
|
||||
1. **Improve User Experience**: Show all errors at once instead of one-by-one
|
||||
2. **Better IDE Integration**: Enable multiple error highlights
|
||||
3. **Provide Context**: Better error messages with expected tokens
|
||||
4. **Maintain Robustness**: Still catch all errors, just report them differently
|
||||
|
||||
The migration requires changes to:
|
||||
- Error handling strategy in parser setup
|
||||
- Error listener implementation
|
||||
- Compiler error reporting
|
||||
- Grammar for better recovery
|
||||
- Test cases for multiple errors
|
||||
|
||||
This change will significantly improve the development experience for Prog8 programmers while maintaining the compiler's accuracy and robustness.
|
||||
@@ -436,7 +436,7 @@ enum class Opcode {
|
||||
ALIGN
|
||||
}
|
||||
|
||||
val OpcodesThatBranchUnconditionally = arrayOf(
|
||||
val OpcodesThatBranchUnconditionally = setOf(
|
||||
Opcode.JUMP,
|
||||
Opcode.JUMPI,
|
||||
Opcode.RETURN,
|
||||
@@ -444,7 +444,7 @@ val OpcodesThatBranchUnconditionally = arrayOf(
|
||||
Opcode.RETURNI
|
||||
)
|
||||
|
||||
val OpcodesThatBranch = OpcodesThatBranchUnconditionally + arrayOf(
|
||||
val OpcodesThatBranch = OpcodesThatBranchUnconditionally + setOf(
|
||||
Opcode.CALLI,
|
||||
Opcode.CALL,
|
||||
Opcode.CALLFAR,
|
||||
@@ -472,7 +472,7 @@ val OpcodesThatBranch = OpcodesThatBranchUnconditionally + arrayOf(
|
||||
Opcode.BLES
|
||||
)
|
||||
|
||||
val OpcodesThatEndSSAblock = OpcodesThatBranchUnconditionally + arrayOf(
|
||||
val OpcodesThatEndSSAblock = OpcodesThatBranchUnconditionally + setOf(
|
||||
Opcode.BSTCC,
|
||||
Opcode.BSTCS,
|
||||
Opcode.BSTEQ,
|
||||
@@ -1143,13 +1143,13 @@ data class IRInstruction(
|
||||
if(opcode==Opcode.SQRT)
|
||||
return IRDataType.WORD
|
||||
}
|
||||
if(opcode in arrayOf(Opcode.JUMPI, Opcode.CALLI, Opcode.STOREZI, Opcode.LSIGW, Opcode.MSIGW))
|
||||
if(opcode in setOf(Opcode.JUMPI, Opcode.CALLI, Opcode.STOREZI, Opcode.LSIGW, Opcode.MSIGW))
|
||||
return IRDataType.WORD
|
||||
if(opcode==Opcode.EXT || opcode==Opcode.EXTS)
|
||||
return if (type == IRDataType.BYTE) IRDataType.WORD else null
|
||||
if(opcode==Opcode.CONCAT)
|
||||
return if (type == IRDataType.BYTE) IRDataType.WORD else null
|
||||
if(opcode in arrayOf(Opcode.ASRNM, Opcode.LSRNM, Opcode.LSLNM, Opcode.SQRT, Opcode.LSIGB, Opcode.MSIGB, Opcode.BSIGB))
|
||||
if(opcode in setOf(Opcode.ASRNM, Opcode.LSRNM, Opcode.LSLNM, Opcode.SQRT, Opcode.LSIGB, Opcode.MSIGB, Opcode.BSIGB))
|
||||
return IRDataType.BYTE
|
||||
return this.type
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user