chained assignments x=y=z=42

This commit is contained in:
Irmen de Jong 2023-12-08 00:57:39 +01:00
parent d91ca8b197
commit 6a639ce533
12 changed files with 106 additions and 14 deletions

View File

@ -25,7 +25,7 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// - repeat-forever loops replaced by label+jump.
// - 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
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
@ -287,4 +287,21 @@ _after:
return noModifications
}
override fun after(chainedAssignment: ChainedAssignment, parent: Node): Iterable<IAstModification> {
val assign = chainedAssignment.nested as? Assignment
if(assign!=null) {
// unpack starting from last in the chain
val assigns = mutableListOf<Statement>(assign)
var lastChained: ChainedAssignment = chainedAssignment
var pc: ChainedAssignment? = chainedAssignment
while(pc!=null) {
lastChained = pc
assigns.add(0, Assignment(pc.target.copy(), assign.value.copy(), assign.origin, pc.position))
pc = pc.parent as? ChainedAssignment
}
return listOf(IAstModification.ReplaceNode(lastChained,
AnonymousScope(assigns, chainedAssignment.position), lastChained.parent))
}
return noModifications
}
}

View File

@ -37,6 +37,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transformStatement(statement: Statement): PtNode {
return when (statement) {
is AnonymousScope -> throw FatalAstException("AnonymousScopes should have been flattened")
is ChainedAssignment -> throw FatalAstException("ChainedAssignment should have been flattened")
is Assignment -> transform(statement)
is Block -> transform(statement)
is Break -> throw FatalAstException("break should have been replaced by Goto")

View File

@ -343,6 +343,12 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
}
override fun visit(chainedAssignment: ChainedAssignment) {
chainedAssignment.target.accept(this)
output(" = ")
chainedAssignment.nested.accept(this)
}
override fun visit(postIncrDecr: PostIncrDecr) {
postIncrDecr.target.accept(this)
output(postIncrDecr.operator)

View File

@ -86,17 +86,11 @@ private fun StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
if(vardecl!=null) return vardecl
assignment()?.let {
return Assignment(it.assign_target().toAst(), it.expression().toAst(), AssignmentOrigin.USERCODE, it.toPosition())
}
val assignment = assignment()?.toAst()
if(assignment!=null) return assignment
augassignment()?.let {
// replace A += X with A = A + X
val target = it.assign_target().toAst()
val oper = it.operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
return Assignment(it.assign_target().toAst(), expression, AssignmentOrigin.USERCODE, it.toPosition())
}
val augassign = augassignment()?.toAst()
if(augassign!=null) return augassign
postincrdecr()?.let {
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition())
@ -338,6 +332,22 @@ private fun ClobberContext.toAst() : Set<CpuRegister> {
}
}
private fun AssignmentContext.toAst(): Statement {
val nestedAssign = assignment()
if(nestedAssign==null)
return Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
else
return ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
}
private fun AugassignmentContext.toAst(): Assignment {
// replace A += X with A = A + X
val target = assign_target().toAst()
val oper = operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, expression().toAst(), expression().toPosition())
return Assignment(assign_target().toAst(), expression, AssignmentOrigin.USERCODE, toPosition())
}
private fun DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
private fun ArrayindexContext.toAst() : ArrayIndex =

View File

@ -354,12 +354,39 @@ enum class AssignmentOrigin {
ASMGEN
}
class ChainedAssignment(var target: AssignTarget, var nested: Statement, override val position: Position): Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
nested.linkParents(this)
}
override fun copy() = throw FatalAstException("you're not supposed to copy a ChainedAssignment")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===target -> target = replacement as AssignTarget
node===nested -> nested = replacement as Statement
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString() = "ChainedAssignment(target: $target, nested: $nested, pos=$position)"
override fun referencesIdentifier(nameInSource: List<String>): Boolean = target.referencesIdentifier(nameInSource) || nested.referencesIdentifier(nameInSource)
}
class Assignment(var target: AssignTarget, var value: Expression, var origin: AssignmentOrigin, override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
this.target.linkParents(this)
target.linkParents(this)
value.linkParents(this)
}

View File

@ -96,6 +96,7 @@ abstract class AstWalker {
open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = noModifications
open fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> = noModifications
open fun before(chainedAssignment: ChainedAssignment, parent: Node): Iterable<IAstModification> = noModifications
open fun before(block: Block, parent: Node): Iterable<IAstModification> = noModifications
open fun before(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> = noModifications
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
@ -140,6 +141,7 @@ abstract class AstWalker {
open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = noModifications
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = noModifications
open fun after(chainedAssignment: ChainedAssignment, parent: Node): Iterable<IAstModification> = noModifications
open fun after(block: Block, parent: Node): Iterable<IAstModification> = noModifications
open fun after(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> = noModifications
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
@ -487,5 +489,12 @@ abstract class AstWalker {
whenChoice.statements.accept(this, whenChoice)
track(after(whenChoice, parent), whenChoice, parent)
}
fun visit(chainedAssignment: ChainedAssignment, parent: Node) {
track(before(chainedAssignment, parent), chainedAssignment, parent)
chainedAssignment.target.accept(this, chainedAssignment)
chainedAssignment.nested.accept(this, chainedAssignment)
track(after(chainedAssignment, parent), chainedAssignment, parent)
}
}

View File

@ -191,4 +191,9 @@ interface IAstVisitor {
whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this)
}
fun visit(chainedAssignment: ChainedAssignment) {
chainedAssignment.target.accept(this)
chainedAssignment.nested.accept(this)
}
}

View File

@ -624,6 +624,9 @@ Only variables of type byte, word and float can be assigned a new value.
It's not possible to set a new value to string or array variables etc, because they get allocated
a fixed amount of memory which will not change. (You *can* change the value of elements in a string or array though).
It is possible to "chain" assignments: ``x = y = z = 42``, this is just a shorthand
for the three individual assignments with the same value 42.
.. attention::
**Data type conversion (in assignments):**
When assigning a value with a 'smaller' datatype to variable with a 'larger' datatype,

View File

@ -555,6 +555,7 @@ bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
assignment: ``=``
Sets the target on the LHS (left hand side) of the operator to the value of the expression on the RHS (right hand side).
Note that an assignment sometimes is not possible or supported.
It's possible to chain assignments like ``x = y = z = 42`` as a shorthand for the three assignments with the same value.
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
This is syntactic sugar; ``aa += xx`` is equivalent to ``aa = aa + xx``

View File

@ -78,7 +78,6 @@ What if we were to re-introduce Structs in prog8? Some thoughts:
Other language/syntax features to think about
---------------------------------------------
- chained assignments `x=y=z=99`
- declare multiple variables `ubyte x,y,z` (if init value present, all get that init value)
- chained comparisons `10<x<20` , `x==y==z` (desugars to `10<x and x<20`, `x==y and y==z`)
- postincrdecr as expression, preincrdecr expression (`y = x++`, `y = ++x`) .... is this even possible, expression with side effects like this?

View File

@ -2,7 +2,21 @@
%zeropage basicsafe
main {
const ubyte VAL = 11
sub start() {
uword w
ubyte x
ubyte y
ubyte z
w = x = y = z = 99+VAL
txt.print_ub(x)
txt.spc()
txt.print_ub(y)
txt.spc()
txt.print_ub(z)
txt.spc()
txt.print_uw(w)
txt.nl()
}
}

View File

@ -146,7 +146,7 @@ datatype: 'ubyte' | 'byte' | 'uword' | 'word' | 'float' | 'str' | 'bool' ;
arrayindex: '[' expression ']' ;
assignment : assign_target '=' expression ;
assignment : (assign_target '=' expression) | (assign_target '=' assignment);
augassignment :
assign_target operator=('+=' | '-=' | '/=' | '*=' | '&=' | '|=' | '^=' | '%=' | '<<=' | '>>=' ) expression