added alias statement

This commit is contained in:
Irmen de Jong 2024-10-27 21:50:48 +01:00
parent 504c80cddf
commit a82f211f9a
19 changed files with 192 additions and 32 deletions

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/compiled" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -285,7 +285,7 @@ internal class ConstantIdentifierReplacer(
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
// 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)

View File

@ -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,

View File

@ -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)
}

View File

@ -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)
}

View File

@ -249,6 +249,14 @@ class AstPreprocessor(val program: Program,
return noModifications
}
override fun after(alias: Alias, parent: Node): Iterable<IAstModification> {
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) {

View File

@ -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<IAstModification> {
return listOf(IAstModification.Remove(alias, parent as IStatementContainer))
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {

View File

@ -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)

View File

@ -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<IAstModification> {
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
}
}

View File

@ -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"
}
})

View File

@ -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<String>): 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) {

View File

@ -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

View File

@ -138,6 +138,22 @@ data class DirectiveArg(val str: String?, val name: String?, val int: UInt?, ove
override fun referencesIdentifier(nameInSource: List<String>): 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<String>): 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

View File

@ -122,6 +122,7 @@ abstract class AstWalker {
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
open fun before(label: Label, parent: Node): Iterable<IAstModification> = noModifications
open fun before(alias: Alias, parent: Node): Iterable<IAstModification> = noModifications
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
open fun before(module: Module, parent: Node): Iterable<IAstModification> = noModifications
@ -168,6 +169,7 @@ abstract class AstWalker {
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
open fun after(label: Label, parent: Node): Iterable<IAstModification> = noModifications
open fun after(alias: Alias, parent: Node): Iterable<IAstModification> = noModifications
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
open fun after(module: Module, parent: Node): Iterable<IAstModification> = 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)

View File

@ -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)

View File

@ -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::

View File

@ -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 <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.
Code blocks
-----------

View File

@ -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()
}
}

View File

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