diff --git a/benchmark-program/benchmark-program.iml b/benchmark-program/benchmark-program.iml deleted file mode 100644 index a22b261b6..000000000 --- a/benchmark-program/benchmark-program.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index ee571477a..212cadc39 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -285,7 +285,7 @@ internal class ConstantIdentifierReplacer( override fun after(identifier: IdentifierReference, parent: Node): Iterable { // replace identifiers that refer to const value, with the value itself // if it's a simple type and if it's not a left hand side variable - if(identifier.parent is AssignTarget) + if(identifier.parent is AssignTarget || identifier.parent is Alias) return noModifications var forloop = identifier.parent as? ForLoop if(forloop==null) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 7de37be85..d56e9c7c1 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -290,6 +290,7 @@ internal class AstChecker(private val program: Program, for (statement in block.statements) { val ok = when (statement) { + is Alias, is Block, is Directive, is Label, diff --git a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt index 226c04d73..15edb8051 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt @@ -193,6 +193,9 @@ internal fun IdentifierReference.checkFunctionOrLabelExists(program: Program, st else errors.err("cannot call that: ${this.nameInSource.joinToString(".")}", this.position) } + is Alias -> { + return targetStatement + } null -> { errors.undefined(this.nameInSource, this.position) } diff --git a/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt index 765652734..80f59f6cd 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt @@ -36,6 +36,11 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, errors.err("invalid number of arguments: expected ${params.size} got $numArgs", pos) } + override fun visit(alias: Alias) { + if(alias.target.targetStatement(program)==null) + errors.err("undefined symbol: ${alias.target.nameInSource.joinToString(".") }", alias.target.position) + } + override fun visit(block: Block) { val existing = blocks[block.name] if(existing!=null) { @@ -210,6 +215,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, if(target.type!=VarDeclType.VAR || target.datatype!=DataType.UWORD) errors.err("wrong address variable datatype, expected uword", call.target.position) } + is Alias -> {} null -> {} else -> errors.err("cannot call this as a subroutine or function", call.target.position) } diff --git a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt index 21ad5770f..96b7bcc75 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt @@ -249,6 +249,14 @@ class AstPreprocessor(val program: Program, return noModifications } + override fun after(alias: Alias, parent: Node): Iterable { + val tgt = alias.target.targetStatement(program) + if(tgt is Block) { + errors.err("cannot alias blocks", alias.target.position) + } + return noModifications + } + private fun checkStringParam(call: IFunctionCall, stmt: Statement) { val targetStatement = call.target.checkFunctionOrLabelExists(program, stmt, errors) if(targetStatement!=null) { diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt index 07b4f2ed3..5dba8a8f3 100644 --- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt +++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt @@ -23,6 +23,11 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep // - 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 + + override fun after(alias: Alias, parent: Node): Iterable { + return listOf(IAstModification.Remove(alias, parent as IStatementContainer)) + } override fun before(breakStmt: Break, parent: Node): Iterable { fun jumpAfter(stmt: Statement): Iterable { diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt index a1c34e12e..07ce6f6de 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt @@ -41,6 +41,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr 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 Break -> throw FatalAstException("break should have been replaced by Goto") is Continue -> throw FatalAstException("continue should have been replaced by Goto") is BuiltinFunctionCallStatement -> transform(statement) diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index ec8089d4f..25d9ce2ec 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -5,9 +5,7 @@ import prog8.ast.IStatementContainer import prog8.ast.Node import prog8.ast.Program import prog8.ast.expressions.* -import prog8.ast.statements.Assignment -import prog8.ast.statements.VarDecl -import prog8.ast.statements.WhenChoice +import prog8.ast.statements.* import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.code.ast.PtContainmentCheck @@ -108,4 +106,24 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro } return noModifications } + + override fun after(identifier: IdentifierReference, parent: Node): Iterable { + val target = identifier.targetStatement(program) + if(target is Alias) { + return listOf(IAstModification.ReplaceNode(identifier, target.target.copy(position = identifier.position), 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 + } } diff --git a/compiler/test/ast/TestVariousCompilerAst.kt b/compiler/test/ast/TestVariousCompilerAst.kt index ab1fc5924..4573eed3b 100644 --- a/compiler/test/ast/TestVariousCompilerAst.kt +++ b/compiler/test/ast/TestVariousCompilerAst.kt @@ -727,5 +727,69 @@ main { val handler = sub.children[7] as PtSub handler.name shouldBe "p8s_prog8_invoke_defers" } + + test("aliases ok") { + val src=""" +main { + alias print = txt.print + alias width = txt.DEFAULT_WIDTH + + sub start() { + alias print2 = txt.print + alias width2 = txt.DEFAULT_WIDTH + print("one") + print2("two") + txt.print_ub(width) + txt.print_ub(width2) + } +} + +txt { + const ubyte DEFAULT_WIDTH = 80 + sub print_ub(ubyte value) { + ; nothing + } + sub print(str msg) { + ; nothing + } +} + +""" + compileText(C64Target(), optimize=false, src, writeAssembly=false) shouldNotBe null + } + + test("wrong alias gives correct error") { + val src=""" +main { + alias print = txt.print2222 + alias width = txt.DEFAULT_WIDTH + + sub start() { + alias print2 = txt.print + alias width2 = txt.DEFAULT_WIDTH_XXX + print("one") + print2("two") + txt.print_ub(width) + txt.print_ub(width2) + } +} + +txt { + const ubyte DEFAULT_WIDTH = 80 + sub print_ub(ubyte value) { + ; nothing + } + sub print(str msg) { + ; nothing + } +} + +""" + val errors = ErrorReporterForTests() + compileText(C64Target(), optimize=false, src, writeAssembly=false, errors=errors) shouldBe null + errors.errors.size shouldBe 2 + errors.errors[0] shouldContain "undefined symbol: txt.print2222" + errors.errors[1] shouldContain "undefined symbol: txt.DEFAULT_WIDTH_XXX" + } }) diff --git a/compilerAst/src/prog8/ast/AstToplevel.kt b/compilerAst/src/prog8/ast/AstToplevel.kt index 94d849786..6a2fb9cfd 100644 --- a/compilerAst/src/prog8/ast/AstToplevel.kt +++ b/compilerAst/src/prog8/ast/AstToplevel.kt @@ -126,6 +126,7 @@ interface IStatementContainer { return found } } + is Alias -> if(stmt.alias==name) return stmt else -> { // NOTE: when other nodes containing AnonymousScope are introduced, // these should be added here as well to look into! @@ -155,6 +156,19 @@ interface INameScope: IStatementContainer, INamedStatement { private fun lookupQualified(scopedName: List): Statement? { // a scoped name refers to a name in another namespace, and stars from the root. + +// experimental code to be able to alias blocks too: +// val stmt = this.lookup(listOf(scopedName[0])) ?: return null +// if(stmt is Alias) { +// val block = this.lookup(stmt.target.nameInSource) ?: return null +// var statement = block as Statement? +// for(name in scopedName.drop(1)) { +// statement = (statement as? IStatementContainer)?.searchSymbol(name) +// if(statement==null) +// return null +// } +// return statement +// } for(module in (this as Node).definingModule.program.modules) { val block = module.searchSymbol(scopedName[0]) if(block!=null) { diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index 543d3aa72..0c0df4870 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -42,6 +42,7 @@ internal fun BlockContext.toAst(isInLibrary: Boolean) : Block { it.inlineasm()!=null -> it.inlineasm().toAst() it.inlineir()!=null -> it.inlineir().toAst() it.labeldef()!=null -> it.labeldef().toAst() + it.alias()!=null -> it.alias().toAst() else -> throw FatalAstException("weird block node $it") } } @@ -159,6 +160,9 @@ private fun StatementContext.toAst() : Statement { val deferstmt = defer()?.toAst() if(deferstmt!=null) return deferstmt + val aliasstmt = alias()?.toAst() + if(aliasstmt!=null) return aliasstmt + throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text") } @@ -291,7 +295,10 @@ private fun UnconditionaljumpContext.toAst(): Jump { } private fun LabeldefContext.toAst(): Statement = - Label(children[0].text, toPosition()) + Label(children[0].text, toPosition()) + +private fun AliasContext.toAst(): Statement = + Alias(identifier().text, scoped_identifier().toAst(), toPosition()) private fun SubroutineContext.toAst() : Subroutine { // non-asm subroutine diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 30da615ed..67f3a5b42 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -138,6 +138,22 @@ data class DirectiveArg(val str: String?, val name: String?, val int: UInt?, ove override fun referencesIdentifier(nameInSource: List): Boolean = false } +data class Alias(val alias: String, val target: IdentifierReference, override val position: Position) : Statement() { + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + target.parent = this + } + + override fun copy(): Statement = Alias(alias, target.copy(), position) + override fun accept(visitor: IAstVisitor) = visitor.visit(this) + override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) + override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") + override fun referencesIdentifier(nameInSource: List): Boolean = (nameInSource.size==1 && nameInSource[0]==alias) || target.referencesIdentifier(nameInSource) +} + + data class Label(override val name: String, override val position: Position) : Statement(), INamedStatement { override lateinit var parent: Node diff --git a/compilerAst/src/prog8/ast/walk/AstWalker.kt b/compilerAst/src/prog8/ast/walk/AstWalker.kt index 5bf713224..829a131a0 100644 --- a/compilerAst/src/prog8/ast/walk/AstWalker.kt +++ b/compilerAst/src/prog8/ast/walk/AstWalker.kt @@ -122,6 +122,7 @@ abstract class AstWalker { open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable = noModifications open fun before(jump: Jump, parent: Node): Iterable = noModifications open fun before(label: Label, parent: Node): Iterable = noModifications + open fun before(alias: Alias, parent: Node): Iterable = noModifications open fun before(memread: DirectMemoryRead, parent: Node): Iterable = noModifications open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable = noModifications open fun before(module: Module, parent: Node): Iterable = noModifications @@ -168,6 +169,7 @@ abstract class AstWalker { open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable = noModifications open fun after(jump: Jump, parent: Node): Iterable = noModifications open fun after(label: Label, parent: Node): Iterable = noModifications + open fun after(alias: Alias, parent: Node): Iterable = noModifications open fun after(memread: DirectMemoryRead, parent: Node): Iterable = noModifications open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable = noModifications open fun after(module: Module, parent: Node): Iterable = noModifications @@ -344,6 +346,12 @@ abstract class AstWalker { track(after(label, parent), label, parent) } + fun visit(alias: Alias, parent: Node) { + track(before(alias, parent), alias, parent) + alias.target.accept(this, alias) + track(after(alias, parent), alias, parent) + } + fun visit(numLiteral: NumericLiteral, parent: Node) { track(before(numLiteral, parent), numLiteral, parent) track(after(numLiteral, parent), numLiteral, parent) diff --git a/compilerAst/src/prog8/ast/walk/IAstVisitor.kt b/compilerAst/src/prog8/ast/walk/IAstVisitor.kt index 86cbfe1ea..f1e3f3f99 100644 --- a/compilerAst/src/prog8/ast/walk/IAstVisitor.kt +++ b/compilerAst/src/prog8/ast/walk/IAstVisitor.kt @@ -26,6 +26,10 @@ interface IAstVisitor { fun visit(directive: Directive) { } + fun visit(alias: Alias) { + alias.target.accept(this) + } + fun visit(containment: ContainmentCheck) { containment.element.accept(this) containment.iterable.accept(this) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index c1a4c1d5e..b7d6fe278 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -145,6 +145,14 @@ to access a variable from a nested subroutine:: } } +**Aliases** make it easier to refer to symbols from other places. They save +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. +You can create and use an alias with the ``alias`` statement like so:: + + alias prn = txt.print_ub + ... + prn(score) .. important:: diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index a7dd579dd..48b129ced 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -296,6 +296,15 @@ scoped names always need to be fully scoped (because they always start in the gl main.start ; the entrypoint subroutine main.start.variable ; a variable in the entrypoint subroutine +**Aliases** + +The ``alias`` statement makes it easier to refer to symbols from other places, and they can save +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 = `` and then you can use ```` as if it were ````. +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. + Code blocks ----------- diff --git a/examples/test.p8 b/examples/test.p8 index 3f2d99924..232a3a543 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,24 +1,18 @@ +%import textio %option no_sysinit -%zeropage dontuse - +%zeropage basicsafe main { sub start() { - derp() - derp2() - } + alias prn = txt.print_ub + alias spc = txt.spc + alias nl = txt.nl - asmsub derp() { - %asm {{ - nop - nop - }} - } - - inline asmsub derp2() { - %asm {{ - nop - nop - }} + prn(10) + spc() + prn(20) + spc() + prn(30) + nl() } } diff --git a/parser/antlr/Prog8ANTLR.g4 b/parser/antlr/Prog8ANTLR.g4 index c92443e30..68b546a5e 100644 --- a/parser/antlr/Prog8ANTLR.g4 +++ b/parser/antlr/Prog8ANTLR.g4 @@ -89,6 +89,7 @@ block_statement: | inlineasm | inlineir | labeldef + | alias ; @@ -116,6 +117,7 @@ statement : | continuestmt | labeldef | defer + | alias ; @@ -133,6 +135,8 @@ subroutinedeclaration : | romsubroutine ; +alias: 'alias' identifier '=' scoped_identifier ; + defer: 'defer' (statement | statement_block) ; labeldef : identifier ':' ;