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 ':' ;