Pipe expression "|>" removed from the language

This commit is contained in:
Irmen de Jong 2022-06-12 18:41:42 +02:00
parent dca092fd7c
commit 5a756aaed9
32 changed files with 44 additions and 1176 deletions

View File

@ -149,19 +149,6 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
}
class PtPipe(type: DataType, val void: Boolean, position: Position) : PtExpression(type, position) {
init {
if(!void)
require(type!=DataType.UNDEFINED)
}
val segments: List<PtExpression>
get() = children.map { it as PtExpression }
override fun printProperties() {}
}
class PtPrefix(val operator: String, type: DataType, position: Position): PtExpression(type, position) {
val value: PtExpression
get() = children.single() as PtExpression

View File

@ -334,7 +334,6 @@ class AsmGen(internal val program: Program,
is RepeatLoop -> translate(stmt)
is When -> translate(stmt)
is AnonymousScope -> translate(stmt)
is Pipe -> translatePipeExpression(stmt.source, stmt.segments, stmt, isStatement = true, pushResultOnEstack = false)
is VarDecl -> { /* do nothing; variables are handled elsewhere */ }
is BuiltinFunctionPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore")
is UntilLoop -> throw AssemblyError("do..until should have been converted to jumps")
@ -444,12 +443,6 @@ class AsmGen(internal val program: Program,
internal fun translateBuiltinFunctionCallExpression(bfc: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(bfc, resultToStack, resultRegister)
private fun translateBuiltinFunctionCallExpression(bfc: IFunctionCall, firstArg: AsmAssignSource, scope: Subroutine): DataType =
builtinFunctionsAsmGen.translateFunctionCallWithFirstArg(bfc, firstArg, false, scope)
private fun translateBuiltinFunctionCallStatement(bfc: IFunctionCall, firstArg: AsmAssignSource, scope: Subroutine) =
builtinFunctionsAsmGen.translateFunctionCallWithFirstArg(bfc, firstArg, true, scope)
internal fun translateFunctionCall(functionCallExpr: FunctionCallExpression, isExpression: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCallExpr, isExpression)
@ -2812,123 +2805,6 @@ $repeatLabel lda $counterVar
}
}
internal fun translatePipeExpression(source: Expression, segments: List<Expression>, scope: Node, isStatement: Boolean, pushResultOnEstack: Boolean) {
// TODO more efficient code generation to avoid needless assignments to the temp var
// the source: an expression (could be anything) producing a value.
// one or more segment expressions, all are a IFunctionCall node, and LACKING the implicit first argument.
// when 'isStatement'=true, the last segment expression should be treated as a funcion call statement (discarding any result value if there is one)
val subroutine = scope.definingSubroutine!!
var valueDt = source.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
var valueSource: AsmAssignSource =
if(source is IFunctionCall) {
val resultReg = returnRegisterOfFunction(source.target)
assignExpressionToRegister(source, resultReg, valueDt in listOf(DataType.BYTE, DataType.WORD, DataType.FLOAT))
AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
} else {
AsmAssignSource.fromAstSource(source, program, this)
}
// the segments (except the last one): function calls taking one or more parameters and producing a value.
// directly assign their first argument from the previous call's returnvalue (and take the rest, if any, from the call itself)
segments.dropLast(1).forEach {
it as IFunctionCall
valueDt = translateFunctionCallWithFirstArg(it, valueSource, false, subroutine)
val resultReg = returnRegisterOfFunction(it.target)
valueSource = AsmAssignSource(SourceStorageKind.REGISTER, program, this, valueDt, register = resultReg)
}
// the last segment: function call taking one or more parameters and optionally producing a result value.
val lastCall = segments.last() as IFunctionCall
if(isStatement) {
translateFunctionCallWithFirstArg(lastCall, valueSource, true, subroutine)
} else {
valueDt = translateFunctionCallWithFirstArg(lastCall, valueSource, false, subroutine)
if(pushResultOnEstack) {
when (valueDt) {
in ByteDatatypes -> {
out(" sta P8ESTACK_LO,x | dex")
}
in WordDatatypes -> {
out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
}
DataType.FLOAT -> {
out(" jsr floats.push_fac1")
}
else -> throw AssemblyError("invalid dt")
}
}
}
}
private fun translateFunctionCallWithFirstArg(
fcall: IFunctionCall,
firstArg: AsmAssignSource,
isStatement: Boolean,
scope: Subroutine
): DataType {
if(fcall.args.isNotEmpty())
TODO("deal with additional args (non-unary function): ${fcall.target.nameInSource} (... , ${fcall.args.joinToString(", ")})")
when(val targetStmt = fcall.target.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> {
return if(isStatement) {
translateBuiltinFunctionCallStatement(fcall, firstArg, scope)
DataType.UNDEFINED
} else {
translateBuiltinFunctionCallExpression(fcall, firstArg, scope)
}
}
is Subroutine -> {
val argDt = targetStmt.parameters.single().type
if(targetStmt.isAsmSubroutine) {
// argument via registers
val argRegister = targetStmt.asmParameterRegisters.single().registerOrPair!!
val assignArgument = AsmAssignment(
firstArg,
AsmAssignTarget.fromRegisters(argRegister, argDt in SignedDatatypes, scope, program, this),
false, program.memsizer, fcall.position
)
translateNormalAssignment(assignArgument)
} else {
val assignArgument: AsmAssignment =
if(functioncallAsmGen.optimizeIntArgsViaRegisters(targetStmt)) {
// argument goes via registers as optimization
val paramReg: RegisterOrPair = when(argDt) {
in ByteDatatypes -> RegisterOrPair.A
in WordDatatypes -> RegisterOrPair.AY
DataType.FLOAT -> RegisterOrPair.FAC1
else -> throw AssemblyError("invalid dt")
}
AsmAssignment(
firstArg,
AsmAssignTarget(TargetStorageKind.REGISTER, program, this, argDt, scope, register = paramReg),
false, program.memsizer, fcall.position
)
} else {
// arg goes via parameter variable
val argVarName = asmVariableName(targetStmt.scopedName + targetStmt.parameters.single().name)
AsmAssignment(
firstArg,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, argDt, scope, argVarName),
false, program.memsizer, fcall.position
)
}
translateNormalAssignment(assignArgument)
}
if(targetStmt.shouldSaveX())
saveRegisterLocal(CpuRegister.X, scope)
out(" jsr ${asmSymbolName(fcall.target)}")
if(targetStmt.shouldSaveX())
restoreRegisterLocal(CpuRegister.X)
return if(isStatement) DataType.UNDEFINED else targetStmt.returntypes.single()
}
else -> throw AssemblyError("invalid call target")
}
}
internal fun popCpuStack(dt: DataType, target: VarDecl, scope: Subroutine?) {
// note: because A is pushed first so popped last, saving A is often not required here.
val parameter = target.subroutineParameter

View File

@ -11,7 +11,6 @@ import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
import prog8.compiler.BuiltinFunctions
import prog8.compiler.FSignature
import prog8.compiler.builtinFunctionReturnType
internal class BuiltinFunctionsAsmGen(private val program: Program,
@ -29,44 +28,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
}
internal fun translateFunctionCallWithFirstArg(bfc: IFunctionCall, singleArg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
val name = bfc.target.nameInSource.single()
val func = BuiltinFunctions.getValue(name)
val argExpression =
when(singleArg.kind) {
SourceStorageKind.LITERALNUMBER -> singleArg.number!!
SourceStorageKind.EXPRESSION -> singleArg.expression!!
SourceStorageKind.ARRAY -> singleArg.array!!
else -> {
// TODO make it so that we can assign efficiently from something else as an expression....namely: register(s)
// this is useful in pipe expressions for instance, to skip the use of a temporary variable
// but for now, just assign it to a temporary variable and use that as a source
// Idea: to do this without having to rewrite every single function in translateFunctioncall(),
// hack a special IdentifierReference like "!6502.A/X/Y/AX/AY/XY" to reference a cpu register
val tempvar = asmgen.getTempVarName(singleArg.datatype)
val assignTempvar = AsmAssignment(
singleArg,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, singleArg.datatype, scope, variableAsmName = asmgen.asmVariableName(tempvar)),
false, program.memsizer, Position.DUMMY
)
assignAsmGen.translateNormalAssignment(assignTempvar)
// now use an expression to assign this tempvar
val ident = IdentifierReference(tempvar, Position.DUMMY)
ident.linkParents(scope)
ident
}
}
val argExpressions = mutableListOf(argExpression)
val fcall = BuiltinFunctionCall(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY)
fcall.linkParents(scope)
translateFunctioncall(fcall, func, discardResult = false, resultToStack = false, null)
return if(isStatement) {
DataType.UNDEFINED
} else {
builtinFunctionReturnType(func.name).getOrElse { throw AssemblyError("unknown dt") }
}
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if (discardResult && func.pure)
return // can just ignore the whole function call altogether

View File

@ -34,8 +34,6 @@ internal class ExpressionsAsmGen(private val program: Program,
is IdentifierReference -> translateExpression(expression)
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
is BuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
is PipeExpression -> asmgen.translatePipeExpression(expression.source, expression.segments,
expression, isStatement = false, pushResultOnEstack = true )
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is ArrayLiteral, is StringLiteral -> 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")

View File

@ -2,7 +2,10 @@ package prog8.codegen.cpu6502.assignment
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.Subroutine
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen

View File

@ -290,16 +290,6 @@ internal class AssignmentAsmGen(private val program: Program,
containmentCheckIntoA(value)
assignRegisterByte(assign.target, CpuRegister.A)
}
is PipeExpression -> {
asmgen.translatePipeExpression(value.source, value.segments, value, isStatement = false, pushResultOnEstack = false)
val resultDt = value.inferType(program)
val register =
if(resultDt.isBytes) RegisterOrPair.A
else if(resultDt.isWords) RegisterOrPair.AY
else if(resultDt istype DataType.FLOAT) RegisterOrPair.FAC1
else throw AssemblyError("invalid dt")
asmgen.assignRegister(register, assign.target)
}
is BinaryExpression -> {
if(value.operator in ComparisonOperators) {
// TODO real optimized code for comparison expressions that yield a boolean result value

View File

@ -172,7 +172,6 @@ class AstToXmlConverter(internal val program: PtProgram,
is PtMemoryByte -> write(it)
is PtMemMapped -> write(it)
is PtNumber -> write(it)
is PtPipe -> write(it)
is PtPostIncrDecr -> write(it)
is PtPrefix -> write(it)
is PtRange -> write(it)
@ -204,14 +203,6 @@ class AstToXmlConverter(internal val program: PtProgram,
xml.endElt()
}
private fun write(pipe: PtPipe) {
xml.elt("pipe")
xml.attr("type", pipe.type.name)
xml.startChildren()
pipe.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(array: PtArray) {
xml.elt("array")
xml.attr("type", array.type.name)

View File

@ -84,7 +84,6 @@ class CodeGen(internal val program: PtProgram,
is PtReturn -> translate(node)
is PtJump -> translate(node)
is PtWhen -> translate(node)
is PtPipe -> expressionEval.translate(node, 0)
is PtForLoop -> translate(node)
is PtIfElse -> translate(node)
is PtPostIncrDecr -> translate(node)

View File

@ -63,7 +63,6 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
is PtBuiltinFunctionCall -> code += codeGen.translateBuiltinFunc(expr, resultRegister)
is PtFunctionCall -> code += translate(expr, resultRegister, resultFpRegister)
is PtContainmentCheck -> code += translate(expr, resultRegister, resultFpRegister)
is PtPipe -> code += translate(expr, resultRegister)
is PtRange,
is PtArray,
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
@ -72,46 +71,6 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
return code
}
internal fun translate(pipe: PtPipe, resultRegister: Int): VmCodeChunk {
val segments = pipe.segments
var valueDt = segments[0].type
var valueReg = if(pipe.void) codeGen.vmRegisters.nextFree() else resultRegister
fun addImplicitArgToSegment(segment: PtExpression, sourceReg: Int, sourceDt: DataType): PtExpression {
return when (segment) {
is PtFunctionCall -> {
val segWithArg = PtFunctionCall(segment.functionName, segment.void, segment.type, segment.position)
segWithArg.children.add(PtMachineRegister(sourceReg, sourceDt, segment.position))
segWithArg.children.addAll(segment.args)
segWithArg
}
is PtBuiltinFunctionCall -> {
val segWithArg = PtBuiltinFunctionCall(segment.name, segment.void, segment.hasNoSideEffects, segment.type, segment.position)
segWithArg.children.add(PtMachineRegister(sourceReg, sourceDt, segment.position))
segWithArg.children.addAll(segment.args)
segWithArg
}
else -> throw AssemblyError("weird segment type")
}
}
val code = VmCodeChunk()
code += translateExpression(segments[0], valueReg, -1)
for (segment in segments.subList(1, segments.size-1)) {
val sourceReg = valueReg
val sourceDt = valueDt
if(segment.type!=valueDt) {
valueDt = segment.type
valueReg = codeGen.vmRegisters.nextFree()
}
val segmentWithImplicitArgument = addImplicitArgToSegment(segment, sourceReg, sourceDt)
code += translateExpression(segmentWithImplicitArgument, valueReg, -1)
}
val segWithArg = addImplicitArgToSegment(segments.last(), valueReg, valueDt)
code += translateExpression(segWithArg, resultRegister, -1)
return code
}
private fun translate(check: PtContainmentCheck, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += translateExpression(check.element, resultRegister, -1) // load the element to check in resultRegister

View File

@ -1,12 +1,20 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Assignment
import prog8.ast.statements.IfElse
import prog8.ast.statements.Jump
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.core.AssociativeOperators
import prog8.code.core.DataType
import prog8.code.core.IntegerDatatypes
import prog8.code.core.NumericDatatypes
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
@ -331,31 +339,6 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return noModifications
}
override fun after(pipeExpr: PipeExpression, parent: Node) = processPipe(pipeExpr, parent)
override fun after(pipe: Pipe, parent: Node) = processPipe(pipe, parent)
private fun processPipe(pipe: IPipe, parent: Node): Iterable<IAstModification> {
if(pipe.source.isSimple) {
val segments = pipe.segments
if(segments.size==1) {
// replace the whole pipe with a normal function call
val funcname = (segments[0] as IFunctionCall).target
val call = if(pipe is Pipe)
FunctionCallStatement(funcname, mutableListOf(pipe.source), true, pipe.position)
else
FunctionCallExpression(funcname, mutableListOf(pipe.source), pipe.position)
return listOf(IAstModification.ReplaceNode(pipe as Node, call, parent))
} else if(segments.size>1) {
// replace source+firstsegment by firstsegment(source) call as the new source
val firstSegment = segments.removeAt(0) as IFunctionCall
val newArgs = listOf(pipe.source) + firstSegment.args
val call = FunctionCallExpression(firstSegment.target, newArgs.toMutableList(), pipe.position)
return listOf(IAstModification.ReplaceNode(pipe.source, call, pipe as Node))
}
}
return noModifications
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when {
subBinExpr.left isSameAs x -> subBinExpr.right

View File

@ -1,7 +1,10 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.expressions.*
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.PrefixExpression
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
@ -236,7 +239,6 @@ class UnusedCodeRemover(private val program: Program,
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is PipeExpression,
is IFunctionCall -> { /* don't remove */ }
else -> {
if(assign1.value !is IFunctionCall)

View File

@ -50,7 +50,6 @@ class IntermediateAstMaker(val program: Program) {
is InlineAssembly -> transform(statement)
is Jump -> transform(statement)
is Label -> transform(statement)
is Pipe -> transform(statement)
is PostIncrDecr -> transform(statement)
is RepeatLoop -> transform(statement)
is Return -> transform(statement)
@ -80,7 +79,6 @@ class IntermediateAstMaker(val program: Program) {
is FunctionCallExpression -> transform(expr)
is IdentifierReference -> transform(expr)
is NumericLiteral -> transform(expr)
is PipeExpression -> transform(expr)
is PrefixExpression -> transform(expr)
is RangeExpression -> transform(expr)
is StringLiteral -> transform(expr)
@ -225,11 +223,7 @@ class IntermediateAstMaker(val program: Program) {
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
val (target, _) = targetOf(srcCall.target)
val type = srcCall.inferType(program).getOrElse {
if((srcCall.parent as? Pipe)?.segments?.last() === srcCall)
// for a pipe, the last segment is allowed to be a call to a function not returning anything.
DataType.UNDEFINED
else
throw FatalAstException("unknown dt $srcCall")
throw FatalAstException("unknown dt $srcCall")
}
val call = PtFunctionCall(target, type==DataType.UNDEFINED, type, srcCall.position)
for (arg in srcCall.args)
@ -287,14 +281,6 @@ class IntermediateAstMaker(val program: Program) {
private fun transform(label: Label): PtLabel =
PtLabel(label.name, label.position)
private fun transform(srcPipe: Pipe): PtPipe {
val pipe = PtPipe(DataType.UNDEFINED, true, srcPipe.position)
pipe.add(transformExpression(srcPipe.source))
for (segment in srcPipe.segments)
pipe.add(transformExpression(segment))
return pipe
}
private fun transform(src: PostIncrDecr): PtPostIncrDecr {
val post = PtPostIncrDecr(src.operator, src.position)
post.add(transform(src.target))
@ -459,15 +445,6 @@ class IntermediateAstMaker(val program: Program) {
private fun transform(number: NumericLiteral): PtNumber =
PtNumber(number.type, number.number, number.position)
private fun transform(srcPipe: PipeExpression): PtPipe {
val type = srcPipe.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val pipe = PtPipe(type, false, srcPipe.position)
pipe.add(transformExpression(srcPipe.source))
for (segment in srcPipe.segments)
pipe.add(transformExpression(segment))
return pipe
}
private fun transform(srcPrefix: PrefixExpression): PtPrefix {
val type = srcPrefix.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
val prefix = PtPrefix(srcPrefix.operator, type, srcPrefix.position)

View File

@ -503,7 +503,7 @@ internal class AstChecker(private val program: Program,
if(constVal==null) {
val sourceDatatype = assignment.value.inferType(program)
if (sourceDatatype.isUnknown) {
if (assignment.value !is FunctionCallExpression && assignment.value !is PipeExpression)
if (assignment.value !is FunctionCallExpression)
errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
@ -1009,7 +1009,7 @@ internal class AstChecker(private val program: Program,
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null) {
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
checkUnusedReturnValues(functionCallStatement, targetStatement, program, errors)
checkUnusedReturnValues(functionCallStatement, targetStatement, errors)
}
val funcName = functionCallStatement.target.nameInSource
@ -1100,33 +1100,6 @@ internal class AstChecker(private val program: Program,
}
}
override fun visit(pipe: PipeExpression) {
process(pipe)
super.visit(pipe)
}
override fun visit(pipe: Pipe) {
process(pipe)
super.visit(pipe)
}
private fun process(pipe: IPipe) {
if(pipe.source in pipe.segments)
throw InternalCompilerException("pipe source and segments should all be different nodes")
if (pipe.segments.isEmpty())
throw FatalAstException("pipe is missing one or more expressions")
if(pipe.segments.any { it !is IFunctionCall })
throw FatalAstException("pipe segments can only be function calls")
if(compilerOptions.compTarget !is VMTarget) {
pipe.segments.forEach {
it as IFunctionCall
if (it.args.size > 0)
errors.err("only unary functions supported in pipe expressions for now", it.position)
}
}
}
override fun visit(postIncrDecr: PostIncrDecr) {
if(postIncrDecr.target.identifier != null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource
@ -1498,7 +1471,7 @@ internal class AstChecker(private val program: Program,
}
}
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, program: Program, errors: IErrorReporter) {
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, errors: IErrorReporter) {
if (!call.void) {
// check for unused return values
if (target is Subroutine && target.returntypes.isNotEmpty()) {

View File

@ -1,7 +1,6 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IPipe
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.FunctionCallExpression
@ -151,32 +150,20 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
private fun visitFunctionCall(call: IFunctionCall) {
val isPartOfPipeSegments = (call.parent as? IPipe)?.segments?.contains(call as Node) == true
val errormessageAboutArgs = if(isPartOfPipeSegments) "invalid number of arguments in piped call" else "invalid number of arguments"
when (val target = call.target.targetStatement(program)) {
is Subroutine -> {
// if the call is part of a Pipe, the number of arguments in the call should be 1 less than the number of parameters
val expectedNumberOfArgs: Int = if(isPartOfPipeSegments) {
target.parameters.size - 1
} else {
target.parameters.size
}
val expectedNumberOfArgs: Int = target.parameters.size
if(call.args.size != expectedNumberOfArgs) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err(errormessageAboutArgs, pos)
errors.err("invalid number of arguments", pos)
}
}
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
// if the call is part of a Pipe, the number of arguments in the call should be 1 less than the number of parameters
val expectedNumberOfArgs: Int = if(isPartOfPipeSegments) {
func.parameters.size-1
} else {
func.parameters.size
}
val expectedNumberOfArgs: Int = func.parameters.size
if(call.args.size != expectedNumberOfArgs) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err(errormessageAboutArgs, pos)
errors.err("invalid number of arguments", pos)
}
if(func.name=="memory") {
val name = call.args[0] as? StringLiteral

View File

@ -1,6 +1,5 @@
package prog8.compiler.astprocessing
import prog8.ast.IPipe
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.SyntaxError
@ -8,7 +7,10 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.code.core.*
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
import prog8.code.core.NumericDatatypes
class AstPreprocessor(val program: Program, val errors: IErrorReporter, val compTarget: ICompilationTarget) : AstWalker() {
@ -25,32 +27,6 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter, val comp
return super.before(string, parent)
}
override fun before(pipe: Pipe, parent: Node): Iterable<IAstModification> {
if(pipe.source is PipeExpression) {
// correct Antlr parse tree quirk: turn nested pipe into single flat pipe
val psrc = pipe.source as PipeExpression
val newSource = psrc.source
val newSegments = psrc.segments
newSegments += pipe.segments.single()
return listOf(IAstModification.ReplaceNode(pipe as Node, Pipe(newSource, newSegments, pipe.position), parent))
} else if(pipe.source is IPipe)
throw InternalCompilerException("pipe source should have been adjusted to be a normal expression")
return noModifications
}
override fun before(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> {
if(pipeExpr.source is PipeExpression) {
// correct Antlr parse tree quirk; turn nested pipe into single flat pipe
val psrc = pipeExpr.source as PipeExpression
val newSource = psrc.source
val newSegments = psrc.segments
newSegments += pipeExpr.segments.single()
return listOf(IAstModification.ReplaceNode(pipeExpr as Node, PipeExpression(newSource, newSegments, pipeExpr.position), parent))
} else if(pipeExpr.source is IPipe)
throw InternalCompilerException("pipe source should have been adjusted to be a normal expression")
return noModifications
}
override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
val modifications = mutableListOf<IAstModification>()

View File

@ -419,7 +419,7 @@ internal class StatementReorderer(val program: Program,
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)
?: throw FatalAstException("no target for $functionCallStatement")
checkUnusedReturnValues(functionCallStatement, function, program, errors)
checkUnusedReturnValues(functionCallStatement, function, errors)
return tryReplaceCallWithGosub(functionCallStatement, parent, program, options)
}
}

View File

@ -1,13 +1,9 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IPipe
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.PipeExpression
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
@ -15,7 +11,6 @@ import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
import prog8.compiler.BuiltinFunctions
import prog8.compiler.builtinFunctionReturnType
internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorReporter) : IAstVisitor {
@ -54,16 +49,10 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
return Pair("argument ${firstUnknownDt+1} invalid argument type", call.args[firstUnknownDt].position)
val argtypes = argITypes.map { it.getOr(DataType.UNDEFINED) }
val target = call.target.targetStatement(program)
val isPartOfPipeSegments = (call.parent as? IPipe)?.segments?.contains(call as Node) == true
val errormessageAboutArgs = if(isPartOfPipeSegments) "invalid number of arguments in piped call" else "invalid number of arguments"
if (target is Subroutine) {
val consideredParamTypes: List<DataType> = if(isPartOfPipeSegments) {
target.parameters.drop(1).map { it.type } // skip first one (the implicit first arg), this is checked elsewhere
} else {
target.parameters.map { it.type }
}
val consideredParamTypes: List<DataType> = target.parameters.map { it.type }
if(argtypes.size != consideredParamTypes.size)
return Pair(errormessageAboutArgs, call.position)
return Pair("invalid number of arguments", call.position)
val mismatch = argtypes.zip(consideredParamTypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
if(mismatch>=0) {
val actual = argtypes[mismatch].toString()
@ -90,13 +79,9 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
}
else if (target is BuiltinFunctionPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
val consideredParamTypes: List<Array<DataType>> = if(isPartOfPipeSegments) {
func.parameters.drop(1).map { it.possibleDatatypes } // skip first one (the implicit first arg), this is checked elsewhere
} else {
func.parameters.map { it.possibleDatatypes }
}
val consideredParamTypes: List<Array<DataType>> = func.parameters.map { it.possibleDatatypes }
if(argtypes.size != consideredParamTypes.size)
return Pair(errormessageAboutArgs, call.position)
return Pair("invalid number of arguments", call.position)
argtypes.zip(consideredParamTypes).forEachIndexed { index, pair ->
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
if (!anyCompatible) {
@ -115,88 +100,4 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
return null
}
}
override fun visit(pipe: PipeExpression) {
processPipe(pipe.source, pipe.segments)
if(errors.noErrors()) {
val last = (pipe.segments.last() as IFunctionCall).target
when (val target = last.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> {
if (BuiltinFunctions.getValue(target.name).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.source, pipe.segments)
if(errors.noErrors()) {
super.visit(pipe)
}
}
private fun processPipe(source: Expression, segments: List<Expression>) {
val sourceArg = (source as? IFunctionCall)?.args?.firstOrNull()
if(sourceArg!=null && segments.any { (it as IFunctionCall).args.firstOrNull() === sourceArg})
throw FatalAstException("some pipe segment first arg is replicated from the source functioncall arg")
// invalid size and other issues will be handled by the ast checker later.
var valueDt = source.inferType(program).getOrElse {
throw FatalAstException("invalid dt")
}
for(funccall in segments) {
val target = (funccall as IFunctionCall).target.targetStatement(program)
if(target!=null) {
when (target) {
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
if(func.parameters.isEmpty())
errors.err("function must have at least one parameter", funccall.position)
else if(func.returnType==null && funccall !== segments.last())
errors.err("function must return a single value", funccall.position)
val paramDts = func.parameters.firstOrNull()?.possibleDatatypes
if(paramDts!=null && !paramDts.any { valueDt isAssignableTo it })
errors.err("pipe value datatype $valueDt incompatible with function argument ${paramDts.toList()}", funccall.position)
if(errors.noErrors()) {
valueDt = builtinFunctionReturnType(func.name).getOrElse { DataType.UNDEFINED }
}
}
is Subroutine -> {
if(target.parameters.isEmpty())
errors.err("function must have at least one parameter", funccall.position)
else if(target.returntypes.size!=1 && funccall !== segments.last())
errors.err("function must return a single value", funccall.position)
val paramDt = target.parameters.firstOrNull()?.type
if(paramDt!=null && !(valueDt isAssignableTo paramDt))
errors.err("pipe value datatype $valueDt incompatible with function argument $paramDt", funccall.position)
if(target.returntypes.isNotEmpty())
valueDt = target.returntypes.single()
}
is VarDecl -> {
if(!(valueDt isAssignableTo target.datatype))
errors.err("final pipe value datatype can't be stored in pipe ending variable", funccall.position)
}
else -> {
throw FatalAstException("weird function")
}
}
}
}
}
}

View File

@ -1,512 +0,0 @@
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.IFunctionCall
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Pipe
import prog8.ast.statements.VarDecl
import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.core.SourceCode
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8.compiler.astprocessing.AstPreprocessor
import prog8.parser.Prog8Parser.parseModule
import prog8tests.helpers.*
class TestPipes: FunSpec({
test("pipe expression parse tree after preprocessing") {
val text = """
main {
sub start() {
uword xx = 9999 |> func1() |> func2()
|> func1() |> func2()
|> func1()
}
sub func1(uword arg) -> uword {
return arg+1111
}
sub func2(uword arg) -> uword {
return arg+2222
}
}"""
val src = SourceCode.Text(text)
val module = parseModule(src)
val errors = ErrorReporterForTests()
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val preprocess = AstPreprocessor(program, errors, C64Target())
preprocess.visit(program)
errors.errors.size shouldBe 0
preprocess.applyModifications()
program.entrypoint.statements.size shouldBe 1
val pipe = (program.entrypoint.statements.single() as VarDecl).value as PipeExpression
pipe.source shouldBe NumericLiteral(DataType.UWORD, 9999.0, Position.DUMMY)
pipe.segments.size shouldBe 5
var call = pipe.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[2] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[3] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[4] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
}
test("pipe statement parse tree after preprocessing") {
val text = """
main {
sub start() {
9999 |> func1() |> func2()
|> func1() |> func2()
|> func3()
}
sub func1(uword arg) -> uword {
return arg+1111
}
sub func2(uword arg) -> uword {
return arg+2222
}
sub func3(uword arg) {
; nothing
}
}"""
val src = SourceCode.Text(text)
val module = parseModule(src)
val errors = ErrorReporterForTests()
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val preprocess = AstPreprocessor(program, errors, C64Target())
preprocess.visit(program)
errors.errors.size shouldBe 0
preprocess.applyModifications()
program.entrypoint.statements.size shouldBe 1
val pipe = program.entrypoint.statements.single() as Pipe
pipe.source shouldBe NumericLiteral(DataType.UWORD, 9999.0, Position.DUMMY)
pipe.segments.size shouldBe 5
var call = pipe.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[2] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[3] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[4] as IFunctionCall
call.target.nameInSource shouldBe listOf("func3")
call.args.size shouldBe 0
}
test("correct pipe statements (no opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
1.234 |> addfloat()
|> floats.print_f()
startvalue(99) |> addword()
|> txt.print_uw()
9999 |> abs() |> txt.print_uw()
9999 |> txt.print_uw()
99 |> abs() |> lsb() |> txt.print_ub()
99 |> txt.print_ub()
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}"""
val result = compileText(C64Target(), optimize = false, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
val pipef = stmts[0] as Pipe
pipef.source shouldBe instanceOf<NumericLiteral>()
pipef.segments.size shouldBe 2
var call = pipef.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
call = pipef.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("floats", "print_f")
val pipew = stmts[1] as Pipe
pipef.source shouldBe instanceOf<NumericLiteral>()
pipew.segments.size shouldBe 2
call = pipew.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
call = pipew.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("txt", "print_uw")
stmts[2] shouldBe instanceOf<Pipe>()
stmts[3] shouldBe instanceOf<Pipe>()
stmts[4] shouldBe instanceOf<Pipe>()
stmts[5] shouldBe instanceOf<Pipe>()
}
test("correct pipe statements (with opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
1.234 |> addfloat()
|> floats.print_f()
startvalue(99) |> addword()
|> txt.print_uw()
; these should be optimized into just the function calls:
9999 |> abs() |> txt.print_uw()
9999 |> txt.print_uw()
99 |> abs() |> txt.print_ub()
99 |> txt.print_ub()
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}"""
val result = compileText(C64Target(), optimize = true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
val pipef = stmts[0] as Pipe
pipef.source shouldBe instanceOf<FunctionCallExpression>()
(pipef.source as IFunctionCall).target.nameInSource shouldBe listOf("addfloat")
pipef.segments.size shouldBe 1
val callf = pipef.segments[0] as IFunctionCall
callf.target.nameInSource shouldBe listOf("floats", "print_f")
val pipew = stmts[1] as Pipe
pipef.source shouldBe instanceOf<FunctionCallExpression>()
(pipew.source as IFunctionCall).target.nameInSource shouldBe listOf("startvalue")
pipew.segments.size shouldBe 2
val callw = pipew.segments[1] as IFunctionCall
callw.target.nameInSource shouldBe listOf("txt", "print_uw")
var stmt = stmts[2] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_uw")
stmt = stmts[3] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_uw")
stmt = stmts[4] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_ub")
stmt = stmts[5] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_ub")
}
test("incorrect type in pipe statement") {
val text = """
%option enable_floats
main {
sub start() {
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) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "incompatible"
}
test("correct pipe expressions (no opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
float @shared fl = 1.234 |> addfloat()
|> addfloat()
uword @shared ww = startvalue(99) |> addword()
|> addword()
ubyte @shared cc = 30 |> abs() |> sqrt16()
cc = cc |> abs() |> sqrt16()
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}"""
val result = compileText(C64Target(), optimize = false, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 8
val assignf = stmts[1] as Assignment
val pipef = assignf.value as PipeExpression
pipef.source shouldBe instanceOf<NumericLiteral>()
pipef.segments.size shouldBe 2
var call = pipef.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
call = pipef.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
val assignw = stmts[3] as Assignment
val pipew = assignw.value as PipeExpression
pipew.source shouldBe instanceOf<IFunctionCall>()
pipew.segments.size shouldBe 2
call = pipew.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
call = pipew.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
var assigncc = stmts[5] as Assignment
val value = assigncc.value as PipeExpression
value.source shouldBe NumericLiteral(DataType.UBYTE, 30.0, Position.DUMMY)
value.segments.size shouldBe 2
call = value.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("abs")
call = value.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("sqrt16")
assigncc = stmts[6] as Assignment
val pipecc = assigncc.value as PipeExpression
pipecc.source shouldBe instanceOf<IdentifierReference>()
pipecc.segments.size shouldBe 2
pipecc.segments[0] shouldBe instanceOf<BuiltinFunctionCall>()
pipecc.segments[1] shouldBe instanceOf<BuiltinFunctionCall>()
}
test("correct pipe expressions (with opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
float @shared fl = 1.234 |> addfloat()
|> addfloat()
uword @shared ww = startvalue(99) |> addword()
|> addword()
ubyte @shared cc = 80 |> abs() |> sqrt16() ; will be optimized away into a const number
cc = cc |> abs() |> sqrt16()
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}
"""
val result = compileText(C64Target(), optimize = true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 8
val assignf = stmts[1] as Assignment
val pipef = assignf.value as PipeExpression
pipef.source shouldBe instanceOf<FunctionCallExpression>()
pipef.segments.size shouldBe 1
pipef.segments[0] shouldBe instanceOf<FunctionCallExpression>()
val assignw = stmts[3] as Assignment
val pipew = assignw.value as PipeExpression
pipew.source shouldBe instanceOf<FunctionCallExpression>()
pipew.segments.size shouldBe 2
pipew.segments[0] shouldBe instanceOf<FunctionCallExpression>()
pipew.segments[1] shouldBe instanceOf<FunctionCallExpression>()
var assigncc = stmts[5] as Assignment
val value = assigncc.value as NumericLiteral
value.number shouldBe 8.0
assigncc = stmts[6] as Assignment
val pipecc = assigncc.value as PipeExpression
pipecc.source shouldBe instanceOf<BuiltinFunctionCall>()
(pipecc.source as BuiltinFunctionCall).target.nameInSource shouldBe listOf("abs")
pipecc.segments.size shouldBe 1
pipecc.segments[0] shouldBe instanceOf<BuiltinFunctionCall>()
(pipecc.segments[0] as BuiltinFunctionCall).target.nameInSource shouldBe listOf("sqrt16")
}
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) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "incompatible"
}
test("correct pipe statement with builtin expression") {
val text = """
%import textio
main {
sub start() {
uword ww = 9999
ubyte bb = 99
ww |> abs() |> txt.print_uw()
bb |> abs() |> lsb() |> txt.print_ub()
}
}
"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
val pipeww = stmts[4] as Pipe
pipeww.source shouldBe instanceOf<BuiltinFunctionCall>()
pipeww.segments.size shouldBe 1
pipeww.segments[0] shouldBe instanceOf<IFunctionCall>()
val pipebb = stmts[5] as Pipe
pipebb.source shouldBe instanceOf<BuiltinFunctionCall>()
pipebb.segments.size shouldBe 2
pipebb.segments[0] shouldBe instanceOf<IFunctionCall>()
pipebb.segments[1] shouldBe instanceOf<IFunctionCall>()
}
test("pipe statement with type errors") {
val text = """
%import textio
main {
sub start() {
uword ww = 9999
9999 |> abs() |> txt.print_ub()
ww |> abs() |> txt.print_ub()
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target(), optimize = false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "UWORD incompatible"
errors.errors[1] shouldContain "UWORD incompatible"
}
test("pipe detects invalid number of args") {
val text = """
main {
sub start() {
uword ww = startvalue() |> addword()
|> addword()
ubyte cc = 30 |> abs(99) |> sqrt16(22)
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addword(uword ww) -> uword {
return ww+2222
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), optimize = false, text, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 3
errors.errors[0] shouldContain ":4:32: invalid number of arguments"
errors.errors[1] shouldContain ":7:42: invalid number of arguments"
errors.errors[2] shouldContain ":7:56: invalid number of arguments"
}
test("non-unary funcions in pipe ok for target virtual") {
val text = """
main {
sub start() {
uword @shared wvalue = add(3,4) |> add(48) |> mkword(234)
}
sub add(ubyte first, ubyte second) -> ubyte {
return first+second
}
}"""
val errors = ErrorReporterForTests()
val result = compileText(VMTarget(), optimize = false, text, writeAssembly = true, errors=errors)!!
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 0
result.program.entrypoint.statements.size shouldBe 3
}
test("non-unary funcions in pipe not yet ok for other targets") {
// NOTE: once other targets also support this, merge this into the test above
val text = """
main {
sub start() {
uword @shared wvalue = add(3,4) |> add(48) |> mkword(234)
}
sub add(ubyte first, ubyte second) -> ubyte {
return first+second
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), optimize = false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "only unary"
errors.errors[1] shouldContain "only unary"
}
})

View File

@ -21,7 +21,7 @@ class TestIntermediateAst: FunSpec({
ubyte cc
ubyte[] array = [1,2,3]
cc = 11 in array
cc = cc |> lsb() |> sqrt16()
cc = sqrt16(lsb(cc))
}
}
"""
@ -53,9 +53,9 @@ class TestIntermediateAst: FunSpec({
val containment = (entry.children[2] as PtAssignment).value as PtContainmentCheck
(containment.element as PtNumber).number shouldBe 11.0
val pipe = (entry.children[3] as PtAssignment).value as PtPipe
pipe.void shouldBe false
pipe.type shouldBe DataType.UBYTE
val fcall = (entry.children[3] as PtAssignment).value as PtFunctionCall
fcall.void shouldBe false
fcall.type shouldBe DataType.UBYTE
ast.print()
}

View File

@ -461,26 +461,4 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
whenChoice.statements.accept(this)
outputln("")
}
override fun visit(pipe: Pipe) {
printPipe(pipe.source, pipe.segments)
}
override fun visit(pipe: PipeExpression) {
printPipe(pipe.source, pipe.segments)
}
private fun printPipe(source: Expression, segments: Iterable<Expression>) {
source.accept(this)
output(" |> ")
segments.first().accept(this)
outputln("")
scopelevel++
segments.drop(1).forEach {
outputi("|> ")
it.accept(this)
outputln("")
}
scopelevel--
}
}

View File

@ -31,13 +31,6 @@ interface IFunctionCall {
var parent: Node // will be linked correctly later (late init)
}
interface IPipe {
var source: Expression
val segments: MutableList<Expression> // are all function calls
val position: Position
var parent: Node // will be linked correctly later (late init)
}
interface IStatementContainer {
val statements: MutableList<Statement>

View File

@ -146,9 +146,6 @@ private fun Prog8ANTLRParser.StatementContext.toAst() : Statement {
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
val pipestmt = pipestmt()?.toAst()
if(pipestmt!=null) return pipestmt
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
@ -437,13 +434,6 @@ private fun Prog8ANTLRParser.ExpressionContext.toAst() : Expression {
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
if(pipesegment()!=null)
return PipeExpression(
expression(0).toAst(),
pipesegment().map { it.functioncall().toAst() }.toMutableList(),
toPosition()
)
throw FatalAstException(text)
}
@ -605,11 +595,3 @@ private fun Prog8ANTLRParser.VardeclContext.toAst(type: VarDeclType, value: Expr
toPosition()
)
}
private fun Prog8ANTLRParser.PipestmtContext.toAst(): Pipe {
return Pipe(
expression().toAst(),
pipesegment().map { it.functioncall().toAst() }.toMutableList(),
toPosition()
)
}

View File

@ -1,7 +1,6 @@
package prog8.ast.expressions
import prog8.ast.IFunctionCall
import prog8.ast.IPipe
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ExpressionError
@ -975,13 +974,6 @@ class FunctionCallExpression(override var target: IdentifierReference,
if(target.nameInSource.size>1)
return null
// If the function call is part of a Pipe segments, the number of args will be 1 less than the number of parameters required
// because of the implicit first argument. We don't know this first argument here. Assume it is not a constant,
// which means that this function call cannot be a constant either.
val pipeParentSegments = (parent as? IPipe)?.segments ?: emptyList()
if(this in pipeParentSegments)
return null
val resultValue: NumericLiteral? = program.builtinFunctions.constValue(target.nameInSource[0], args, position)
if(withDatatypeCheck) {
val resultDt = this.inferType(program)
@ -1112,38 +1104,6 @@ class ContainmentCheck(var element: Expression,
}
}
class PipeExpression(override var source: Expression,
override val segments: MutableList<Expression>, // are all function calls
override val position: Position): Expression(), IPipe {
override lateinit var parent: Node
override val isSimple = false
override fun linkParents(parent: Node) {
this.parent=parent
source.linkParents(this)
segments.forEach { it.linkParents(this) }
}
override fun copy(): PipeExpression = PipeExpression(source.copy(), segments.map {it.copy()}.toMutableList(), position)
override fun constValue(program: Program): NumericLiteral? = null
override fun referencesIdentifier(nameInSource: List<String>) =
source.referencesIdentifier(nameInSource) || segments.any { it.referencesIdentifier(nameInSource) }
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun inferType(program: Program) = segments.last().inferType(program)
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Expression)
require(replacement is Expression)
if(node===source) {
source = replacement
} else {
require(replacement is IFunctionCall)
val idx = segments.indexOf(node)
segments[idx] = replacement
}
}
}
fun invertCondition(cond: Expression): BinaryExpression? {
if(cond is BinaryExpression) {

View File

@ -1064,34 +1064,6 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
}
class Pipe(override var source: Expression,
override val segments: MutableList<Expression>, // are all function calls
override val position: Position): Statement(), IPipe {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
source.linkParents(this)
segments.forEach { it.linkParents(this) }
}
override fun copy() = Pipe(source.copy(), segments.map { it.copy() }.toMutableList(), 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) {
require(node is Expression)
require(replacement is Expression)
if(node===source) {
source = replacement
} else {
require(replacement is IFunctionCall)
val idx = segments.indexOf(node)
segments[idx] = replacement
}
}
}
// Calls to builtin functions will be replaced with this node just before handing the Ast to the codegen.
// this is meant to eventually (?) be able to not have any FunctionCallStatement nodes to worry about anymore
// in the codegen, because they have been converted into GoSub (for instance) or this node.

View File

@ -121,8 +121,6 @@ abstract class AstWalker {
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
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: ArrayLiteral, parent: Node): Iterable<IAstModification> = noModifications
@ -166,8 +164,6 @@ abstract class AstWalker {
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
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>>()
@ -470,19 +466,5 @@ abstract class AstWalker {
whenChoice.statements.accept(this, whenChoice)
track(after(whenChoice, parent), whenChoice, parent)
}
fun visit(pipe: Pipe, parent: Node) {
track(before(pipe, parent), pipe, parent)
pipe.source.accept(this, pipe)
pipe.segments.forEach { it.accept(this, pipe) }
track(after(pipe, parent), pipe, parent)
}
fun visit(pipe: PipeExpression, parent: Node) {
track(before(pipe, parent), pipe, parent)
pipe.source.accept(this, pipe)
pipe.segments.forEach { it.accept(this, pipe) }
track(after(pipe, parent), pipe, parent)
}
}

View File

@ -187,14 +187,4 @@ interface IAstVisitor {
whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this)
}
fun visit(pipe: Pipe) {
pipe.source.accept(this)
pipe.segments.forEach { it.accept(this) }
}
fun visit(pipe: PipeExpression) {
pipe.source.accept(this)
pipe.segments.forEach { it.accept(this) }
}
}

View File

@ -66,7 +66,6 @@ Language features
- Conditional branches to map directly on processor branch instructions
- ``when`` statement to avoid if-else chains
- ``in`` expression for concise and efficient multi-value/containment test
- pipe operator ``|>`` to rewrite nested function call expressions in a more readable chained form
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Floating point math also supported if the target system provides floating point library routines (C64 and Cx16 both do).

View File

@ -701,10 +701,6 @@ The arguments in parentheses after the function name, should match the parameter
If you want to ignore a return value of a subroutine, you should prefix the call with the ``void`` keyword.
Otherwise the compiler will issue a warning about discarding a result value.
Deeply nested function calls can be rewritten as a chain using the *pipe operator* ``|>`` as long as they
are unary functions (taking a single argument). Various possibilities of using this operator are explained
in the syntax reference for this operator.
.. note::
**Order of evaluation:**

View File

@ -526,29 +526,6 @@ containment check: ``in``
txt.print("email address seems ok")
}
pipe: ``|>``
Used as an alternative to nesting function calls. The pipe operator is used to 'pipe' the value
into the next function. You write a pipe as a sequence of function calls. You don't write
the arguments to the functions though: the value of one segment in the pipe, will be used as the argument
for the next function call in the sequence.
*note:* It only works on unary functions (taking a single argument) for now.
For example, this: ``txt.print_uw(add_bonus(determine_score(get_player(1))))``
can be rewritten as::
get_player(1)
|> determine_score()
|> add_bonus()
|> txt.print_uw()
A pipe can also be written as an expression that returns a value, for example ``uword score = add_bonus(determine_score(get_player(1)))`` ::
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.
It results in the memory address (UWORD) of that string or array in memory: ``uword a = &stringvar``

View File

@ -3,7 +3,6 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- pipe operator: (targets other than 'Virtual'): allow non-unary function calls in the pipe that specify the other argument(s) in the calls. Already working for VM target.
- add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value?
...
@ -73,9 +72,6 @@ Optimizations:
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once (if?) 6502-codegen is no longer done from
the old CompilerAst, those checks should probably be removed, or be made permanent
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served
- translateUnaryFunctioncall() in BuiltinFunctionsAsmGen: should be able to assign parameters to a builtin function directly from register(s), this will make the use of a builtin function in a pipe expression more efficient without using a temporary variable
compare ``aa = startvalue(1) |> sin8u() |> cos8u() |> sin8u() |> cos8u()``
versus: ``aa = cos8u(sin8u(cos8u(sin8u(startvalue(1)))))`` the second one contains no sta cx16.r9L in between.
- AssignmentAsmGen.assignExpression() -> improve code gen for assigning boolean comparison expressions
Check what the vm target does here, maybe just do this as part of the vm -> 6502 codegen.
- when a for loop's loopvariable isn't referenced in the body, and the iterations are known, replace the loop by a repeatloop

View File

@ -42,8 +42,10 @@ main {
; 9+10+42 = 61
; 200-61-2 = 137
ubyte result = 9 |> func1(10) |> func2(2)
txt.print_ub(result)
; ubyte result = 9 |> func1(10) |> func2(2)
; $090a $0a02
uword resultw = 9 |> mkword(10) |> lsb() |> mkword(2)
txt.print_uw(resultw)
txt.nl()
; a "pixelshader":

View File

@ -55,10 +55,6 @@ ARRAYSIG :
'[]'
;
PIPE :
'|>'
;
cpuregister: 'A' | 'X' | 'Y';
register: 'A' | 'X' | 'Y' | 'AX' | 'AY' | 'XY' | 'Pc' | 'Pz' | 'Pn' | 'Pv' | 'R0' | 'R1' | 'R2' | 'R3' | 'R4' | 'R5' | 'R6' | 'R7' | 'R8' | 'R9' | 'R10' | 'R11' | 'R12' | 'R13' | 'R14' | 'R15';
@ -100,7 +96,6 @@ statement :
| whenstmt
| breakstmt
| labeldef
| pipestmt
;
@ -183,7 +178,6 @@ expression :
| directmemory
| addressof
| expression typecast
| expression pipesegment+
;
typecast : 'as' datatype;
@ -207,10 +201,6 @@ returnstmt : 'return' expression? ;
breakstmt : 'break';
pipestmt : expression pipesegment+;
pipesegment : EOL? PIPE functioncall ;
identifier : NAME ;
scoped_identifier : NAME ('.' NAME)* ;