diff --git a/compiler/src/prog8/ast/base/Extensions.kt b/compiler/src/prog8/ast/base/Extensions.kt index d90ab3864..9daaede67 100644 --- a/compiler/src/prog8/ast/base/Extensions.kt +++ b/compiler/src/prog8/ast/base/Extensions.kt @@ -57,6 +57,9 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter) { val transforms = AstVariousTransforms(this) transforms.visit(this) transforms.applyModifications() + val lit2decl = LiteralsToAutoVars(this) + lit2decl.visit(this) + lit2decl.applyModifications() } if (modules.map { it.name }.toSet().size != modules.size) { diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index ca7068887..aa802257c 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -832,10 +832,10 @@ internal class AstChecker(private val program: Program, } } - if(leftDt !in NumericDatatypes) - errors.err("left operand is not numeric", expr.left.position) - if(rightDt!in NumericDatatypes) - errors.err("right operand is not numeric", expr.right.position) + if(leftDt !in NumericDatatypes && leftDt != DataType.STR) + errors.err("left operand is not numeric or str", expr.left.position) + if(rightDt!in NumericDatatypes && rightDt != DataType.STR) + errors.err("right operand is not numeric or str", expr.right.position) if(leftDt!=rightDt) errors.err("left and right operands aren't the same type", expr.left.position) } diff --git a/compiler/src/prog8/ast/processing/AstVariousTransforms.kt b/compiler/src/prog8/ast/processing/AstVariousTransforms.kt index ceb606e5b..15db329f8 100644 --- a/compiler/src/prog8/ast/processing/AstVariousTransforms.kt +++ b/compiler/src/prog8/ast/processing/AstVariousTransforms.kt @@ -47,78 +47,59 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker() return noModifications } - override fun before(expr: BinaryExpression, parent: Node): Iterable { - when { - expr.left is StringLiteralValue -> - return listOf(IAstModification.ReplaceNode( - expr, - processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr), - parent - )) - expr.right is StringLiteralValue -> - return listOf(IAstModification.ReplaceNode( - expr, - processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr), - parent - )) + override fun after(expr: BinaryExpression, parent: Node): Iterable { + val leftStr = expr.left as? StringLiteralValue + val rightStr = expr.right as? StringLiteralValue + if(expr.operator == "+") { + val concatenatedString = concatString(expr) + if(concatenatedString!=null) + return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent)) } - - return noModifications - } - - override fun after(string: StringLiteralValue, parent: Node): Iterable { - if(string.parent !is VarDecl) { - // replace the literal string by a identifier reference to a new local vardecl - val vardecl = VarDecl.createAuto(string) - val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position) - return listOf( - IAstModification.ReplaceNode(string, identifier, parent), - IAstModification.InsertFirst(vardecl, string.definingScope() as Node) - ) - } - return noModifications - } - - override fun after(array: ArrayLiteralValue, parent: Node): Iterable { - val vardecl = array.parent as? VarDecl - if(vardecl!=null) { - // adjust the datatype of the array (to an educated guess) - val arrayDt = array.type - if(!arrayDt.istype(vardecl.datatype)) { - val cast = array.cast(vardecl.datatype) - if (cast != null && cast !== array) - return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) + else if(expr.operator == "*") { + if (leftStr!=null) { + val amount = expr.right.constValue(program) + if(amount!=null) { + val string = leftStr.value.repeat(amount.number.toInt()) + val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position) + return listOf(IAstModification.ReplaceNode(expr, strval, parent)) + } } - } else { - val arrayDt = array.guessDatatype(program) - if(arrayDt.isKnown) { - // this array literal is part of an expression, turn it into an identifier reference - val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT)) - if(litval2!=null) { - val vardecl2 = VarDecl.createAuto(litval2) - val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position) - return listOf( - IAstModification.ReplaceNode(array, identifier, parent), - IAstModification.InsertFirst(vardecl2, array.definingScope() as Node) - ) + else if (rightStr!=null) { + val amount = expr.right.constValue(program) + if(amount!=null) { + val string = rightStr.value.repeat(amount.number.toInt()) + val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position) + return listOf(IAstModification.ReplaceNode(expr, strval, parent)) } } } + return noModifications } - private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression { - val constvalue = operand.constValue(program) - if(constvalue!=null) { - if (expr.operator == "*") { - // repeat a string a number of times - return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position) + private fun concatString(expr: BinaryExpression): StringLiteralValue? { + val rightStrval = expr.right as? StringLiteralValue + val leftStrval = expr.left as? StringLiteralValue + return when { + expr.operator!="+" -> null + expr.left is BinaryExpression && rightStrval!=null -> { + val subStrVal = concatString(expr.left as BinaryExpression) + if(subStrVal==null) + null + else + StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position) } + expr.right is BinaryExpression && leftStrval!=null -> { + val subStrVal = concatString(expr.right as BinaryExpression) + if(subStrVal==null) + null + else + StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position) + } + leftStrval!=null && rightStrval!=null -> { + StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position) + } + else -> null } - if(expr.operator == "+" && operand is StringLiteralValue) { - // concatenate two strings - return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position) - } - return expr } } diff --git a/compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt b/compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt new file mode 100644 index 000000000..0378ecd2e --- /dev/null +++ b/compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt @@ -0,0 +1,53 @@ +package prog8.ast.processing + +import prog8.ast.Node +import prog8.ast.Program +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.statements.* + + +internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { + private val noModifications = emptyList() + + override fun after(string: StringLiteralValue, parent: Node): Iterable { + if(string.parent !is VarDecl) { + // replace the literal string by a identifier reference to a new local vardecl + val vardecl = VarDecl.createAuto(string) + val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position) + return listOf( + IAstModification.ReplaceNode(string, identifier, parent), + IAstModification.InsertFirst(vardecl, string.definingScope() as Node) + ) + } + return noModifications + } + + override fun after(array: ArrayLiteralValue, parent: Node): Iterable { + val vardecl = array.parent as? VarDecl + if(vardecl!=null) { + // adjust the datatype of the array (to an educated guess) + val arrayDt = array.type + if(!arrayDt.istype(vardecl.datatype)) { + val cast = array.cast(vardecl.datatype) + if (cast != null && cast !== array) + return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) + } + } else { + val arrayDt = array.guessDatatype(program) + if(arrayDt.isKnown) { + // this array literal is part of an expression, turn it into an identifier reference + val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT)) + if(litval2!=null) { + val vardecl2 = VarDecl.createAuto(litval2) + val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position) + return listOf( + IAstModification.ReplaceNode(array, identifier, parent), + IAstModification.InsertFirst(vardecl2, array.definingScope() as Node) + ) + } + } + } + return noModifications + } +} diff --git a/examples/test.p8 b/examples/test.p8 index 245a4baec..51e3de0a9 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -7,11 +7,10 @@ main { sub start() { - ; TODO fix multi- string concatenation: -; txt.print("\nCommands are:\n"+ -; "buy jump inf cash\n" + -; "sell teleport market hold\n" + -; "fuel galhyp local quit\n") + txt.print("\nCommands are:\n"+ + "buy jump inf cash\n" + + "sell teleport market hold\n" + + "fuel galhyp local quit\n") ; str name = "irmen de jong" ; uword strptr = &name @@ -28,14 +27,6 @@ main { ; txt.print_ub(strlen(strptr)) ; txt.chrout('\n') - ubyte q - for q in 0 to 255 { - txt.print_ub(q) - txt.chrout(' ') - txt.print_uw(q*5) ; TODO fix - txt.chrout('\n') - } - } diff --git a/examples/textelite.p8 b/examples/textelite.p8 index 61baea0a4..a986c5795 100644 --- a/examples/textelite.p8 +++ b/examples/textelite.p8 @@ -31,7 +31,10 @@ main { if num_chars { when input[0] { '?' -> { - txt.print("\nCommands are:\nbuy jump info cash\nsell teleport market hold\nfuel galhyp local quit\n") + txt.print("\nCommands are:\n"+ + "buy jump info cash\n"+ + "sell teleport market hold\n"+ + "fuel galhyp local quit\n") } 'q' -> break 'b' -> trader.do_buy()