mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
Pipe expression "|>" removed from the language
This commit is contained in:
parent
dca092fd7c
commit
5a756aaed9
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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>()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
})
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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--
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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).
|
||||
|
@ -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:**
|
||||
|
||||
|
@ -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``
|
||||
|
@ -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
|
||||
|
@ -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":
|
||||
|
@ -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)* ;
|
||||
|
Loading…
x
Reference in New Issue
Block a user