pipes also as expressions, cleanup codegen, fix various typecasting issues

This commit is contained in:
Irmen de Jong 2022-01-08 13:45:00 +01:00
parent 75d857027e
commit 7dd7e562bc
21 changed files with 368 additions and 118 deletions

View File

@ -47,7 +47,7 @@ class AsmGen(private val program: Program,
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this, functioncallAsmGen)
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
internal val loopEndLabels = ArrayDeque<String>()
@ -850,7 +850,7 @@ class AsmGen(private val program: Program,
is RepeatLoop -> translate(stmt)
is When -> translate(stmt)
is AnonymousScope -> translate(stmt)
is Pipe -> translate(stmt)
is Pipe -> expressionsAsmGen.translatePipeExpression(stmt.expressions, stmt,true)
is BuiltinFunctionPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore")
is UntilLoop -> throw AssemblyError("do..until should have been converted to jumps")
is WhileLoop -> throw AssemblyError("while should have been converted to jumps")
@ -1628,31 +1628,7 @@ $label nop""")
assemblyLines.add(assembly)
}
private fun translate(pipe: Pipe) {
// TODO more efficient code generation to avoid needless assignments to the temp var
var valueDt = pipe.valueDatatype(program)
var valueVar = getTempVarName(valueDt)
val subroutine = pipe.definingSubroutine
assignExpressionToVariable(pipe.expressions.first(), valueVar.joinToString("."), valueDt, subroutine)
pipe.expressions.drop(1).dropLast(1).forEach {
valueDt = functioncallAsmGen.translateFunctionCall(it as IdentifierReference, listOf(IdentifierReference(valueVar, it.position)), pipe)
// assign result value from the functioncall back to the temp var:
valueVar = getTempVarName(valueDt)
val valueVarTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, valueDt, subroutine, variableAsmName = valueVar.joinToString("."))
val returnRegister = returnRegisterOfFunction(it)!!
assignRegister(returnRegister, valueVarTarget)
}
// the last term in the pipe, don't care about return var:
functioncallAsmGen.translateFunctionCallStatement(
pipe.expressions.last() as IdentifierReference,
listOf(IdentifierReference(valueVar, pipe.expressions.last().position)),
pipe
)
}
private fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair? {
internal fun returnRegisterOfFunction(it: IdentifierReference): RegisterOrPair? {
return when (val targetRoutine = it.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> {
when (BuiltinFunctions.getValue(targetRoutine.name).known_returntype) {

View File

@ -1,5 +1,6 @@
package prog8.codegen.target.cpu6502.codegen
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
@ -7,11 +8,13 @@ import prog8.ast.statements.BuiltinFunctionPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.codegen.target.AssemblyError
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val functioncallAsmGen: FunctionCallAsmGen) {
@Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) {
@ -37,6 +40,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is NumericLiteralValue -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
is PipeExpression -> translatePipeExpression(expression.expressions, expression,false)
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpression -> throw AssemblyError("range expression should have been changed into array values")
@ -789,4 +793,38 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
asmgen.out(" dex")
}
internal fun translatePipeExpression(expressions: Iterable<Expression>, scope: Node, isStatement: Boolean) {
// TODO more efficient code generation to avoid needless assignments to the temp var
val subroutine = scope.definingSubroutine
var valueDt = expressions.first().inferType(program).getOrElse { throw FatalAstException("invalid dt") }
var valueVar = asmgen.getTempVarName(valueDt)
asmgen.assignExpressionToVariable(expressions.first(), valueVar.joinToString("."), valueDt, subroutine)
expressions.drop(1).dropLast(1).forEach {
valueDt = functioncallAsmGen.translateFunctionCall(it as IdentifierReference, listOf(IdentifierReference(valueVar, it.position)), scope)
// assign result value from the functioncall back to the temp var:
valueVar = asmgen.getTempVarName(valueDt)
val valueVarTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, valueDt, subroutine, variableAsmName = valueVar.joinToString("."))
val returnRegister = asmgen.returnRegisterOfFunction(it)!!
asmgen.assignRegister(returnRegister, valueVarTarget)
}
if(isStatement) {
// the last term in the pipe, don't care about return var:
functioncallAsmGen.translateFunctionCallStatement(
expressions.last() as IdentifierReference,
listOf(IdentifierReference(valueVar, expressions.last().position)),
scope
)
} else {
// the last term in the pipe, regular function call with returnvalue:
functioncallAsmGen.translateFunctionCall(
expressions.last() as IdentifierReference,
listOf(IdentifierReference(valueVar, expressions.last().position)),
scope
)
}
}
}

View File

@ -361,16 +361,36 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
return noModifications
}
override fun after(pipe: Pipe, parent: Node): Iterable<IAstModification> {
val firstValue = pipe.expressions.first()
override fun after(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> {
val expressions = pipeExpr.expressions
val firstValue = expressions.first()
if(firstValue.isSimple) {
val funcname = pipe.expressions[1] as IdentifierReference
val funcname = expressions[1] as IdentifierReference
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
val newExprs = mutableListOf<Expression>(first)
newExprs.addAll(pipe.expressions.drop(2))
newExprs.addAll(expressions.drop(2))
return listOf(IAstModification.ReplaceNode(pipeExpr, PipeExpression(newExprs, pipeExpr.position), parent))
}
val singleExpr = expressions.singleOrNull()
if(singleExpr!=null) {
val callExpr = singleExpr as FunctionCallExpression
val call = FunctionCallExpression(callExpr.target, callExpr.args, callExpr.position)
return listOf(IAstModification.ReplaceNode(pipeExpr, call, parent))
}
return noModifications
}
override fun after(pipe: Pipe, parent: Node): Iterable<IAstModification> {
val expressions = pipe.expressions
val firstValue = expressions.first()
if(firstValue.isSimple) {
val funcname = expressions[1] as IdentifierReference
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
val newExprs = mutableListOf<Expression>(first)
newExprs.addAll(expressions.drop(2))
return listOf(IAstModification.ReplaceNode(pipe, Pipe(newExprs, pipe.position), parent))
}
val singleExpr = pipe.expressions.singleOrNull()
val singleExpr = expressions.singleOrNull()
if(singleExpr!=null) {
val callExpr = singleExpr as FunctionCallExpression
val call = FunctionCallStatement(callExpr.target, callExpr.args, true, callExpr.position)

View File

@ -1224,17 +1224,44 @@ internal class AstChecker(private val program: Program,
super.visit(containment)
}
override fun visit(pipe: PipeExpression) {
processPipe(pipe.expressions, pipe)
val last = pipe.expressions.last() as IdentifierReference
val target = last.targetStatement(program)!!
when(target) {
is BuiltinFunctionPlaceholder -> {
if(BuiltinFunctions.getValue(target.name).known_returntype==null)
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
}
is Subroutine -> {
if(target.returntypes.isEmpty())
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
else if(target.returntypes.size!=1)
errors.err("invalid pipe expression; last term doesn't return a single value", last.position)
}
else -> errors.err("invalid pipe expression; last term doesn't return a value", last.position)
}
super.visit(pipe)
}
override fun visit(pipe: Pipe) {
processPipe(pipe.expressions, pipe)
super.visit(pipe)
}
private fun processPipe(expressions: List<Expression>, scope: Node) {
// first expression is just any expression producing a value
// all other expressions should be the name of a unary function that returns a single value
// the last expression should be the name of a unary function whose return value we don't care about.
if (pipe.expressions.size < 2) {
errors.err("pipe is missing one or more expressions", pipe.position)
if (expressions.size < 2) {
errors.err("pipe is missing one or more expressions", scope.position)
} else {
// invalid size and other issues will be handled by the ast checker later.
var valueDt = pipe.expressions[0].inferType(program).getOrElse { throw FatalAstException("invalid dt") }
var valueDt = expressions[0].inferType(program).getOrElse {
throw FatalAstException("invalid dt ${expressions[0]} @ ${scope.position}")
}
for(expr in pipe.expressions.drop(1)) { // just keep the first expression value as-is
for(expr in expressions.drop(1)) { // just keep the first expression value as-is
val functionName = expr as? IdentifierReference
val function = functionName?.targetStatement(program)
if(functionName!=null && function!=null) {
@ -1243,7 +1270,7 @@ internal class AstChecker(private val program: Program,
val func = BuiltinFunctions.getValue(function.name)
if(func.parameters.size!=1)
errors.err("can only use unary function", expr.position)
else if(func.known_returntype==null && expr !== pipe.expressions.last())
else if(func.known_returntype==null && expr !== expressions.last())
errors.err("function must return a single value", expr.position)
val paramDts = func.parameters.firstOrNull()?.possibleDatatypes
@ -1255,7 +1282,7 @@ internal class AstChecker(private val program: Program,
is Subroutine -> {
if(function.parameters.size!=1)
errors.err("can only use unary function", expr.position)
else if(function.returntypes.size!=1 && expr !== pipe.expressions.last())
else if(function.returntypes.size!=1 && expr !== expressions.last())
errors.err("function must return a single value", expr.position)
val paramDt = function.parameters.firstOrNull()?.type
@ -1277,11 +1304,8 @@ internal class AstChecker(private val program: Program,
}
}
}
return super.visit(pipe)
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
when (val targetStatement = target.targetStatement(program)) {
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement

View File

@ -33,11 +33,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(valueDt isNotAssignableTo decl.datatype)
return noModifications
return listOf(IAstModification.ReplaceNode(
declValue,
TypecastExpression(declValue, decl.datatype, true, declValue.position),
decl
))
val modifications = mutableListOf<IAstModification>()
addTypecastOrCastedValueModification(modifications, declValue, decl.datatype, decl)
return modifications
}
}
return noModifications
@ -71,13 +69,13 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.operator, expr.right)
if(toFix!=null) {
return when {
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
val modifications = mutableListOf<IAstModification>()
when {
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr)
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr)
else -> throw FatalAstException("confused binary expression side")
}
return modifications
}
}
return noModifications
@ -95,10 +93,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
return noModifications
return listOf(IAstModification.ReplaceNode(
assignment.value,
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
val modifications = mutableListOf<IAstModification>()
addTypecastOrCastedValueModification(modifications, assignment.value, targettype, assignment)
return modifications
} else {
fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> {
val cast = cvalue2.cast(targettype)
@ -154,17 +151,14 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if (argtype isAssignableTo requiredType) {
// don't need a cast for pass-by-reference types that are assigned to UWORD
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
modifications += IAstModification.ReplaceNode(
call.args[index],
TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node)
addTypecastOrCastedValueModification(modifications, pair.second, requiredType, call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode(
call.args[index],
identifier,
AddressOf(identifier, pair.second.position),
call as Node)
}
@ -172,7 +166,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
if(cast.isValid)
modifications += IAstModification.ReplaceNode(
call.args[index],
pair.second,
cast.valueOrZero(),
call as Node)
}
@ -189,10 +183,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if (pair.first.possibleDatatypes.all { argtype != it }) {
for (possibleType in pair.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
modifications += IAstModification.ReplaceNode(
call.args[index],
TypecastExpression(pair.second, possibleType, true, pair.second.position),
call as Node)
addTypecastOrCastedValueModification(modifications, pair.second, possibleType, call as Node)
break
}
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
@ -226,29 +217,36 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
else
errors.err("integer implicitly converted to float but floating point is not enabled via options", typecast.position)
}
return noModifications
}
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
val modifications = mutableListOf<IAstModification>()
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
val castedValue = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memread.addressExpression, castedValue, memread)
else
addTypecastOrCastedValueModification(modifications, memread.addressExpression, DataType.UWORD, memread)
}
return noModifications
return modifications
}
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
val modifications = mutableListOf<IAstModification>()
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
val castedValue = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
if(castedValue!=null)
modifications += IAstModification.ReplaceNode(memwrite.addressExpression, castedValue, memwrite)
else
addTypecastOrCastedValueModification(modifications, memwrite.addressExpression, DataType.UWORD, memwrite)
}
return noModifications
return modifications
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
@ -265,13 +263,32 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(cast.isValid)
returnStmt.value = cast.valueOrZero()
} else {
return listOf(IAstModification.ReplaceNode(
returnValue,
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
returnStmt))
val modifications = mutableListOf<IAstModification>()
addTypecastOrCastedValueModification(modifications, returnValue, subReturnType, returnStmt)
return modifications
}
}
}
return noModifications
}
private fun addTypecastOrCastedValueModification(
modifications: MutableList<IAstModification>,
expressionToCast: Expression,
requiredType: DataType,
parent: Node
) {
val sourceDt = expressionToCast.inferType(program).getOr(DataType.UNDEFINED)
if(sourceDt == requiredType)
return
if(expressionToCast is NumericLiteralValue && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats
val castedValue = expressionToCast.cast(requiredType)
if (castedValue.isValid) {
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
return
}
}
val cast = TypecastExpression(expressionToCast, requiredType, true, expressionToCast.position)
modifications += IAstModification.ReplaceNode(expressionToCast, cast, parent)
}
}

View File

@ -36,13 +36,13 @@ class TestNumericLiteralValue: FunSpec({
test("test rounding") {
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.BYTE, -2.345, dummyPos)
}.message shouldContain "refused silent rounding"
}.message shouldContain "refused rounding"
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.BYTE, -2.6, dummyPos)
}.message shouldContain "refused silent rounding"
}.message shouldContain "refused rounding"
shouldThrow<ExpressionError> {
NumericLiteralValue(DataType.UWORD, 2222.345, dummyPos)
}.message shouldContain "refused silent rounding"
}.message shouldContain "refused rounding"
NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
NumericLiteralValue(DataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
NumericLiteralValue(DataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0

View File

@ -590,9 +590,8 @@ class TestOptimization: FunSpec({
"""
val errors = ErrorReporterForTests()
compileText(C64Target, optimize=true, src, writeAssembly=false, errors = errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "type of value BYTE doesn't match target UBYTE"
errors.errors[1] shouldContain "value '-1' out of range for unsigned byte"
}
test("test augmented expression asmgen") {

View File

@ -6,6 +6,8 @@ import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.PipeExpression
import prog8.ast.statements.Assignment
import prog8.ast.statements.Pipe
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
@ -16,7 +18,7 @@ import prog8tests.helpers.compileText
class TestPipes: FunSpec({
test("correct pipes") {
test("correct pipe statements") {
val text = """
%import floats
%import textio
@ -54,7 +56,7 @@ class TestPipes: FunSpec({
pipew.expressions[1] shouldBe instanceOf<IdentifierReference>()
}
test("incorrect type in pipe") {
test("incorrect type in pipe statement") {
val text = """
%option enable_floats
@ -79,4 +81,66 @@ class TestPipes: FunSpec({
errors.errors[0] shouldContain "incompatible"
}
test("correct pipe expressions") {
val text = """
%import floats
%import textio
main {
sub start() {
float @shared fl = 1.234 |> addfloat
|> addfloat
uword @shared ww = 9999 |> addword
|> addword
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}
"""
val result = compileText(C64Target, true, text, writeAssembly = true).assertSuccess()
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 5
val assignf = stmts[1] as Assignment
val pipef = assignf.value as PipeExpression
pipef.expressions.size shouldBe 2
pipef.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
pipef.expressions[1] shouldBe instanceOf<IdentifierReference>()
val assignw = stmts[3] as Assignment
val pipew = assignw.value as PipeExpression
pipew.expressions.size shouldBe 2
pipew.expressions[0] shouldBe instanceOf<FunctionCallExpression>()
pipew.expressions[1] shouldBe instanceOf<IdentifierReference>()
}
test("incorrect type in pipe expression") {
val text = """
%option enable_floats
main {
sub start() {
uword result = 1.234 |> addfloat
|> addword |> addword
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors=errors).assertFailure()
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "incompatible"
}
})

View File

@ -9,7 +9,6 @@ import io.kotest.matchers.types.instanceOf
import prog8.ast.base.DataType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.printProgram
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure

View File

@ -3,10 +3,6 @@ package prog8tests
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.Pipe
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure

View File

@ -459,10 +459,18 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(pipe: Pipe) {
pipe.expressions.first().accept(this)
printPipe(pipe.expressions)
}
override fun visit(pipe: PipeExpression) {
printPipe(pipe.expressions)
}
private fun printPipe(expressions: Iterable<Expression>) {
expressions.first().accept(this)
outputln("")
scopelevel++
pipe.expressions.drop(1).forEach {
expressions.drop(1).forEach {
outputi("|> ")
it.accept(this)
outputln("")

View File

@ -9,6 +9,7 @@ import prog8.parser.Prog8ANTLRParser
import prog8.parser.SourceCode
import java.nio.file.Path
import kotlin.io.path.isRegularFile
import kotlin.math.exp
/***************** Antlr Extension methods to create AST ****************/
@ -481,6 +482,9 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
if(pipe!=null)
return PipeExpression(pipesource.toAst(), pipetarget.toAst(), toPosition())
throw FatalAstException(text)
}
@ -617,6 +621,7 @@ private fun Prog8ANTLRParser.VardeclContext.toAst(): VarDecl {
}
private fun Prog8ANTLRParser.PipestmtContext.toAst(): Pipe {
val expressions = expression().map { it.toAst() }
return Pipe(expressions.toMutableList(), toPosition())
val source = this.source.toAst()
val target = this.target.toAst()
return Pipe(source, target, toPosition())
}

View File

@ -1076,6 +1076,52 @@ class ContainmentCheck(var element: Expression,
}
}
class PipeExpression(val expressions: MutableList<Expression>, override val position: Position): Expression() {
override lateinit var parent: Node
constructor(source: Expression, target: Expression, position: Position) : this(mutableListOf(), position) {
if(source is PipeExpression) {
expressions.addAll(source.expressions)
expressions.add(target)
} else {
expressions.add(source)
expressions.add(target)
}
}
override val isSimple = false
override fun linkParents(parent: Node) {
this.parent=parent
expressions.forEach { it.linkParents(this) }
}
override fun copy(): PipeExpression = PipeExpression(expressions.map {it.copy()}.toMutableList(), position)
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun referencesIdentifier(nameInSource: List<String>) =
expressions.any { it.referencesIdentifier(nameInSource) }
override fun inferType(program: Program): InferredTypes.InferredType {
val last = expressions.last()
val type = last.inferType(program)
if(type.isKnown)
return type
val identifier = last as? IdentifierReference
if(identifier!=null) {
val call = FunctionCallExpression(identifier, mutableListOf(), identifier.position)
return call.inferType(program)
}
return InferredTypes.InferredType.unknown()
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Expression)
require(replacement is Expression)
val idx = expressions.indexOf(node)
expressions[idx] = replacement
}
}
fun invertCondition(cond: Expression): BinaryExpression? {
if(cond is BinaryExpression) {

View File

@ -1019,6 +1019,18 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
class Pipe(val expressions: MutableList<Expression>, override val position: Position): Statement() {
override lateinit var parent: Node
constructor(source: Expression, target: Expression, position: Position) : this(mutableListOf(), position) {
if(source is PipeExpression)
expressions.addAll(source.expressions)
else
expressions.add(source)
if(target is PipeExpression)
expressions.addAll(target.expressions)
else
expressions.add(target)
}
override fun linkParents(parent: Node) {
this.parent = parent
expressions.forEach { it.linkParents(this) }
@ -1034,7 +1046,4 @@ class Pipe(val expressions: MutableList<Expression>, override val position: Posi
val idx = expressions.indexOf(node)
expressions[idx] = replacement
}
fun valueDatatype(program: Program): DataType =
expressions.first().inferType(program).getOrElse { throw FatalAstException("invalid dt") }
}

View File

@ -121,6 +121,7 @@ abstract class AstWalker {
open fun before(whenStmt: When, parent: Node): Iterable<IAstModification> = noModifications
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun before(pipe: Pipe, parent: Node): Iterable<IAstModification> = noModifications
open fun before(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> = noModifications
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
@ -165,6 +166,7 @@ abstract class AstWalker {
open fun after(whenStmt: When, parent: Node): Iterable<IAstModification> = noModifications
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
open fun after(pipe: Pipe, parent: Node): Iterable<IAstModification> = noModifications
open fun after(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> = noModifications
protected val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
@ -465,5 +467,11 @@ abstract class AstWalker {
pipe.expressions.forEach { it.accept(this, pipe) }
track(after(pipe, parent), pipe, parent)
}
fun visit(pipe: PipeExpression, parent: Node) {
track(before(pipe, parent), pipe, parent)
pipe.expressions.forEach { it.accept(this, pipe) }
track(after(pipe, parent), pipe, parent)
}
}

View File

@ -185,4 +185,8 @@ interface IAstVisitor {
fun visit(pipe: Pipe) {
pipe.expressions.forEach { it.accept(this) }
}
fun visit(pipe: PipeExpression) {
pipe.expressions.forEach { it.accept(this) }
}
}

View File

@ -120,8 +120,18 @@ class CallGraph(private val program: Program) : IAstVisitor {
allAssemblyNodes.add(inlineAssembly)
}
override fun visit(pipe: PipeExpression) {
processPipe(pipe.expressions, pipe)
super.visit(pipe)
}
override fun visit(pipe: Pipe) {
pipe.expressions.forEach {
processPipe(pipe.expressions, pipe)
super.visit(pipe)
}
private fun processPipe(expressions: Iterable<Expression>, pipe: Node) {
expressions.forEach {
if(it is IdentifierReference){
val otherSub = it.targetSubroutine(program)
if(otherSub!=null) {
@ -132,7 +142,6 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
}
}
super.visit(pipe)
}
fun checkRecursiveCalls(errors: IErrorReporter) {

View File

@ -533,12 +533,11 @@ pipe: ``|>``
|> add_bonus
|> txt.print_uw
or even::
It also works for expressions that return a value, for example ``uword score = add_bonus(determine_score(get_player(1)))`` ::
1 |> get_player
|> determine_score
|> add_bonus
|> txt.print_uw
uword score = get_player(1)
|> determine_score
|> add_bonus
address of: ``&``
This is a prefix operator that can be applied to a string or array variable or literal value.

View File

@ -3,7 +3,7 @@ TODO
For next compiler release (7.7)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- make pipe statement an expression so that it DOES return a result value (possibly) so you can assign it ??
- make pipe statement also an expression so that it can return a result value
- optimize codegen of pipe operator to avoid needless assigns to temp var
- copying floats around: do it with a subroutine rather than 5 lda/sta pairs .
is slower but floats are very slow already anyway and this should take a lot less program size.
@ -25,7 +25,7 @@ Blocked by an official Commander-x16 r39 release
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- can we promise a left-to-right function call argument evaluation? without sacrificing performance
- for the pipe operator: recognise a placeholder (? or % or _) in a non-unary function call to allow things as 4 |> mkword(?, $44) |> print_uw
- for the pipe operator: recognise a placeholder (``?`` or ``%`` or ``_``) in a non-unary function call to allow things as ``4 |> mkword(?, $44) |> print_uw``
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``v_``
then we can get rid of the instruction lists in the machinedefinitions as well?
- fix the asm-labels problem (github issue #62)

View File

@ -1,20 +1,49 @@
%import textio
%import floats
%import test_stack
%zeropage basicsafe
main {
sub start() {
1.234 |> addfloat |> addfloat |> floats.print_f
; float @shared f1
;
; f1 = 1.234 |> addfloat1 |> addfloat2 |> addfloat3 ; TODO fix that the value is actually returned
; floats.print_f(f1)
; txt.nl()
; 1.234 |> addfloat1
; |> addfloat2 |> addfloat3 |> floats.print_f
; txt.nl()
;
; 9+3 |> assemblything
; |> sin8u
; |> add_one
; |> times_two
; |> txt.print_uw
; txt.nl()
test_stack.test()
; TODO fix that the value is actually returned (398) and that X register is preserved:
uword @shared uw = 9+3 |> assemblything
|> sin8u
|> add_one
|> times_two
txt.print_uw(uw)
txt.nl()
9 * 3 |> assemblything
|> sin8u
|> add_one |> times_two
|> txt.print_uw
test_stack.test()
}
sub addfloat(float fl) -> float {
sub addfloat1(float fl) -> float {
return fl+1.11
}
sub addfloat2(float fl) -> float {
return fl+2.22
}
sub addfloat3(float fl) -> float {
return fl+3.33
}
sub add_one(ubyte input) -> ubyte {
return input+1
}

View File

@ -101,8 +101,8 @@ statement :
| repeatloop
| whenstmt
| breakstmt
| pipestmt
| labeldef
| pipestmt
;
@ -160,7 +160,8 @@ assign_target:
postincrdecr : assign_target operator = ('++' | '--') ;
expression :
functioncall
'(' expression ')'
| functioncall
| <assoc=right> prefix = ('+'|'-'|'~') expression
| left = expression EOL? bop = '**' EOL? right = expression
| left = expression EOL? bop = ('*' | '/' | '%' ) EOL? right = expression
@ -183,13 +184,12 @@ expression :
| directmemory
| addressof
| expression typecast
| '(' expression ')'
| pipesource = expression EOL? pipe=PIPE EOL? pipetarget = expression
;
typecast : 'as' datatype;
arrayindexed : scoped_identifier arrayindex ;
directmemory : '@' '(' expression ')';
@ -209,6 +209,8 @@ returnstmt : 'return' expression? ;
breakstmt : 'break';
pipestmt: source=expression pipe=PIPE EOL? target=expression ;
identifier : NAME ;
scoped_identifier : NAME ('.' NAME)* ;
@ -300,5 +302,3 @@ repeatloop: 'repeat' expression? EOL? (statement | statement_block) ;
whenstmt: 'when' expression '{' EOL (when_choice | EOL) * '}' EOL? ;
when_choice: (expression_list | 'else' ) '->' (statement | statement_block ) ;
pipestmt: expression EOL? PIPE expression ( EOL? PIPE expression)* ;