mirror of
https://github.com/KarolS/millfork.git
synced 2024-12-22 16:31:02 +00:00
Enumeration types. Stricter type checks.
This commit is contained in:
parent
ff93775cbe
commit
c4c1bf00f2
@ -10,6 +10,8 @@
|
||||
|
||||
* Added aliases.
|
||||
|
||||
* Added enumeration types.
|
||||
|
||||
* Added preprocessor
|
||||
|
||||
* Automatic selection of text encoding based on target platform.
|
||||
|
@ -43,11 +43,13 @@ Note that you cannot mix `+'` and `-'` with `+` and `-`.
|
||||
|
||||
In the descriptions below, arguments to the operators are explained as follows:
|
||||
|
||||
* `byte` means any one-byte type
|
||||
* `enum` means any enumeration type
|
||||
|
||||
* `word` means any two-byte type, or a byte expanded to a word
|
||||
* `byte` means any numeric one-byte type
|
||||
|
||||
* `long` means any type longer than two bytes, or a shorter type expanded to such length to match the other argument
|
||||
* `word` means any numeric two-byte type, or a byte expanded to a word; `pointer` is considered to be numeric
|
||||
|
||||
* `long` means any numeric type longer than two bytes, or a shorter type expanded to such length to match the other argument
|
||||
|
||||
* `constant` means a compile-time constant
|
||||
|
||||
@ -128,11 +130,13 @@ Note you cannot mix those operators, so `a <= b < c` is not valid.
|
||||
(the current implementation calls it twice, but do not rely on this behaviour).
|
||||
|
||||
* `==`: equality
|
||||
`enum == enum`
|
||||
`byte == byte`
|
||||
`simple word == simple word`
|
||||
`simple long == simple long`
|
||||
|
||||
* `!=`: inequality
|
||||
`enum != enum`
|
||||
`byte != byte`
|
||||
`simple word != simple word`
|
||||
`simple long != simple long`
|
||||
@ -152,6 +156,7 @@ and fail to compile otherwise. This will be changed in the future.
|
||||
An expression of form `a[f()] += b` may call `f` an undefined number of times.
|
||||
|
||||
* `=`: normal assignment
|
||||
`mutable enum = enum`
|
||||
`mutable byte = byte`
|
||||
`mutable word = word`
|
||||
`mutable long = long`
|
||||
@ -189,13 +194,20 @@ While Millfork does not consider indexing an operator, this is a place as good a
|
||||
|
||||
An expression of form `a[i]`, where `i` is an expression of type `byte`, is:
|
||||
|
||||
* when `a` is an array: an access to the `i`-th element of the array `a`
|
||||
* when `a` is an array that has numeric index type: an access to the `i`-th element of the array `a`
|
||||
|
||||
* when `a` is a pointer variable: an access to the byte in memory at address `a + i`
|
||||
|
||||
Those expressions are of type `byte`. If `a` is any other kind of expression, `a[i]` is invalid.
|
||||
|
||||
If the zeropage register is enabled, `i` can also be of type `word`.
|
||||
If the zeropage register is enabled, `i` can also be of type `word`.
|
||||
|
||||
An expression of form `a[i]`, where `i` is an expression of a enumeration type, is:
|
||||
|
||||
* when `a` is an array that has index type equal to the type of `i`:
|
||||
an access to the element of the array `a` at the location assigned to the key `i`
|
||||
|
||||
* otherwise: a compile error
|
||||
|
||||
## Built-in functions
|
||||
|
||||
@ -212,6 +224,8 @@ Other kinds of expressions than the above (even `nonet(byte + byte + byte)`) wil
|
||||
* `hi`, `lo`: most/least significant byte of a word
|
||||
`hi(word)`
|
||||
|
||||
Furthermore, any type that can be assigned to a variable
|
||||
can be used to convert from one type to another of the same size.
|
||||
|
||||
|
||||
|
||||
|
@ -94,6 +94,12 @@ Syntax:
|
||||
then defaults to `default_code_segment` as defined for the platform if the array has initial values,
|
||||
or to `default` if it doesn't.
|
||||
|
||||
* `<size>`: either a constant number, which then defines the size of the array,
|
||||
or a name of a plain enumeration type, in which case changes the type of the index to that enumeration
|
||||
and declares the array size to be equal to the number of variants in that enumeration.
|
||||
If the size is not specified here, then it's deduced from the `<initial_values>`.
|
||||
If the declared size and the size deduced from the `<initial_values>` don't match, then an error is raised.
|
||||
|
||||
TODO
|
||||
|
||||
### Function declarations
|
||||
|
@ -41,3 +41,33 @@ TODO
|
||||
## Special types
|
||||
|
||||
* `void` – a unit type containing no information, can be only used as a return type for a function.
|
||||
|
||||
## Enumerations
|
||||
|
||||
Enumeration is a 1-byte type that represents a set of values:
|
||||
|
||||
enum <name> { <variants, separated by commas or newlines> }
|
||||
|
||||
The first variant has value 0. Every next variant has a value increased by 1 compared to a previous one.
|
||||
|
||||
Alternatively, a variant can be given a custom constant value, which will change the sequence.
|
||||
|
||||
If there is at least one variant and no variant is given a custom constant value,
|
||||
then the enumeration is considered _plain_. Plain enumeration types can be used as array keys.
|
||||
For plain enumerations, a constant `<name>.count` is defined,
|
||||
equal to the number of variants in the enumeration.
|
||||
|
||||
Assigment between numeric types and enumerations is not possible without an explicit type cast:
|
||||
|
||||
enum E {}
|
||||
byte b
|
||||
E e
|
||||
e = b // won't compile
|
||||
b = e // won't compile
|
||||
b = byte(e) // ok
|
||||
e = E(b) // ok
|
||||
|
||||
Plain enumerations have their variants equal to `byte(0)` to `byte(<name>.count - 1)`.
|
||||
|
||||
Tip: You can use an enumeration with no variants as a strongly checked alternative byte type,
|
||||
as there are no checks no values when converting bytes to enumeration values and vice versa.
|
||||
|
@ -12,6 +12,15 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
|
||||
def getExpressionType(ctx: CompilationContext, expr: Expression): Type = AbstractExpressionCompiler.getExpressionType(ctx, expr)
|
||||
|
||||
def assertAllArithmetic(ctx: CompilationContext,expressions: List[Expression]) = {
|
||||
for(e <- expressions) {
|
||||
val typ = getExpressionType(ctx, e)
|
||||
if (!typ.isArithmetic) {
|
||||
ErrorReporting.error(s"Cannot perform arithmetic operations on type `$typ`", e.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def lookupFunction(ctx: CompilationContext, f: FunctionCallExpression): MangledFunction = AbstractExpressionCompiler.lookupFunction(ctx, f)
|
||||
|
||||
def assertCompatible(exprType: Type, variableType: Type): Unit = {
|
||||
@ -24,7 +33,8 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
ctx.copy(env = result)
|
||||
}
|
||||
|
||||
def getParamMaxSize(ctx: CompilationContext, params: List[Expression]): Int = {
|
||||
def getArithmeticParamMaxSize(ctx: CompilationContext, params: List[Expression]): Int = {
|
||||
assertAllArithmetic(ctx, params)
|
||||
params.map(expr => getExpressionType(ctx, expr).size).max
|
||||
}
|
||||
|
||||
@ -32,12 +42,19 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
params.map { case (_, expr) => getExpressionType(ctx, expr).size}.max
|
||||
}
|
||||
|
||||
def assertAllBytes(msg: String, ctx: CompilationContext, params: List[Expression]): Unit = {
|
||||
def assertAllArithmeticBytes(msg: String, ctx: CompilationContext, params: List[Expression]): Unit = {
|
||||
assertAllArithmetic(ctx, params)
|
||||
if (params.exists { expr => getExpressionType(ctx, expr).size != 1 }) {
|
||||
ErrorReporting.fatal(msg, params.head.position)
|
||||
}
|
||||
}
|
||||
|
||||
@inline
|
||||
def assertArithmeticBinary(ctx: CompilationContext, params: List[Expression]): (Expression, Expression, Int) = {
|
||||
assertAllArithmetic(ctx, params)
|
||||
assertBinary(ctx, params)
|
||||
}
|
||||
|
||||
def assertBinary(ctx: CompilationContext, params: List[Expression]): (Expression, Expression, Int) = {
|
||||
if (params.length != 2) {
|
||||
ErrorReporting.fatal("sfgdgfsd", None)
|
||||
@ -56,6 +73,11 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
}
|
||||
}
|
||||
|
||||
def assertArithmeticComparison(ctx: CompilationContext, params: List[Expression]): (Int, Boolean) = {
|
||||
assertAllArithmetic(ctx, params)
|
||||
assertComparison(ctx, params)
|
||||
}
|
||||
|
||||
def assertBool(ctx: CompilationContext, fname: String, params: List[Expression], expectedParamCount: Int): Unit = {
|
||||
if (params.length != expectedParamCount) {
|
||||
ErrorReporting.error("Invalid number of parameters for " + fname, params.headOption.flatMap(_.position))
|
||||
@ -78,10 +100,11 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
}
|
||||
}
|
||||
|
||||
def assertAssignmentLike(ctx: CompilationContext, params: List[Expression]): (LhsExpression, Expression, Int) = {
|
||||
def assertArithmeticAssignmentLike(ctx: CompilationContext, params: List[Expression]): (LhsExpression, Expression, Int) = {
|
||||
if (params.length != 2) {
|
||||
ErrorReporting.fatal("sfgdgfsd", None)
|
||||
}
|
||||
assertAllArithmetic(ctx, params)
|
||||
(params.head, params(1)) match {
|
||||
case (l: LhsExpression, r: Expression) =>
|
||||
val lsize = getExpressionType(ctx, l).size
|
||||
@ -115,7 +138,8 @@ object AbstractExpressionCompiler {
|
||||
case HalfWordExpression(param, _) =>
|
||||
getExpressionType(ctx, param)
|
||||
b
|
||||
case IndexedExpression(_, _) => b
|
||||
case IndexedExpression(name, _) =>
|
||||
env.getPointy(name).elementType
|
||||
case SeparateBytesExpression(hi, lo) =>
|
||||
if (getExpressionType(ctx, hi).size > 1) ErrorReporting.error("Hi byte too large", hi.position)
|
||||
if (getExpressionType(ctx, lo).size > 1) ErrorReporting.error("Lo byte too large", lo.position)
|
||||
@ -176,6 +200,29 @@ object AbstractExpressionCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
def checkIndexType(ctx: CompilationContext, pointy: Pointy, index: Expression): Unit = {
|
||||
val indexerType = getExpressionType(ctx, index)
|
||||
if (!indexerType.isAssignableTo(pointy.indexType)) {
|
||||
ErrorReporting.error(s"Invalid type for index${pointy.name.fold("")(" for `" + _ + "`")}: expected `${pointy.indexType}`, got `$indexerType`", index.position)
|
||||
}
|
||||
}
|
||||
|
||||
def checkAssignmentTypeAndGetSourceType(ctx: CompilationContext, source: Expression, target: LhsExpression): Type = {
|
||||
val sourceType = getExpressionType(ctx, source)
|
||||
val targetType = getExpressionType(ctx, target)
|
||||
if (!sourceType.isAssignableTo(targetType)) {
|
||||
ErrorReporting.error(s"Cannot assign `$sourceType` to `$targetType`", target.position.orElse(source.position))
|
||||
}
|
||||
sourceType
|
||||
}
|
||||
|
||||
def checkAssignmentType(ctx: CompilationContext, source: Expression, targetType: Type): Unit = {
|
||||
val sourceType = getExpressionType(ctx, source)
|
||||
if (!sourceType.isAssignableTo(targetType)) {
|
||||
ErrorReporting.error(s"Cannot assign `$sourceType` to `$targetType`", source.position)
|
||||
}
|
||||
}
|
||||
|
||||
def lookupFunction(ctx: CompilationContext, f: FunctionCallExpression): MangledFunction = {
|
||||
val paramsWithTypes = f.expressions.map(x => getExpressionType(ctx, x) -> x)
|
||||
ctx.env.lookupFunction(f.functionName, paramsWithTypes).getOrElse(
|
||||
|
@ -102,15 +102,17 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
def compileForStatement(ctx: CompilationContext, f: ForStatement): List[T] = {
|
||||
// TODO: check sizes
|
||||
// TODO: special faster cases
|
||||
val p = f.position
|
||||
val vex = VariableExpression(f.variable)
|
||||
val one = LiteralExpression(1, 1)
|
||||
val increment = ExpressionStatement(FunctionCallExpression("+=", List(vex, one)))
|
||||
val decrement = ExpressionStatement(FunctionCallExpression("-=", List(vex, one)))
|
||||
val one = LiteralExpression(1, 1).pos(p)
|
||||
val increment = ExpressionStatement(FunctionCallExpression("+=", List(vex, one)).pos(p)).pos(p)
|
||||
val decrement = ExpressionStatement(FunctionCallExpression("-=", List(vex, one)).pos(p)).pos(p)
|
||||
val names = Set("", "for", f.variable)
|
||||
|
||||
val startEvaluated = ctx.env.eval(f.start)
|
||||
val endEvaluated = ctx.env.eval(f.end)
|
||||
ctx.env.maybeGet[Variable](f.variable).foreach{ v=>
|
||||
val variable = ctx.env.maybeGet[Variable](f.variable)
|
||||
variable.foreach{ v=>
|
||||
startEvaluated.foreach(value => if (!value.quickSimplify.fitsInto(v.typ)) {
|
||||
ErrorReporting.error(s"Variable `${f.variable}` is too small to hold the initial value in the for loop", f.position)
|
||||
})
|
||||
@ -129,45 +131,80 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
|
||||
case (ForDirection.Until | ForDirection.ParallelUntil, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s == e - 1 =>
|
||||
val end = nextLabel("of")
|
||||
compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, f.start) :: f.body) ++ labelChunk(end)
|
||||
compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, f.start).pos(p) :: f.body) ++ labelChunk(end)
|
||||
case (ForDirection.Until | ForDirection.ParallelUntil, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s >= e =>
|
||||
Nil
|
||||
|
||||
case (ForDirection.To | ForDirection.ParallelTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s == e =>
|
||||
val end = nextLabel("of")
|
||||
compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, f.start) :: f.body) ++ labelChunk(end)
|
||||
compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, f.start).pos(p) :: f.body) ++ labelChunk(end)
|
||||
case (ForDirection.To | ForDirection.ParallelTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s > e =>
|
||||
Nil
|
||||
|
||||
case (ForDirection.ParallelUntil, Some(NumericConstant(0, ssize)), Some(NumericConstant(e, _))) if e > 0 =>
|
||||
case (ForDirection.Until | ForDirection.ParallelUntil, Some(c), Some(NumericConstant(256, _)))
|
||||
if variable.map(_.typ.size).contains(1) && c.requiredSize == 1 && c.isProvablyNonnegative =>
|
||||
// LDX #s
|
||||
// loop:
|
||||
// stuff
|
||||
// INX
|
||||
// BNE loop
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.start).pos(p),
|
||||
DoWhileStatement(f.body, List(increment), FunctionCallExpression("!=", List(vex, LiteralExpression(0, 1).pos(p))), names).pos(p)
|
||||
))
|
||||
|
||||
case (ForDirection.ParallelUntil, Some(NumericConstant(0, _)), Some(NumericConstant(e, _))) if e > 0 =>
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.end),
|
||||
DoWhileStatement(Nil, decrement :: f.body, FunctionCallExpression("!=", List(vex, f.start)), names)
|
||||
DoWhileStatement(Nil, decrement :: f.body, FunctionCallExpression("!=", List(vex, f.start)), names).pos(p)
|
||||
))
|
||||
|
||||
case (ForDirection.DownTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, esize))) if s == e =>
|
||||
val end = nextLabel("of")
|
||||
compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, LiteralExpression(s, ssize)) :: f.body) ++ labelChunk(end)
|
||||
compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, LiteralExpression(s, ssize)).pos(p) :: f.body) ++ labelChunk(end)
|
||||
case (ForDirection.DownTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, esize))) if s < e =>
|
||||
Nil
|
||||
case (ForDirection.DownTo, Some(NumericConstant(s, 1)), Some(NumericConstant(0, _))) if s > 0 =>
|
||||
compile(ctx, List(
|
||||
Assignment(vex, FunctionCallExpression("lo", List(SumExpression(List(false -> f.start, false -> LiteralExpression(1, 2)), decimal = false)))),
|
||||
DoWhileStatement(decrement :: f.body, Nil, FunctionCallExpression("!=", List(vex, f.end)), names)
|
||||
Assignment(
|
||||
vex,
|
||||
FunctionCallExpression("lo", List(
|
||||
SumExpression(List(
|
||||
false -> f.start,
|
||||
false -> LiteralExpression(1, 2).pos(p)),
|
||||
decimal = false
|
||||
).pos(p)
|
||||
)).pos(p)
|
||||
).pos(p),
|
||||
DoWhileStatement(
|
||||
decrement :: f.body,
|
||||
Nil,
|
||||
FunctionCallExpression("!=", List(vex, f.end)).pos(p), names).pos(p)
|
||||
))
|
||||
case (ForDirection.DownTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(0, _))) if s > 0 =>
|
||||
compile(ctx, List(
|
||||
Assignment(vex, SumExpression(List(false -> f.start, false -> LiteralExpression(1, 1)), decimal = false)),
|
||||
DoWhileStatement(decrement :: f.body, Nil, FunctionCallExpression("!=", List(vex, f.end)), names)
|
||||
Assignment(
|
||||
vex,
|
||||
SumExpression(
|
||||
List(false -> f.start, false -> LiteralExpression(1, 1).pos(p)),
|
||||
decimal = false
|
||||
).pos(p)
|
||||
).pos(p),
|
||||
DoWhileStatement(
|
||||
decrement :: f.body,
|
||||
Nil,
|
||||
FunctionCallExpression("!=", List(vex, f.end)).pos(p),
|
||||
names
|
||||
).pos(p)
|
||||
))
|
||||
|
||||
|
||||
case (ForDirection.Until | ForDirection.ParallelUntil, _, _) =>
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.start),
|
||||
Assignment(vex, f.start).pos(p),
|
||||
WhileStatement(
|
||||
FunctionCallExpression("<", List(vex, f.end)),
|
||||
f.body, List(increment), names),
|
||||
FunctionCallExpression("<", List(vex, f.end)).pos(p),
|
||||
f.body, List(increment), names).pos(p)
|
||||
))
|
||||
// case (ForDirection.To | ForDirection.ParallelTo, _, Some(NumericConstant(n, _))) if n > 0 && n < 255 =>
|
||||
// compile(ctx, List(
|
||||
@ -178,28 +215,28 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
// ))
|
||||
case (ForDirection.To | ForDirection.ParallelTo, _, _) =>
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.start),
|
||||
Assignment(vex, f.start).pos(p),
|
||||
WhileStatement(
|
||||
VariableExpression("true"),
|
||||
VariableExpression("true").pos(p),
|
||||
f.body,
|
||||
List(IfStatement(
|
||||
FunctionCallExpression("==", List(vex, f.end)),
|
||||
List(BreakStatement(f.variable)),
|
||||
FunctionCallExpression("==", List(vex, f.end)).pos(p),
|
||||
List(BreakStatement(f.variable).pos(p)),
|
||||
List(increment)
|
||||
)),
|
||||
names),
|
||||
names)
|
||||
))
|
||||
case (ForDirection.DownTo, _, _) =>
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.start),
|
||||
Assignment(vex, f.start).pos(p),
|
||||
IfStatement(
|
||||
FunctionCallExpression(">=", List(vex, f.end)),
|
||||
FunctionCallExpression(">=", List(vex, f.end)).pos(p),
|
||||
List(DoWhileStatement(
|
||||
f.body,
|
||||
List(decrement),
|
||||
FunctionCallExpression("!=", List(vex, f.end)),
|
||||
FunctionCallExpression("!=", List(vex, f.end)).pos(p),
|
||||
names
|
||||
)),
|
||||
).pos(p)),
|
||||
Nil)
|
||||
))
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ object BuiltIns {
|
||||
Nil -> AssemblyLine.variable(ctx, opcode, v)
|
||||
case IndexedExpression(arrayName, index) =>
|
||||
val pointy = env.getPointy(arrayName)
|
||||
AbstractExpressionCompiler.checkIndexType(ctx, pointy, index)
|
||||
val (variablePart, constantPart) = env.evalVariableAndConstantSubParts(index)
|
||||
val indexerSize = variablePart.map(v => getIndexerSize(ctx, v)).getOrElse(1)
|
||||
val totalIndexSize = getIndexerSize(ctx, index)
|
||||
@ -57,11 +58,11 @@ object BuiltIns {
|
||||
case (p: ConstantPointy, _, _, _, None) =>
|
||||
Nil -> List(AssemblyLine.absolute(opcode, p.value + constantPart))
|
||||
case (p: ConstantPointy, _, 1, IndexChoice.RequireX | IndexChoice.PreferX, Some(v)) =>
|
||||
MosExpressionCompiler.compile(ctx, v, Some(b -> RegisterVariable(MosRegister.X, b)), NoBranching) -> List(AssemblyLine.absoluteX(opcode, p.value + constantPart))
|
||||
MosExpressionCompiler.compile(ctx, v, Some(b -> RegisterVariable(MosRegister.X, pointy.indexType)), NoBranching) -> List(AssemblyLine.absoluteX(opcode, p.value + constantPart))
|
||||
case (p: ConstantPointy, _, 1, IndexChoice.PreferY, Some(v)) =>
|
||||
MosExpressionCompiler.compile(ctx, v, Some(b -> RegisterVariable(MosRegister.Y, b)), NoBranching) -> List(AssemblyLine.absoluteY(opcode, p.value + constantPart))
|
||||
MosExpressionCompiler.compile(ctx, v, Some(b -> RegisterVariable(MosRegister.Y, pointy.indexType)), NoBranching) -> List(AssemblyLine.absoluteY(opcode, p.value + constantPart))
|
||||
case (p: VariablePointy, 0 | 1, _, IndexChoice.PreferX | IndexChoice.PreferY, _) =>
|
||||
MosExpressionCompiler.compile(ctx, index, Some(b -> RegisterVariable(MosRegister.Y, b)), NoBranching) -> List(AssemblyLine.indexedY(opcode, p.addr))
|
||||
MosExpressionCompiler.compile(ctx, index, Some(b -> RegisterVariable(MosRegister.Y, pointy.indexType)), NoBranching) -> List(AssemblyLine.indexedY(opcode, p.addr))
|
||||
case (p: ConstantPointy, _, 2, IndexChoice.PreferX | IndexChoice.PreferY, Some(v)) =>
|
||||
MosExpressionCompiler.prepareWordIndexing(ctx, p, index) -> List(AssemblyLine.indexedY(opcode, env.get[VariableInMemory]("__reg")))
|
||||
case (p: VariablePointy, 2, _, IndexChoice.PreferX | IndexChoice.PreferY, _) =>
|
||||
@ -157,6 +158,14 @@ object BuiltIns {
|
||||
case None => expr match {
|
||||
case VariableExpression(_) => 'V'
|
||||
case IndexedExpression(_, LiteralExpression(_, _)) => 'K'
|
||||
case IndexedExpression(_, expr@VariableExpression(v)) =>
|
||||
env.eval(expr) match {
|
||||
case Some(_) => 'K'
|
||||
case None => env.get[Variable](v).typ.size match {
|
||||
case 1 => 'J'
|
||||
case _ => 'I'
|
||||
}
|
||||
}
|
||||
case IndexedExpression(_, VariableExpression(v)) if env.get[Variable](v).typ.size == 1 => 'J'
|
||||
case IndexedExpression(_, _) => 'I'
|
||||
case _ => 'A'
|
||||
@ -760,8 +769,8 @@ object BuiltIns {
|
||||
}
|
||||
}
|
||||
|
||||
private def getIndexerSize(ctx: CompilationContext, indexExpr: Expression) = {
|
||||
ctx.env.evalVariableAndConstantSubParts(indexExpr)._1.map(v => MosExpressionCompiler.getExpressionType(ctx, v)).size
|
||||
private def getIndexerSize(ctx: CompilationContext, indexExpr: Expression): Int = {
|
||||
ctx.env.evalVariableAndConstantSubParts(indexExpr)._1.map(v => MosExpressionCompiler.getExpressionType(ctx, v).size).sum
|
||||
}
|
||||
|
||||
def compileInPlaceWordOrLongAddition(ctx: CompilationContext, lhs: LhsExpression, addend: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = {
|
||||
|
@ -143,14 +143,14 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
val reg = ctx.env.get[VariableInMemory]("__reg")
|
||||
val compileIndex = compile(ctx, indexExpression, Some(MosExpressionCompiler.getExpressionType(ctx, indexExpression) -> RegisterVariable(MosRegister.YA, w)), BranchSpec.None)
|
||||
val prepareRegister = pointy match {
|
||||
case ConstantPointy(addr, _) =>
|
||||
case ConstantPointy(addr, _, _, _, _) =>
|
||||
List(
|
||||
AssemblyLine.implied(CLC),
|
||||
AssemblyLine.immediate(ADC, addr.hiByte),
|
||||
AssemblyLine.zeropage(STA, reg, 1),
|
||||
AssemblyLine.immediate(LDA, addr.loByte),
|
||||
AssemblyLine.zeropage(STA, reg))
|
||||
case VariablePointy(addr) =>
|
||||
case VariablePointy(addr, _, _) =>
|
||||
List(
|
||||
AssemblyLine.implied(CLC),
|
||||
AssemblyLine.zeropage(ADC, addr + 1),
|
||||
@ -257,7 +257,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
case (p: VariablePointy, _, _, 2) =>
|
||||
wrapWordIndexingStorage(prepareWordIndexing(ctx, p, indexExpr))
|
||||
case (p: ConstantPointy, Some(v), 2, _) =>
|
||||
wrapWordIndexingStorage(prepareWordIndexing(ctx, ConstantPointy(p.value + constIndex, if (constIndex.isProvablyZero) p.size else None), v))
|
||||
val w = env.get[VariableType]("word")
|
||||
wrapWordIndexingStorage(prepareWordIndexing(ctx, ConstantPointy(p.value + constIndex, None, if (constIndex.isProvablyZero) p.size else None, w, p.elementType), v))
|
||||
case (p: ConstantPointy, Some(v), 1, _) =>
|
||||
storeToArrayAtUnknownIndex(v, p.value)
|
||||
//TODO: should there be a type check or a zeropage check?
|
||||
@ -499,6 +500,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
case IndexedExpression(arrayName, indexExpr) =>
|
||||
val pointy = env.getPointy(arrayName)
|
||||
AbstractExpressionCompiler.checkIndexType(ctx, pointy, indexExpr)
|
||||
// TODO: check
|
||||
val (variableIndex, constantIndex) = env.evalVariableAndConstantSubParts(indexExpr)
|
||||
val variableIndexSize = variableIndex.map(v => getExpressionType(ctx, v).size).getOrElse(0)
|
||||
@ -559,7 +561,12 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
case (a: ConstantPointy, Some(v), _, 1) =>
|
||||
loadFromArrayAtUnknownIndex(v, a.value)
|
||||
case (a: ConstantPointy, Some(v), _, 2) =>
|
||||
prepareWordIndexing(ctx, ConstantPointy(a.value + constantIndex, if (constantIndex.isProvablyZero) a.size else None), v) ++ loadFromReg()
|
||||
prepareWordIndexing(ctx, ConstantPointy(
|
||||
a.value + constantIndex,
|
||||
None,
|
||||
if (constantIndex.isProvablyZero) a.size else None,
|
||||
env.get[VariableType]("word"),
|
||||
a.elementType), v) ++ loadFromReg()
|
||||
case (a: VariablePointy, _, 2, _) =>
|
||||
prepareWordIndexing(ctx, a, indexExpr) ++ loadFromReg()
|
||||
case (p:VariablePointy, None, 0 | 1, _) =>
|
||||
@ -592,6 +599,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case SumExpression(params, decimal) =>
|
||||
assertAllArithmetic(ctx, params.map(_._2))
|
||||
val a = params.map{case (n, p) => env.eval(p).map(n -> _)}
|
||||
if (a.forall(_.isDefined)) {
|
||||
val value = a.foldLeft(Constant.Zero){(c, pair) =>
|
||||
@ -693,7 +701,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
ErrorReporting.error("Invalid number of parameters", f.position)
|
||||
Nil
|
||||
} else {
|
||||
assertAllBytes("Nonet argument has to be a byte", ctx, params)
|
||||
assertAllArithmeticBytes("Nonet argument has to be a byte", ctx, params)
|
||||
params.head match {
|
||||
case SumExpression(addends, _) =>
|
||||
if (addends.exists(a => !a._1)) {
|
||||
@ -738,7 +746,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
case "^^" => ???
|
||||
case "&" =>
|
||||
getParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 =>
|
||||
zeroExtend = true
|
||||
BuiltIns.compileBitOps(AND, ctx, params)
|
||||
@ -746,31 +754,31 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
case "*" =>
|
||||
zeroExtend = true
|
||||
assertAllBytes("Long multiplication not supported", ctx, params)
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
BuiltIns.compileByteMultiplication(ctx, params)
|
||||
case "|" =>
|
||||
getParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 =>
|
||||
zeroExtend = true
|
||||
BuiltIns.compileBitOps(ORA, ctx, params)
|
||||
case 2 => PseudoregisterBuiltIns.compileWordBitOpsToAX(ctx, params, ORA)
|
||||
}
|
||||
case "^" =>
|
||||
getParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 =>
|
||||
zeroExtend = true
|
||||
BuiltIns.compileBitOps(EOR, ctx, params)
|
||||
case 2 => PseudoregisterBuiltIns.compileWordBitOpsToAX(ctx, params, EOR)
|
||||
}
|
||||
case ">>>>" =>
|
||||
val (l, r, 2) = assertBinary(ctx, params)
|
||||
val (l, r, 2) = assertArithmeticBinary(ctx, params)
|
||||
l match {
|
||||
case v: LhsExpression =>
|
||||
zeroExtend = true
|
||||
BuiltIns.compileNonetOps(ctx, v, r)
|
||||
}
|
||||
case "<<" =>
|
||||
val (l, r, size) = assertBinary(ctx, params)
|
||||
val (l, r, size) = assertArithmeticBinary(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
zeroExtend = true
|
||||
@ -782,7 +790,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
Nil
|
||||
}
|
||||
case ">>" =>
|
||||
val (l, r, size) = assertBinary(ctx, params)
|
||||
val (l, r, size) = assertArithmeticBinary(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
zeroExtend = true
|
||||
@ -795,17 +803,17 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
case "<<'" =>
|
||||
zeroExtend = true
|
||||
assertAllBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertBinary(ctx, params)
|
||||
assertAllArithmeticBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticBinary(ctx, params)
|
||||
DecimalBuiltIns.compileByteShiftLeft(ctx, l, r, rotate = false)
|
||||
case ">>'" =>
|
||||
zeroExtend = true
|
||||
assertAllBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertBinary(ctx, params)
|
||||
assertAllArithmeticBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticBinary(ctx, params)
|
||||
DecimalBuiltIns.compileByteShiftRight(ctx, l, r, rotate = false)
|
||||
case "<" =>
|
||||
// TODO: signed
|
||||
val (size, signed) = assertComparison(ctx, params)
|
||||
val (size, signed) = assertArithmeticComparison(ctx, params)
|
||||
compileTransitiveRelation(ctx, "<", params, exprTypeAndVariable, branches) { (l, r) =>
|
||||
size match {
|
||||
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.LessSigned else ComparisonType.LessUnsigned, l, r, branches)
|
||||
@ -815,7 +823,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
case ">=" =>
|
||||
// TODO: signed
|
||||
val (size, signed) = assertComparison(ctx, params)
|
||||
val (size, signed) = assertArithmeticComparison(ctx, params)
|
||||
compileTransitiveRelation(ctx, ">=", params, exprTypeAndVariable, branches) { (l, r) =>
|
||||
size match {
|
||||
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.GreaterOrEqualSigned else ComparisonType.GreaterOrEqualUnsigned, l, r, branches)
|
||||
@ -825,7 +833,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
case ">" =>
|
||||
// TODO: signed
|
||||
val (size, signed) = assertComparison(ctx, params)
|
||||
val (size, signed) = assertArithmeticComparison(ctx, params)
|
||||
compileTransitiveRelation(ctx, ">", params, exprTypeAndVariable, branches) { (l, r) =>
|
||||
size match {
|
||||
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.GreaterSigned else ComparisonType.GreaterUnsigned, l, r, branches)
|
||||
@ -835,7 +843,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
case "<=" =>
|
||||
// TODO: signed
|
||||
val (size, signed) = assertComparison(ctx, params)
|
||||
val (size, signed) = assertArithmeticComparison(ctx, params)
|
||||
compileTransitiveRelation(ctx, "<=", params, exprTypeAndVariable, branches) { (l, r) =>
|
||||
size match {
|
||||
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, branches)
|
||||
@ -860,7 +868,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
case _ => BuiltIns.compileLongComparison(ctx, ComparisonType.NotEqual, l, r, size, branches)
|
||||
}
|
||||
case "+=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteAddition(ctx, l, r, subtract = false, decimal = false)
|
||||
@ -876,7 +884,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case "-=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteAddition(ctx, l, r, subtract = true, decimal = false)
|
||||
@ -892,7 +900,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case "+'=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteAddition(ctx, l, r, subtract = false, decimal = true)
|
||||
@ -908,7 +916,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case "-'=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteAddition(ctx, l, r, subtract = true, decimal = true)
|
||||
@ -924,7 +932,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case "<<=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteShiftOps(ASL, ctx, l, r)
|
||||
@ -935,7 +943,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case ">>=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteShiftOps(LSR, ctx, l, r)
|
||||
@ -946,7 +954,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case "<<'=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
DecimalBuiltIns.compileByteShiftLeft(ctx, l, r, rotate = false) ++ compileByteStorage(ctx, MosRegister.A, l)
|
||||
@ -957,7 +965,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case ">>'=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
DecimalBuiltIns.compileByteShiftRight(ctx, l, r, rotate = false) ++ compileByteStorage(ctx, MosRegister.A, l)
|
||||
@ -968,15 +976,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case "*=" =>
|
||||
assertAllBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertAssignmentLike(ctx, params)
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
|
||||
BuiltIns.compileInPlaceByteMultiplication(ctx, l, r)
|
||||
case "*'=" =>
|
||||
assertAllBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertAssignmentLike(ctx, params)
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
|
||||
DecimalBuiltIns.compileInPlaceByteMultiplication(ctx, l, r)
|
||||
case "&=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteBitOp(ctx, l, r, AND)
|
||||
@ -987,7 +995,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case "^=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteBitOp(ctx, l, r, EOR)
|
||||
@ -998,7 +1006,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case "|=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 =>
|
||||
BuiltIns.compileInPlaceByteBitOp(ctx, l, r, ORA)
|
||||
@ -1241,13 +1249,14 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
|
||||
def compileAssignment(ctx: CompilationContext, source: Expression, target: LhsExpression): List[AssemblyLine] = {
|
||||
val env = ctx.env
|
||||
val sourceType = AbstractExpressionCompiler.checkAssignmentTypeAndGetSourceType(ctx, source, target)
|
||||
val b = env.get[Type]("byte")
|
||||
val w = env.get[Type]("word")
|
||||
target match {
|
||||
case VariableExpression(name) =>
|
||||
val v = env.get[Variable](name, target.position)
|
||||
// TODO check v.typ
|
||||
compile(ctx, source, Some((getExpressionType(ctx, source), v)), NoBranching)
|
||||
compile(ctx, source, Some((sourceType, v)), NoBranching)
|
||||
case SeparateBytesExpression(h: LhsExpression, l: LhsExpression) =>
|
||||
compile(ctx, source, Some(w, RegisterVariable(MosRegister.AX, w)), NoBranching) ++
|
||||
compileByteStorage(ctx, MosRegister.A, l) ++ compileByteStorage(ctx, MosRegister.X, h)
|
||||
@ -1261,10 +1270,12 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
|
||||
def arrayBoundsCheck(ctx: CompilationContext, pointy: Pointy, register: MosRegister.Value, index: Expression): List[AssemblyLine] = {
|
||||
if (!ctx.options.flags(CompilationFlag.CheckIndexOutOfBounds)) return Nil
|
||||
val arrayLength = pointy match {
|
||||
val arrayLength:Int = pointy match {
|
||||
case _: VariablePointy => return Nil
|
||||
case ConstantPointy(_, None) => return Nil
|
||||
case ConstantPointy(_, Some(s)) => s
|
||||
case p: ConstantPointy => p.size match {
|
||||
case None => return Nil
|
||||
case Some(s) => s
|
||||
}
|
||||
}
|
||||
ctx.env.eval(index) match {
|
||||
case Some(NumericConstant(i, _)) =>
|
||||
|
@ -121,19 +121,20 @@ object MosReturnDispatch {
|
||||
else actualMax
|
||||
}
|
||||
|
||||
val b = ctx.env.get[VariableType]("byte")
|
||||
|
||||
while (env.parent.isDefined) env = env.parent.get
|
||||
val label = MosCompiler.nextLabel("di")
|
||||
val paramArrays = stmt.params.indices.map { ix =>
|
||||
val a = InitializedArray(label + "$" + ix + ".array", None, (paramMins(ix) to paramMaxes(ix)).map { key =>
|
||||
map(key)._2.lift(ix).getOrElse(LiteralExpression(0, 1))
|
||||
}.toList,
|
||||
ctx.function.declaredBank)
|
||||
ctx.function.declaredBank, b, b)
|
||||
env.registerUnnamedArray(a)
|
||||
a
|
||||
}
|
||||
|
||||
val useJmpaix = ctx.options.flag(CompilationFlag.EmitCmosOpcodes) && !ctx.options.flag(CompilationFlag.LUnixRelocatableCode) && (actualMax - actualMin) <= 127
|
||||
val b = ctx.env.get[Type]("byte")
|
||||
|
||||
import AddrMode._
|
||||
import millfork.assembly.mos.Opcode._
|
||||
@ -147,7 +148,7 @@ object MosReturnDispatch {
|
||||
}
|
||||
|
||||
if (useJmpaix) {
|
||||
val jumpTable = InitializedArray(label + "$jt.array", None, (actualMin to actualMax).flatMap(i => List(lobyte0(map(i)._1), hibyte0(map(i)._1))).toList, ctx.function.declaredBank)
|
||||
val jumpTable = InitializedArray(label + "$jt.array", None, (actualMin to actualMax).flatMap(i => List(lobyte0(map(i)._1), hibyte0(map(i)._1))).toList, ctx.function.declaredBank, b, b)
|
||||
env.registerUnnamedArray(jumpTable)
|
||||
if (copyParams.isEmpty) {
|
||||
val loadIndex = MosExpressionCompiler.compile(ctx, stmt.indexer, Some(b -> RegisterVariable(MosRegister.A, b)), BranchSpec.None)
|
||||
@ -162,8 +163,8 @@ object MosReturnDispatch {
|
||||
}
|
||||
} else {
|
||||
val loadIndex = MosExpressionCompiler.compile(ctx, stmt.indexer, Some(b -> RegisterVariable(MosRegister.X, b)), BranchSpec.None)
|
||||
val jumpTableLo = InitializedArray(label + "$jl.array", None, (actualMin to actualMax).map(i => lobyte1(map(i)._1)).toList, ctx.function.declaredBank)
|
||||
val jumpTableHi = InitializedArray(label + "$jh.array", None, (actualMin to actualMax).map(i => hibyte1(map(i)._1)).toList, ctx.function.declaredBank)
|
||||
val jumpTableLo = InitializedArray(label + "$jl.array", None, (actualMin to actualMax).map(i => lobyte1(map(i)._1)).toList, ctx.function.declaredBank, b, b)
|
||||
val jumpTableHi = InitializedArray(label + "$jh.array", None, (actualMin to actualMax).map(i => hibyte1(map(i)._1)).toList, ctx.function.declaredBank, b, b)
|
||||
env.registerUnnamedArray(jumpTableLo)
|
||||
env.registerUnnamedArray(jumpTableHi)
|
||||
val actualJump = if (ctx.options.flag(CompilationFlag.LUnixRelocatableCode)) {
|
||||
|
@ -223,6 +223,7 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] {
|
||||
MosExpressionCompiler.compile(ctx, e, someRegisterAX, NoBranching) ++ stackPointerFixBeforeReturn(ctx) ++ returnInstructions
|
||||
}
|
||||
case _ =>
|
||||
AbstractExpressionCompiler.checkAssignmentType(ctx, e, m.returnType)
|
||||
m.returnType.size match {
|
||||
case 0 =>
|
||||
ErrorReporting.error("Cannot return anything from a void function", statement.position)
|
||||
|
@ -205,7 +205,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
case code => code ++ loadByteViaHL(target)
|
||||
}
|
||||
case SumExpression(params, decimal) =>
|
||||
getParamMaxSize(ctx, params.map(_._2)) match {
|
||||
getArithmeticParamMaxSize(ctx, params.map(_._2)) match {
|
||||
case 1 => targetifyA(target, ZBuiltIns.compile8BitSum(ctx, params, decimal), isSigned = false)
|
||||
case 2 => targetifyHL(target, ZBuiltIns.compile16BitSum(ctx, params, decimal))
|
||||
}
|
||||
@ -302,50 +302,50 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
case "^^" => ???
|
||||
|
||||
case "&" =>
|
||||
getParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, AND, params), isSigned = false)
|
||||
case 2 => targetifyHL(target, ZBuiltIns.compile16BitOperation(ctx, AND, params))
|
||||
}
|
||||
case "*" =>
|
||||
assertAllBytes("Long multiplication not supported", ctx, params)
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
targetifyA(target, Z80Multiply.compile8BitMultiply(ctx, params), isSigned = false)
|
||||
case "|" =>
|
||||
getParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, OR, params), isSigned = false)
|
||||
case 2 => targetifyHL(target, ZBuiltIns.compile16BitOperation(ctx, OR, params))
|
||||
}
|
||||
case "^" =>
|
||||
getParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, XOR, params), isSigned = false)
|
||||
case 2 => targetifyHL(target, ZBuiltIns.compile16BitOperation(ctx, XOR, params))
|
||||
}
|
||||
case ">>>>" =>
|
||||
val (l, r, 2) = assertBinary(ctx, params)
|
||||
val (l, r, 2) = assertArithmeticBinary(ctx, params)
|
||||
???
|
||||
case "<<" =>
|
||||
val (l, r, size) = assertBinary(ctx, params)
|
||||
val (l, r, size) = assertArithmeticBinary(ctx, params)
|
||||
size match {
|
||||
case 1 => targetifyA(target, Z80Shifting.compile8BitShift(ctx, l, r, left = true), isSigned = false)
|
||||
case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = true)
|
||||
case _ => ???
|
||||
}
|
||||
case ">>" =>
|
||||
val (l, r, size) = assertBinary(ctx, params)
|
||||
val (l, r, size) = assertArithmeticBinary(ctx, params)
|
||||
size match {
|
||||
case 1 => targetifyA(target, Z80Shifting.compile8BitShift(ctx, l, r, left = false), isSigned = false)
|
||||
case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = false)
|
||||
case _ => ???
|
||||
}
|
||||
case "<<'" =>
|
||||
assertAllBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertBinary(ctx, params)
|
||||
assertAllArithmeticBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticBinary(ctx, params)
|
||||
???
|
||||
case ">>'" =>
|
||||
assertAllBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertBinary(ctx, params)
|
||||
assertAllArithmeticBytes("Long shift ops not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticBinary(ctx, params)
|
||||
???
|
||||
case "<" =>
|
||||
val (size, signed) = assertComparison(ctx, params)
|
||||
val (size, signed) = assertArithmeticComparison(ctx, params)
|
||||
compileTransitiveRelation(ctx, "<", params, target, branches) { (l, r) =>
|
||||
size match {
|
||||
case 1 => Z80Comparisons.compile8BitComparison(ctx, if (signed) ComparisonType.LessSigned else ComparisonType.LessUnsigned, l, r, branches)
|
||||
@ -354,7 +354,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
}
|
||||
}
|
||||
case ">=" =>
|
||||
val (size, signed) = assertComparison(ctx, params)
|
||||
val (size, signed) = assertArithmeticComparison(ctx, params)
|
||||
compileTransitiveRelation(ctx, ">=", params, target, branches) { (l, r) =>
|
||||
size match {
|
||||
case 1 => Z80Comparisons.compile8BitComparison(ctx, if (signed) ComparisonType.GreaterOrEqualSigned else ComparisonType.GreaterOrEqualUnsigned, l, r, branches)
|
||||
@ -363,7 +363,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
}
|
||||
}
|
||||
case ">" =>
|
||||
val (size, signed) = assertComparison(ctx, params)
|
||||
val (size, signed) = assertArithmeticComparison(ctx, params)
|
||||
compileTransitiveRelation(ctx, ">", params, target, branches) { (l, r) =>
|
||||
size match {
|
||||
case 1 => Z80Comparisons.compile8BitComparison(ctx, if (signed) ComparisonType.GreaterSigned else ComparisonType.GreaterUnsigned, l, r, branches)
|
||||
@ -372,7 +372,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
}
|
||||
}
|
||||
case "<=" =>
|
||||
val (size, signed) = assertComparison(ctx, params)
|
||||
val (size, signed) = assertArithmeticComparison(ctx, params)
|
||||
compileTransitiveRelation(ctx, "<=", params, target, branches) { (l, r) =>
|
||||
size match {
|
||||
case 1 => Z80Comparisons.compile8BitComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, branches)
|
||||
@ -399,72 +399,72 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
}
|
||||
}
|
||||
case "+=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, ADD)
|
||||
case _ => ZBuiltIns.performLongInPlace(ctx, l, r, ADD, ADC, size)
|
||||
}
|
||||
case "-=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, SUB)
|
||||
case _ => ZBuiltIns.performLongInPlace(ctx, l, r, SUB, SBC, size)
|
||||
}
|
||||
case "+'=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, ADD, decimal = true)
|
||||
case _ => ZBuiltIns.performLongInPlace(ctx, l, r, ADD, ADC, size, decimal = true)
|
||||
}
|
||||
case "-'=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, SUB, decimal = true)
|
||||
case _ => ZBuiltIns.performLongInPlace(ctx, l, r, SUB, SBC, size, decimal = true)
|
||||
}
|
||||
Nil
|
||||
case "<<=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => Z80Shifting.compile8BitShiftInPlace(ctx, l, r, left = true)
|
||||
case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = true) ++ storeHL(ctx, l, signedSource = false)
|
||||
case _ => ???
|
||||
}
|
||||
case ">>=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => Z80Shifting.compile8BitShiftInPlace(ctx, l, r, left = false)
|
||||
case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = false) ++ storeHL(ctx, l, signedSource = false)
|
||||
case _ => ???
|
||||
}
|
||||
case "<<'=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
???
|
||||
case ">>'=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
???
|
||||
case "*=" =>
|
||||
assertAllBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertAssignmentLike(ctx, params)
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
|
||||
Z80Multiply.compile8BitInPlaceMultiply(ctx, l, r)
|
||||
case "*'=" =>
|
||||
assertAllBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertAssignmentLike(ctx, params)
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
|
||||
???
|
||||
case "&=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, AND)
|
||||
case _ => ZBuiltIns.performLongInPlace(ctx, l, r, AND, AND, size)
|
||||
}
|
||||
case "^=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, XOR)
|
||||
case _ => ZBuiltIns.performLongInPlace(ctx, l, r, XOR, XOR, size)
|
||||
}
|
||||
case "|=" =>
|
||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, OR)
|
||||
case _ => ZBuiltIns.performLongInPlace(ctx, l, r, OR, OR, size)
|
||||
@ -584,8 +584,10 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
|
||||
def calculateAddressToHL(ctx: CompilationContext, i: IndexedExpression): List[ZLine] = {
|
||||
val env = ctx.env
|
||||
env.getPointy(i.name) match {
|
||||
case ConstantPointy(baseAddr, _) =>
|
||||
val pointy = env.getPointy(i.name)
|
||||
AbstractExpressionCompiler.checkIndexType(ctx, pointy, i.index)
|
||||
pointy match {
|
||||
case ConstantPointy(baseAddr, _, _, _, _) =>
|
||||
env.evalVariableAndConstantSubParts(i.index) match {
|
||||
case (None, offset) => List(ZLine.ldImm16(ZRegister.HL, (baseAddr + offset).quickSimplify))
|
||||
case (Some(index), offset) =>
|
||||
@ -593,7 +595,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
stashBCIfChanged(compileToHL(ctx, index)) ++
|
||||
List(ZLine.registers(ADD_16, ZRegister.HL, ZRegister.BC))
|
||||
}
|
||||
case VariablePointy(varAddr) =>
|
||||
case VariablePointy(varAddr, _, _) =>
|
||||
compileToHL(ctx, i.index) ++
|
||||
loadBCFromHL ++
|
||||
List(
|
||||
|
@ -50,18 +50,20 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
|
||||
Z80ExpressionCompiler.compileToHL(ctx, e) ++ fixStackOnReturn(ctx) ++
|
||||
List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET))
|
||||
}
|
||||
case t => t.size match {
|
||||
case 0 =>
|
||||
ErrorReporting.error("Cannot return anything from a void function", statement.position)
|
||||
fixStackOnReturn(ctx) ++
|
||||
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET))
|
||||
case 1 =>
|
||||
Z80ExpressionCompiler.compileToA(ctx, e) ++ fixStackOnReturn(ctx) ++
|
||||
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET))
|
||||
case 2 =>
|
||||
Z80ExpressionCompiler.compileToHL(ctx, e) ++ fixStackOnReturn(ctx) ++
|
||||
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET))
|
||||
}
|
||||
case t =>
|
||||
AbstractExpressionCompiler.checkAssignmentType(ctx, e, ctx.function.returnType)
|
||||
t.size match {
|
||||
case 0 =>
|
||||
ErrorReporting.error("Cannot return anything from a void function", statement.position)
|
||||
fixStackOnReturn(ctx) ++
|
||||
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET))
|
||||
case 1 =>
|
||||
Z80ExpressionCompiler.compileToA(ctx, e) ++ fixStackOnReturn(ctx) ++
|
||||
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET))
|
||||
case 2 =>
|
||||
Z80ExpressionCompiler.compileToHL(ctx, e) ++ fixStackOnReturn(ctx) ++
|
||||
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BCDEIX), ZLine.implied(RET))
|
||||
}
|
||||
}
|
||||
case Assignment(destination, source) =>
|
||||
val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source)
|
||||
|
43
src/main/scala/millfork/env/Constant.scala
vendored
43
src/main/scala/millfork/env/Constant.scala
vendored
@ -24,6 +24,7 @@ import millfork.node.Position
|
||||
sealed trait Constant {
|
||||
def isProvablyZero: Boolean = false
|
||||
def isProvably(value: Int): Boolean = false
|
||||
def isProvablyNonnegative: Boolean = false
|
||||
|
||||
def asl(i: Constant): Constant = i match {
|
||||
case NumericConstant(sa, _) => asl(sa.toInt)
|
||||
@ -81,6 +82,7 @@ sealed trait Constant {
|
||||
case class AssertByte(c: Constant) extends Constant {
|
||||
override def isProvablyZero: Boolean = c.isProvablyZero
|
||||
override def isProvably(i: Int): Boolean = c.isProvably(i)
|
||||
override def isProvablyNonnegative: Boolean = c.isProvablyNonnegative
|
||||
|
||||
override def requiredSize: Int = 1
|
||||
|
||||
@ -105,6 +107,7 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant {
|
||||
}
|
||||
override def isProvablyZero: Boolean = value == 0
|
||||
override def isProvably(i: Int): Boolean = value == i
|
||||
override def isProvablyNonnegative: Boolean = value >= 0
|
||||
|
||||
override def isLowestByteAlwaysEqual(i: Int) : Boolean = (value & 0xff) == (i&0xff)
|
||||
|
||||
@ -146,6 +149,9 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant {
|
||||
}
|
||||
|
||||
case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant {
|
||||
|
||||
override def isProvablyNonnegative: Boolean = true
|
||||
|
||||
override def requiredSize = 2
|
||||
|
||||
override def toString: String = thing.name
|
||||
@ -168,6 +174,8 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant {
|
||||
|
||||
override def requiredSize = 1
|
||||
|
||||
override def isProvablyNonnegative: Boolean = true
|
||||
|
||||
override def toString: String = base + (index match {
|
||||
case 0 => ".lo"
|
||||
case 1 => ".hi"
|
||||
@ -185,21 +193,35 @@ object MathOperator extends Enumeration {
|
||||
}
|
||||
|
||||
case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Constant) extends Constant {
|
||||
|
||||
override def isProvablyNonnegative: Boolean = {
|
||||
import MathOperator._
|
||||
operator match {
|
||||
case Plus | DecimalPlus |
|
||||
Times | DecimalTimes |
|
||||
Shl | DecimalShl |
|
||||
Shl9 | DecimalShl9 |
|
||||
Shr | DecimalShr |
|
||||
And | Or | Exor => lhs.isProvablyNonnegative && rhs.isProvablyNonnegative
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
override def quickSimplify: Constant = {
|
||||
val l = lhs.quickSimplify
|
||||
val r = rhs.quickSimplify
|
||||
(l, r) match {
|
||||
case (CompoundConstant(MathOperator.Plus, a, ll@NumericConstant(lv, _)), rr@NumericConstant(rv,_)) if operator == MathOperator.Plus =>
|
||||
case (CompoundConstant(MathOperator.Plus, a, ll@NumericConstant(lv, _)), rr@NumericConstant(rv, _)) if operator == MathOperator.Plus =>
|
||||
CompoundConstant(MathOperator.Plus, a, ll + rr).quickSimplify
|
||||
case (CompoundConstant(MathOperator.Minus, a, ll@NumericConstant(lv, _)), rr@NumericConstant(rv,_)) if operator == MathOperator.Minus =>
|
||||
case (CompoundConstant(MathOperator.Minus, a, ll@NumericConstant(lv, _)), rr@NumericConstant(rv, _)) if operator == MathOperator.Minus =>
|
||||
CompoundConstant(MathOperator.Minus, a, ll + rr).quickSimplify
|
||||
case (CompoundConstant(MathOperator.Plus, a, ll@NumericConstant(lv, _)), rr@NumericConstant(rv,_)) if operator == MathOperator.Minus =>
|
||||
case (CompoundConstant(MathOperator.Plus, a, ll@NumericConstant(lv, _)), rr@NumericConstant(rv, _)) if operator == MathOperator.Minus =>
|
||||
if (lv >= rv) {
|
||||
CompoundConstant(MathOperator.Plus, a, ll - rr).quickSimplify
|
||||
} else {
|
||||
CompoundConstant(MathOperator.Minus, a, rr - ll).quickSimplify
|
||||
}
|
||||
case (CompoundConstant(MathOperator.Minus, a, ll@NumericConstant(lv, _)), rr@NumericConstant(rv,_)) if operator == MathOperator.Plus =>
|
||||
case (CompoundConstant(MathOperator.Minus, a, ll@NumericConstant(lv, _)), rr@NumericConstant(rv, _)) if operator == MathOperator.Plus =>
|
||||
if (lv >= rv) {
|
||||
CompoundConstant(MathOperator.Minus, a, ll - rr).quickSimplify
|
||||
} else {
|
||||
@ -229,7 +251,7 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
|
||||
size = 2
|
||||
case MathOperator.Times | MathOperator.Shl =>
|
||||
val mask = (1 << (size * 8)) - 1
|
||||
if (value != (value & mask)){
|
||||
if (value != (value & mask)) {
|
||||
size = ls + rs
|
||||
}
|
||||
case _ =>
|
||||
@ -364,7 +386,16 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
|
||||
}
|
||||
}
|
||||
|
||||
override def requiredSize: Int = lhs.requiredSize max rhs.requiredSize
|
||||
override def requiredSize: Int = {
|
||||
import MathOperator._
|
||||
operator match {
|
||||
case Plus9 | DecimalPlus9 | Shl9 | DecimalShl9 => 2
|
||||
case Times | Shl =>
|
||||
// TODO
|
||||
lhs.requiredSize max rhs.requiredSize
|
||||
case _ => lhs.requiredSize max rhs.requiredSize
|
||||
}
|
||||
}
|
||||
|
||||
override def isRelatedTo(v: Thing): Boolean = lhs.isRelatedTo(v) || rhs.isRelatedTo(v)
|
||||
}
|
117
src/main/scala/millfork/env/Environment.scala
vendored
117
src/main/scala/millfork/env/Environment.scala
vendored
@ -73,7 +73,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
|
||||
def getAllFixedAddressObjects: List[(String, Int, Int)] = {
|
||||
things.values.flatMap {
|
||||
case RelativeArray(_, NumericConstant(addr, _), size, declaredBank) =>
|
||||
case RelativeArray(_, NumericConstant(addr, _), size, declaredBank, _, _) =>
|
||||
List((declaredBank.getOrElse("default"), addr.toInt, size))
|
||||
case RelativeVariable(_, NumericConstant(addr, _), typ, _, declaredBank) =>
|
||||
List((declaredBank.getOrElse("default"), addr.toInt, typ.size))
|
||||
@ -270,14 +270,24 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
InitializedMemoryVariable
|
||||
UninitializedMemoryVariable
|
||||
getArrayOrPointer(name) match {
|
||||
case th@InitializedArray(_, _, cs, _) => ConstantPointy(th.toAddress, Some(cs.length))
|
||||
case th@UninitializedArray(_, size, _) => ConstantPointy(th.toAddress, Some(size))
|
||||
case th@RelativeArray(_, _, size, _) => ConstantPointy(th.toAddress, Some(size))
|
||||
case ConstantThing(_, value, typ) if typ.size <= 2 => ConstantPointy(value, None)
|
||||
case th:VariableInMemory => VariablePointy(th.toAddress)
|
||||
case th@InitializedArray(_, _, cs, _, i, e) => ConstantPointy(th.toAddress, Some(name), Some(cs.length), i, e)
|
||||
case th@UninitializedArray(_, size, _, i, e) => ConstantPointy(th.toAddress, Some(name), Some(size), i, e)
|
||||
case th@RelativeArray(_, _, size, _, i, e) => ConstantPointy(th.toAddress, Some(name), Some(size), i, e)
|
||||
case ConstantThing(_, value, typ) if typ.size <= 2 =>
|
||||
val b = get[VariableType]("byte")
|
||||
val w = get[VariableType]("word")
|
||||
// TODO:
|
||||
ConstantPointy(value, None, None, w, b)
|
||||
case th:VariableInMemory =>
|
||||
val b = get[VariableType]("byte")
|
||||
val w = get[VariableType]("word")
|
||||
// TODO:
|
||||
VariablePointy(th.toAddress, w, b)
|
||||
case _ =>
|
||||
ErrorReporting.error(s"$name is not a valid pointer or array")
|
||||
ConstantPointy(Constant.Zero, None)
|
||||
val b = get[VariableType]("byte")
|
||||
val w = get[VariableType]("word")
|
||||
ConstantPointy(Constant.Zero, None, None, w, b)
|
||||
}
|
||||
}
|
||||
|
||||
@ -506,6 +516,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
case "||" | "|" =>
|
||||
constantOperation(MathOperator.Or, params)
|
||||
case _ =>
|
||||
if (params.size == 1) {
|
||||
return maybeGet[Type](name).flatMap(_ => eval(params.head))
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
@ -580,6 +593,26 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
addThing(Alias(stmt.name, stmt.target), stmt.position)
|
||||
}
|
||||
|
||||
def registerEnum(stmt: EnumDefinitionStatement): Unit = {
|
||||
val count = if (stmt.variants.nonEmpty && stmt.variants.forall(_._2.isEmpty)) {
|
||||
val size = stmt.variants.size
|
||||
addThing(ConstantThing(stmt.name + ".count", NumericConstant(size, 1), get[Type]("byte")), stmt.position)
|
||||
Some(size)
|
||||
} else None
|
||||
val t = EnumType(stmt.name, count)
|
||||
addThing(t, stmt.position)
|
||||
var value = Constant.Zero
|
||||
for((name, optValue) <- stmt.variants) {
|
||||
optValue match {
|
||||
case Some(v) =>
|
||||
value = eval(v).getOrElse(Constant.error(s"Enum constant `${stmt.name}.$name` is not a constant", stmt.position))
|
||||
case _ =>
|
||||
}
|
||||
addThing(ConstantThing(name, value, t), stmt.position)
|
||||
value += 1
|
||||
}
|
||||
}
|
||||
|
||||
def registerFunction(stmt: FunctionDeclarationStatement, options: CompilationOptions): Unit = {
|
||||
val w = get[Type]("word")
|
||||
val name = stmt.name
|
||||
@ -799,23 +832,37 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
}
|
||||
|
||||
def registerArray(stmt: ArrayDeclarationStatement, options: CompilationOptions): Unit = {
|
||||
val b = get[Type]("byte")
|
||||
val b = get[VariableType]("byte")
|
||||
val w = get[VariableType]("word")
|
||||
val p = get[Type]("pointer")
|
||||
stmt.elements match {
|
||||
case None =>
|
||||
stmt.length match {
|
||||
case None => ErrorReporting.error(s"Array `${stmt.name}` without size nor contents", stmt.position)
|
||||
case Some(l) =>
|
||||
// array arr[...]
|
||||
val address = stmt.address.map(a => eval(a).getOrElse(ErrorReporting.fatal(s"Array `${stmt.name}` has non-constant address", stmt.position)))
|
||||
val lengthConst = eval(l).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant length", stmt.position))
|
||||
val (indexType, lengthConst) = l match {
|
||||
case VariableExpression(name) =>
|
||||
maybeGet[Type](name) match {
|
||||
case Some(typ@EnumType(_, Some(count))) =>
|
||||
typ -> NumericConstant(count, 1)
|
||||
case Some(typ) =>
|
||||
ErrorReporting.error(s"Type $name cannot be used as an array index", l.position)
|
||||
w -> Constant.Zero
|
||||
case _ => w -> eval(l).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant length", stmt.position))
|
||||
}
|
||||
case _ =>
|
||||
w -> eval(l).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant length", stmt.position))
|
||||
}
|
||||
lengthConst match {
|
||||
case NumericConstant(length, _) =>
|
||||
if (length > 0xffff || length < 0) ErrorReporting.error(s"Array `${stmt.name}` has invalid length", stmt.position)
|
||||
val array = address match {
|
||||
case None => UninitializedArray(stmt.name + ".array", length.toInt,
|
||||
declaredBank = stmt.bank)
|
||||
declaredBank = stmt.bank, indexType, b)
|
||||
case Some(aa) => RelativeArray(stmt.name + ".array", aa, length.toInt,
|
||||
declaredBank = stmt.bank)
|
||||
declaredBank = stmt.bank, indexType, b)
|
||||
}
|
||||
addThing(array, stmt.position)
|
||||
registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, stmt.bank), stmt.position, options)
|
||||
@ -850,20 +897,40 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
}
|
||||
case Some(contents1) =>
|
||||
val contents = extractArrayContents(contents1)
|
||||
stmt.length match {
|
||||
case None =>
|
||||
case Some(l) =>
|
||||
val lengthConst = eval(l).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant length", stmt.position))
|
||||
val indexType = stmt.length match {
|
||||
case None => // array arr = [...]
|
||||
w
|
||||
case Some(l) => // array arr[...] = [...]
|
||||
val (indexTyp, lengthConst) = l match {
|
||||
case VariableExpression(name) =>
|
||||
maybeGet[Type](name) match {
|
||||
case Some(typ@EnumType(_, Some(count))) =>
|
||||
if (count != contents.size)
|
||||
ErrorReporting.error(s"Array `${stmt.name}` has actual length different than the number of variants in the enum `${typ.name}`", stmt.position)
|
||||
typ -> NumericConstant(count, 1)
|
||||
case Some(typ@EnumType(_, None)) =>
|
||||
// using a non-enumerable enum for an array index is ok if the array is preïnitialized
|
||||
typ -> NumericConstant(contents.length, 1)
|
||||
case Some(_) =>
|
||||
ErrorReporting.error(s"Type $name cannot be used as an array index", l.position)
|
||||
w -> Constant.Zero
|
||||
case _ =>
|
||||
w -> eval(l).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant length", stmt.position))
|
||||
}
|
||||
case _ =>
|
||||
w -> eval(l).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant length", stmt.position))
|
||||
}
|
||||
lengthConst match {
|
||||
case NumericConstant(ll, _) =>
|
||||
if (ll != contents.length) ErrorReporting.error(s"Array `${stmt.name}` has different declared and actual length", stmt.position)
|
||||
case _ => ErrorReporting.error(s"Array `${stmt.name}` has weird length", stmt.position)
|
||||
}
|
||||
indexTyp
|
||||
}
|
||||
val length = contents.length
|
||||
if (length > 0xffff || length < 0) ErrorReporting.error(s"Array `${stmt.name}` has invalid length", stmt.position)
|
||||
val address = stmt.address.map(a => eval(a).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant address", stmt.position)))
|
||||
val array = InitializedArray(stmt.name + ".array", address, contents, declaredBank = stmt.bank)
|
||||
val array = InitializedArray(stmt.name + ".array", address, contents, declaredBank = stmt.bank, indexType, b)
|
||||
addThing(array, stmt.position)
|
||||
registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None,
|
||||
declaredBank = stmt.bank), stmt.position, options)
|
||||
@ -905,7 +972,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
}
|
||||
val b = get[Type]("byte")
|
||||
val w = get[Type]("word")
|
||||
val typ = get[PlainType](stmt.typ)
|
||||
val typ = get[VariableType](stmt.typ)
|
||||
if (stmt.typ == "pointer" || stmt.typ == "farpointer") {
|
||||
// if (stmt.constant) {
|
||||
// ErrorReporting.error(s"Pointer `${stmt.name}` cannot be constant")
|
||||
@ -1067,15 +1134,23 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
}
|
||||
|
||||
def collectDeclarations(program: Program, options: CompilationOptions): Unit = {
|
||||
val b = get[VariableType]("byte")
|
||||
if (options.flag(CompilationFlag.OptimizeForSonicSpeed)) {
|
||||
addThing(InitializedArray("identity$", None, List.tabulate(256)(n => LiteralExpression(n, 1)), declaredBank = None), None)
|
||||
addThing(InitializedArray("identity$", None, List.tabulate(256)(n => LiteralExpression(n, 1)), declaredBank = None, b, b), None)
|
||||
}
|
||||
program.declarations.foreach {
|
||||
case a: AliasDefinitionStatement => registerAlias(a)
|
||||
case _ =>
|
||||
}
|
||||
program.declarations.foreach {
|
||||
case e: EnumDefinitionStatement => registerEnum(e)
|
||||
case _ =>
|
||||
}
|
||||
program.declarations.foreach {
|
||||
case f: FunctionDeclarationStatement => registerFunction(f, options)
|
||||
case v: VariableDeclarationStatement => registerVariable(v, options)
|
||||
case a: ArrayDeclarationStatement => registerArray(a, options)
|
||||
case a: AliasDefinitionStatement => registerAlias(a)
|
||||
case i: ImportStatement => ()
|
||||
case _ =>
|
||||
}
|
||||
if (options.zpRegisterSize > 0 && !things.contains("__reg")) {
|
||||
addThing(BasicPlainType("__reg$type", options.zpRegisterSize), None)
|
||||
@ -1093,7 +1168,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
}
|
||||
if (CpuFamily.forType(options.platform.cpu) == CpuFamily.M6502) {
|
||||
if (!things.contains("__constant8")) {
|
||||
things("__constant8") = InitializedArray("__constant8", None, List(LiteralExpression(8, 1)), declaredBank = None)
|
||||
things("__constant8") = InitializedArray("__constant8", None, List(LiteralExpression(8, 1)), declaredBank = None, b, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
src/main/scala/millfork/env/Pointy.scala
vendored
13
src/main/scala/millfork/env/Pointy.scala
vendored
@ -1,6 +1,13 @@
|
||||
package millfork.env
|
||||
|
||||
trait Pointy
|
||||
trait Pointy {
|
||||
def name: Option[String]
|
||||
def indexType: VariableType
|
||||
def elementType: VariableType
|
||||
}
|
||||
|
||||
case class VariablePointy(addr: Constant) extends Pointy
|
||||
case class ConstantPointy(value: Constant, size: Option[Int]) extends Pointy
|
||||
case class VariablePointy(addr: Constant, indexType: VariableType, elementType: VariableType) extends Pointy {
|
||||
override def name: Option[String] = None
|
||||
}
|
||||
|
||||
case class ConstantPointy(value: Constant, name: Option[String], size: Option[Int], indexType: VariableType, elementType: VariableType) extends Pointy
|
||||
|
26
src/main/scala/millfork/env/Thing.scala
vendored
26
src/main/scala/millfork/env/Thing.scala
vendored
@ -30,8 +30,12 @@ sealed trait Type extends CallableThing {
|
||||
override def toString(): String = name
|
||||
|
||||
def isAssignableTo(targetType: Type): Boolean = isCompatible(targetType)
|
||||
|
||||
def isArithmetic = false
|
||||
}
|
||||
|
||||
sealed trait VariableType extends Type
|
||||
|
||||
case object VoidType extends Type {
|
||||
def size = 0
|
||||
|
||||
@ -40,13 +44,16 @@ case object VoidType extends Type {
|
||||
override def name = "void"
|
||||
}
|
||||
|
||||
sealed trait PlainType extends Type {
|
||||
sealed trait PlainType extends VariableType {
|
||||
override def isCompatible(other: Type): Boolean = this == other || this.isSubtypeOf(other) || other.isSubtypeOf(this)
|
||||
|
||||
override def isAssignableTo(targetType: Type): Boolean = isCompatible(targetType) || (targetType match {
|
||||
case BasicPlainType(_, size) => size > this.size // TODO
|
||||
case DerivedPlainType(_, parent, size) => isAssignableTo(parent)
|
||||
case _ => false
|
||||
})
|
||||
|
||||
override def isArithmetic = true
|
||||
}
|
||||
|
||||
case class BasicPlainType(name: String, size: Int) extends PlainType {
|
||||
@ -61,6 +68,12 @@ case class DerivedPlainType(name: String, parent: PlainType, isSigned: Boolean)
|
||||
override def isSubtypeOf(other: Type): Boolean = parent == other || parent.isSubtypeOf(other)
|
||||
}
|
||||
|
||||
case class EnumType(name: String, count: Option[Int]) extends VariableType {
|
||||
override def size: Int = 1
|
||||
|
||||
override def isSigned: Boolean = false
|
||||
}
|
||||
|
||||
sealed trait BooleanType extends Type {
|
||||
def size = 0
|
||||
|
||||
@ -175,9 +188,12 @@ case class InitializedMemoryVariable(name: String, address: Option[Constant], ty
|
||||
override def alloc: VariableAllocationMethod.Value = VariableAllocationMethod.Static
|
||||
}
|
||||
|
||||
trait MfArray extends ThingInMemory with IndexableThing
|
||||
trait MfArray extends ThingInMemory with IndexableThing {
|
||||
def indexType: VariableType
|
||||
def elementType: VariableType
|
||||
}
|
||||
|
||||
case class UninitializedArray(name: String, sizeInBytes: Int, declaredBank: Option[String]) extends MfArray with UninitializedMemory {
|
||||
case class UninitializedArray(name: String, sizeInBytes: Int, declaredBank: Option[String], indexType: VariableType, elementType: VariableType) extends MfArray with UninitializedMemory {
|
||||
override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this)
|
||||
|
||||
override def alloc = VariableAllocationMethod.Static
|
||||
@ -189,7 +205,7 @@ case class UninitializedArray(name: String, sizeInBytes: Int, declaredBank: Opti
|
||||
override def zeropage: Boolean = false
|
||||
}
|
||||
|
||||
case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, declaredBank: Option[String]) extends MfArray {
|
||||
case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, declaredBank: Option[String], indexType: VariableType, elementType: VariableType) extends MfArray {
|
||||
override def toAddress: Constant = address
|
||||
|
||||
override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false)
|
||||
@ -199,7 +215,7 @@ case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, decl
|
||||
override def zeropage: Boolean = false
|
||||
}
|
||||
|
||||
case class InitializedArray(name: String, address: Option[Constant], contents: List[Expression], declaredBank: Option[String]) extends MfArray with PreallocableThing {
|
||||
case class InitializedArray(name: String, address: Option[Constant], contents: List[Expression], declaredBank: Option[String], indexType: VariableType, elementType: VariableType) extends MfArray with PreallocableThing {
|
||||
override def shouldGenerate = true
|
||||
|
||||
override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false)
|
||||
|
@ -9,6 +9,21 @@ object ErrorReporting {
|
||||
|
||||
var hasErrors = false
|
||||
|
||||
private var sourceLines: Option[IndexedSeq[String]] = None
|
||||
|
||||
private def printErrorContext(pos: Option[Position]): Unit = {
|
||||
if (sourceLines.isDefined && pos.isDefined) {
|
||||
val line = sourceLines.get.apply(pos.get.line - 1)
|
||||
val column = pos.get.column - 1
|
||||
val margin = " "
|
||||
print(margin)
|
||||
println(line)
|
||||
print(margin)
|
||||
print(" " * column)
|
||||
println("^")
|
||||
}
|
||||
}
|
||||
|
||||
def f(position: Option[Position]): String = position.fold("")(p => s"(${p.line}:${p.column}) ")
|
||||
|
||||
def info(msg: String, position: Option[Position] = None): Unit = {
|
||||
@ -40,6 +55,7 @@ object ErrorReporting {
|
||||
def warn(msg: String, options: CompilationOptions, position: Option[Position] = None): Unit = {
|
||||
if (verbosity < 0) return
|
||||
println("WARN: " + f(position) + msg)
|
||||
printErrorContext(position)
|
||||
flushOutput()
|
||||
if (options.flag(CompilationFlag.FatalWarnings)) {
|
||||
hasErrors = true
|
||||
@ -49,12 +65,14 @@ object ErrorReporting {
|
||||
def error(msg: String, position: Option[Position] = None): Unit = {
|
||||
hasErrors = true
|
||||
println("ERROR: " + f(position) + msg)
|
||||
printErrorContext(position)
|
||||
flushOutput()
|
||||
}
|
||||
|
||||
def fatal(msg: String, position: Option[Position] = None): Nothing = {
|
||||
hasErrors = true
|
||||
println("FATAL: " + f(position) + msg)
|
||||
printErrorContext(position)
|
||||
flushOutput()
|
||||
System.exit(1)
|
||||
throw new RuntimeException(msg)
|
||||
@ -63,6 +81,7 @@ object ErrorReporting {
|
||||
def fatalQuit(msg: String, position: Option[Position] = None): Nothing = {
|
||||
hasErrors = true
|
||||
println("FATAL: " + f(position) + msg)
|
||||
printErrorContext(position)
|
||||
flushOutput()
|
||||
System.exit(1)
|
||||
throw new RuntimeException(msg)
|
||||
@ -75,4 +94,13 @@ object ErrorReporting {
|
||||
}
|
||||
}
|
||||
|
||||
def clearErrors(): Unit = {
|
||||
hasErrors = false
|
||||
sourceLines = None
|
||||
}
|
||||
|
||||
def setSource(source: Option[IndexedSeq[String]]): Unit = {
|
||||
sourceLines = source
|
||||
}
|
||||
|
||||
}
|
@ -16,6 +16,12 @@ object Node {
|
||||
node.position = Some(position)
|
||||
node
|
||||
}
|
||||
def pos(position: Option[Position]): N = {
|
||||
if (position.isDefined) {
|
||||
node.position = position
|
||||
}
|
||||
node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,6 +235,10 @@ case class AliasDefinitionStatement(name: String, target: String) extends Declar
|
||||
override def getAllExpressions: List[Expression] = Nil
|
||||
}
|
||||
|
||||
case class EnumDefinitionStatement(name: String, variants: List[(String, Option[Expression])]) extends DeclarationStatement {
|
||||
override def getAllExpressions: List[Expression] = variants.flatMap(_._2)
|
||||
}
|
||||
|
||||
case class ArrayDeclarationStatement(name: String,
|
||||
bank: Option[String],
|
||||
length: Option[Expression],
|
||||
|
@ -247,7 +247,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
||||
}
|
||||
|
||||
env.allPreallocatables.foreach {
|
||||
case thing@InitializedArray(name, Some(NumericConstant(address, _)), items, _) =>
|
||||
case thing@InitializedArray(name, Some(NumericConstant(address, _)), items, _, _, _) =>
|
||||
val bank = thing.bank(options)
|
||||
val bank0 = mem.banks(bank)
|
||||
var index = address.toInt
|
||||
@ -271,7 +271,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
||||
}).mkString(", "))
|
||||
}
|
||||
initializedVariablesSize += items.length
|
||||
case thing@InitializedArray(name, Some(_), items, _) => ???
|
||||
case thing@InitializedArray(name, Some(_), items, _, _, _) => ???
|
||||
case f: NormalFunction if f.address.isDefined =>
|
||||
val bank = f.bank(options)
|
||||
val bank0 = mem.banks(bank)
|
||||
@ -354,7 +354,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
||||
justAfterCode += "default" -> (index + 1)
|
||||
}
|
||||
env.allPreallocatables.foreach {
|
||||
case thing@InitializedArray(name, None, items, _) =>
|
||||
case thing@InitializedArray(name, None, items, _, _, _) =>
|
||||
val bank = thing.bank(options)
|
||||
val bank0 = mem.banks(bank)
|
||||
var index = codeAllocators(bank).allocateBytes(bank0, options, items.size, initialized = true, writeable = true, location = AllocationLocation.High)
|
||||
|
@ -354,6 +354,7 @@ abstract class MfParser[T](filename: String, input: String, currentDirectory: St
|
||||
bank <- bankDeclaration
|
||||
flags <- functionFlags ~ HWS
|
||||
returnType <- identifier ~ SWS
|
||||
if !InvalidReturnTypes(returnType)
|
||||
name <- identifier ~ HWS
|
||||
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS
|
||||
addr <- ("@" ~/ HWS ~/ mfExpression(1)).?.opaque("<address>") ~/ AWS
|
||||
@ -385,9 +386,25 @@ abstract class MfParser[T](filename: String, input: String, currentDirectory: St
|
||||
|
||||
def validateAsmFunctionBody(p: Position, flags: Set[String], name: String, statements: Option[List[Statement]])
|
||||
|
||||
val enumVariant: P[(String, Option[Expression])] = for {
|
||||
name <- identifier ~/ HWS
|
||||
value <- ("=" ~/ HWS ~/ mfExpression(1)).? ~ HWS
|
||||
} yield name -> value
|
||||
|
||||
val enumVariants: P[List[(String, Option[Expression])]] =
|
||||
("{" ~/ AWS ~ enumVariant.rep(sep = NoCut(EOLOrComma) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList)
|
||||
|
||||
val enumDefinition: P[Seq[EnumDefinitionStatement]] = for {
|
||||
p <- position()
|
||||
_ <- "enum" ~ !letterOrDigit ~/ SWS ~ position("enum name")
|
||||
name <- identifier ~/ HWS
|
||||
_ <- position("enum defintion block")
|
||||
variants <- enumVariants ~/ Pass
|
||||
} yield Seq(EnumDefinitionStatement(name, variants).pos(p))
|
||||
|
||||
val program: Parser[Program] = for {
|
||||
_ <- Start ~/ AWS ~/ Pass
|
||||
definitions <- (importStatement | arrayDefinition | aliasDefinition | functionDefinition | globalVariableDefinition).rep(sep = EOL)
|
||||
definitions <- (importStatement | arrayDefinition | aliasDefinition | enumDefinition | functionDefinition | globalVariableDefinition).rep(sep = EOL)
|
||||
_ <- AWS ~ End
|
||||
} yield Program(definitions.flatten.toList)
|
||||
|
||||
@ -405,6 +422,8 @@ object MfParser {
|
||||
|
||||
val EOL: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | comment).opaque("<first line break>") ~ AWS).opaque("<line break>")
|
||||
|
||||
val EOLOrComma: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | "," | comment).opaque("<first line break or comma>") ~ AWS).opaque("<line break or comma>")
|
||||
|
||||
val letter: P[String] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_").!)
|
||||
|
||||
val letterOrDigit: P[Unit] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_.$1234567890"))
|
||||
@ -531,4 +550,6 @@ object MfParser {
|
||||
val variableFlags: P[Set[String]] = flags_("const", "static", "volatile", "stack", "register")
|
||||
|
||||
val functionFlags: P[Set[String]] = flags_("asm", "inline", "interrupt", "macro", "noinline", "reentrant", "kernal_interrupt")
|
||||
|
||||
val InvalidReturnTypes = Set("enum", "alias", "array", "const", "stack", "register", "static", "volatile", "import")
|
||||
}
|
||||
|
155
src/test/scala/millfork/test/EnumSuite.scala
Normal file
155
src/test/scala/millfork/test/EnumSuite.scala
Normal file
@ -0,0 +1,155 @@
|
||||
package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.test.emu.{EmuUnoptimizedCrossPlatformRun, ShouldNotCompile}
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
class EnumSuite extends FunSuite with Matchers {
|
||||
|
||||
test("Enum basic test") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)(
|
||||
"""
|
||||
| enum ugly {
|
||||
| a
|
||||
| b,c,
|
||||
| d
|
||||
| }
|
||||
| void main () {
|
||||
| byte i
|
||||
| ugly e
|
||||
| e = a
|
||||
| if byte(e) != 0 { crash() }
|
||||
| i = 1
|
||||
| if ugly(i) != b { crash() }
|
||||
| }
|
||||
| asm void crash() {
|
||||
| #if ARCH_6502
|
||||
| sta $bfff
|
||||
| rts
|
||||
| #elseif ARCH_Z80
|
||||
| ld ($bfff),a
|
||||
| ret
|
||||
| #else
|
||||
| #error
|
||||
| #endif
|
||||
| }
|
||||
""".stripMargin){_=>}
|
||||
}
|
||||
|
||||
test("Enum arrays") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)(
|
||||
"""
|
||||
| enum ugly {
|
||||
| a
|
||||
| b,c,
|
||||
| d
|
||||
| }
|
||||
| array a1 [ugly]
|
||||
| array a2 [ugly] = [6,7,8,9]
|
||||
| void main () {
|
||||
| if a2[a] != 6 { crash() }
|
||||
| }
|
||||
| asm void crash() {
|
||||
| #if ARCH_6502
|
||||
| sta $bfff
|
||||
| rts
|
||||
| #elseif ARCH_Z80
|
||||
| ld ($bfff),a
|
||||
| ret
|
||||
| #else
|
||||
| #error
|
||||
| #endif
|
||||
| }
|
||||
""".stripMargin){_=>}
|
||||
}
|
||||
|
||||
test("Enum-byte incompatibility test") {
|
||||
// ShouldNotCompile(
|
||||
// """
|
||||
// | enum ugly { a }
|
||||
// | void main() {
|
||||
// | byte b
|
||||
// | ugly u
|
||||
// | b = u
|
||||
// | }
|
||||
// """.stripMargin)
|
||||
//
|
||||
// ShouldNotCompile(
|
||||
// """
|
||||
// | enum ugly { a }
|
||||
// | void main() {
|
||||
// | byte b
|
||||
// | ugly u
|
||||
// | u = b
|
||||
// | }
|
||||
// """.stripMargin)
|
||||
//
|
||||
// ShouldNotCompile(
|
||||
// """
|
||||
// | enum ugly { a }
|
||||
// | byte main() {
|
||||
// | byte b
|
||||
// | ugly u
|
||||
// | return u
|
||||
// | }
|
||||
// """.stripMargin)
|
||||
//
|
||||
// ShouldNotCompile(
|
||||
// """
|
||||
// | enum ugly { a }
|
||||
// | ugly main() {
|
||||
// | byte b
|
||||
// | ugly u
|
||||
// | return b
|
||||
// | }
|
||||
// """.stripMargin)
|
||||
//
|
||||
// ShouldNotCompile(
|
||||
// """
|
||||
// | enum ugly { a }
|
||||
// | byte main() {
|
||||
// | byte b
|
||||
// | ugly u
|
||||
// | return b + u
|
||||
// | }
|
||||
// """.stripMargin)
|
||||
//
|
||||
// ShouldNotCompile(
|
||||
// """
|
||||
// | enum ugly { a }
|
||||
// | void main() {
|
||||
// | byte b
|
||||
// | ugly u
|
||||
// | if b > u {}
|
||||
// | }
|
||||
// """.stripMargin)
|
||||
//
|
||||
// ShouldNotCompile(
|
||||
// """
|
||||
// | enum ugly { a }
|
||||
// | array arr[ugly] = []
|
||||
// | void main() {
|
||||
// | }
|
||||
// """.stripMargin)
|
||||
|
||||
// ShouldNotCompile(
|
||||
// """
|
||||
// | enum ugly { a }
|
||||
// | array arr[ugly] = [1,2,3]
|
||||
// | void main() {
|
||||
// | }
|
||||
// """.stripMargin)
|
||||
|
||||
ShouldNotCompile(
|
||||
"""
|
||||
| enum ugly { a }
|
||||
| array arr[ugly]
|
||||
| ugly main() {
|
||||
| return a[0]
|
||||
| }
|
||||
""".stripMargin)
|
||||
}
|
||||
}
|
@ -39,8 +39,8 @@ class FarwordTest extends FunSuite with Matchers {
|
||||
| w = $7788
|
||||
| b = $55
|
||||
| output3 = $23344
|
||||
| output2 = $11223344
|
||||
| output1 = $11223344
|
||||
| output2 = $23344
|
||||
| output1 = $3344
|
||||
| output2 = w
|
||||
| output1 = b
|
||||
| }
|
||||
|
@ -2,7 +2,7 @@ package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.error.ErrorReporting
|
||||
import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedRun}
|
||||
import millfork.test.emu._
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
@ -209,5 +209,58 @@ class ForLoopSuite extends FunSuite with Matchers {
|
||||
}
|
||||
}
|
||||
|
||||
test("Edge cases - positive") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)("""
|
||||
| void main() {
|
||||
| byte i
|
||||
| for i,0,until,256 { f() }
|
||||
| for i,0,paralleluntil,256 { f() }
|
||||
| for i,0,until,255 { f() }
|
||||
| for i,0,paralleluntil,255 { f() }
|
||||
| for i,0,to,255 { f() }
|
||||
| for i,0,parallelto,255 { f() }
|
||||
| for i,255,downto,0 { f() }
|
||||
| }
|
||||
| void f() { }
|
||||
""".stripMargin){ m => }
|
||||
}
|
||||
|
||||
test("Edge cases - negative") {
|
||||
ShouldNotCompile("""
|
||||
| void main() {
|
||||
| byte i
|
||||
| for i,0,until,257 { f() }
|
||||
| }
|
||||
| void f() { }
|
||||
""".stripMargin)
|
||||
ShouldNotCompile("""
|
||||
| void main() {
|
||||
| byte i
|
||||
| for i,0,paralleluntil,257 { f() }
|
||||
| }
|
||||
| void f() { }
|
||||
""".stripMargin)
|
||||
ShouldNotCompile("""
|
||||
| void main() {
|
||||
| byte i
|
||||
| for i,0,to,256 { f() }
|
||||
| }
|
||||
| void f() { }
|
||||
""".stripMargin)
|
||||
ShouldNotCompile("""
|
||||
| void main() {
|
||||
| byte i
|
||||
| for i,0,parallelto,256 { f() }
|
||||
| }
|
||||
| void f() { }
|
||||
""".stripMargin)
|
||||
ShouldNotCompile("""
|
||||
| void main() {
|
||||
| byte i
|
||||
| for i,256,downto,0 { f() }
|
||||
| }
|
||||
| void f() { }
|
||||
""".stripMargin)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ class IllegalSuite extends FunSuite with Matchers {
|
||||
| byte main () {
|
||||
| output = five()
|
||||
| output <<= 1
|
||||
| return output
|
||||
| return output.b0
|
||||
| }
|
||||
| byte five () {
|
||||
| return 5
|
||||
|
@ -41,7 +41,7 @@ class LongTest extends FunSuite with Matchers {
|
||||
| b = $55
|
||||
| output4 = $11223344
|
||||
| output2 = $11223344
|
||||
| output1 = $11223344
|
||||
| output1 = $3344
|
||||
| output2 = w
|
||||
| output1 = b
|
||||
| }
|
||||
|
@ -119,6 +119,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
|
||||
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
|
||||
if (source.contains("import zp_reg"))
|
||||
effectiveSource += Files.readAllLines(Paths.get("include/zp_reg.mfk"), StandardCharsets.US_ASCII).asScala.mkString("\n", "\n", "")
|
||||
ErrorReporting.setSource(Some(effectiveSource.lines.toIndexedSeq))
|
||||
val (preprocessedSource, features) = Preprocessor.preprocessForTest(options, effectiveSource)
|
||||
val parserF = MosParser("", preprocessedSource, "", options, features)
|
||||
parserF.toAst match {
|
||||
@ -167,7 +168,9 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
|
||||
println(f"Gain: ${(100L * (unoptimizedSize - optimizedSize) / unoptimizedSize.toDouble).round}%5d%%")
|
||||
}
|
||||
|
||||
ErrorReporting.assertNoErrors("Code generation failed")
|
||||
if (ErrorReporting.hasErrors) {
|
||||
fail("Code generation failed")
|
||||
}
|
||||
|
||||
val memoryBank = assembler.mem.banks("default")
|
||||
if (source.contains("return [")) {
|
||||
@ -175,7 +178,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
|
||||
if (memoryBank.readable(i)) memoryBank.readable(i + 1) = true
|
||||
}
|
||||
}
|
||||
platform.cpu match {
|
||||
val timings = platform.cpu match {
|
||||
case millfork.Cpu.Cmos =>
|
||||
runViaSymon(memoryBank, platform.codeAllocators("default").startAt, CpuBehavior.CMOS_6502)
|
||||
case millfork.Cpu.Ricoh =>
|
||||
@ -189,12 +192,14 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
|
||||
ErrorReporting.trace("No emulation support for " + platform.cpu)
|
||||
Timings(-1, -1) -> memoryBank
|
||||
}
|
||||
ErrorReporting.clearErrors()
|
||||
timings
|
||||
case f: Failure[_, _] =>
|
||||
println(f)
|
||||
println(f.extra.toString)
|
||||
println(f.lastParser.toString)
|
||||
ErrorReporting.error("Syntax error", Some(parserF.lastPosition))
|
||||
???
|
||||
fail("syntax error")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
|
||||
ErrorReporting.verbosity = 999
|
||||
var effectiveSource = source
|
||||
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
|
||||
ErrorReporting.setSource(Some(effectiveSource.lines.toIndexedSeq))
|
||||
val (preprocessedSource, features) = Preprocessor.preprocessForTest(options, effectiveSource)
|
||||
val parserF = Z80Parser("", preprocessedSource, "", options, features)
|
||||
parserF.toAst match {
|
||||
@ -84,7 +85,9 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
|
||||
println(f"Gain: ${(100L * (unoptimizedSize - optimizedSize) / unoptimizedSize.toDouble).round}%5d%%")
|
||||
}
|
||||
|
||||
ErrorReporting.assertNoErrors("Code generation failed")
|
||||
if (ErrorReporting.hasErrors) {
|
||||
fail("Code generation failed")
|
||||
}
|
||||
|
||||
val memoryBank = assembler.mem.banks("default")
|
||||
(0x1f0 until 0x200).foreach(i => memoryBank.readable(i) = true)
|
||||
@ -102,7 +105,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
|
||||
|
||||
(0x200 until 0x2000).takeWhile(memoryBank.occupied(_)).map(memoryBank.output).grouped(16).map(_.map(i => f"$i%02x").mkString(" ")).foreach(ErrorReporting.debug(_))
|
||||
|
||||
platform.cpu match {
|
||||
val timings = platform.cpu match {
|
||||
case millfork.Cpu.Z80 =>
|
||||
val cpu = new Z80Core(Z80Memory(memoryBank), DummyIO)
|
||||
cpu.reset()
|
||||
@ -118,6 +121,8 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
|
||||
case _ =>
|
||||
Timings(-1, -1) -> memoryBank
|
||||
}
|
||||
ErrorReporting.clearErrors()
|
||||
timings
|
||||
case f: Failure[_, _] =>
|
||||
println(f)
|
||||
println(f.extra.toString)
|
||||
|
80
src/test/scala/millfork/test/emu/ShouldNotCompile.scala
Normal file
80
src/test/scala/millfork/test/emu/ShouldNotCompile.scala
Normal file
@ -0,0 +1,80 @@
|
||||
package millfork.test.emu
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.{Files, Paths}
|
||||
|
||||
import fastparse.core.Parsed.{Failure, Success}
|
||||
import millfork.compiler.CompilationContext
|
||||
import millfork.compiler.mos.MosCompiler
|
||||
import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, NormalFunction}
|
||||
import millfork.error.ErrorReporting
|
||||
import millfork.node.StandardCallGraph
|
||||
import millfork.parser.{MosParser, Preprocessor}
|
||||
import millfork.{CompilationFlag, CompilationOptions, Cpu, CpuFamily}
|
||||
import org.scalatest.Matchers
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
|
||||
object ShouldNotCompile extends Matchers {
|
||||
|
||||
def apply(source: String): Unit = {
|
||||
checkCase(Cpu.Mos, source)
|
||||
checkCase(Cpu.Z80, source)
|
||||
}
|
||||
|
||||
private def checkCase(cpu: Cpu.Value, source: String) {
|
||||
Console.out.flush()
|
||||
Console.err.flush()
|
||||
println(source)
|
||||
val platform = EmuPlatform.get(cpu)
|
||||
val options = CompilationOptions(platform, Map(CompilationFlag.LenientTextEncoding -> true), None, platform.zpRegisterSize)
|
||||
ErrorReporting.hasErrors = false
|
||||
ErrorReporting.verbosity = 999
|
||||
var effectiveSource = source
|
||||
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
|
||||
if (source.contains("import zp_reg"))
|
||||
effectiveSource += Files.readAllLines(Paths.get("include/zp_reg.mfk"), StandardCharsets.US_ASCII).asScala.mkString("\n", "\n", "")
|
||||
ErrorReporting.setSource(Some(effectiveSource.lines.toIndexedSeq))
|
||||
val (preprocessedSource, features) = Preprocessor.preprocessForTest(options, effectiveSource)
|
||||
val parserF = MosParser("", preprocessedSource, "", options, features)
|
||||
parserF.toAst match {
|
||||
case Success(program, _) =>
|
||||
ErrorReporting.assertNoErrors("Parse failed")
|
||||
|
||||
// prepare
|
||||
val callGraph = new StandardCallGraph(program)
|
||||
val cpuFamily = CpuFamily.forType(cpu)
|
||||
val env = new Environment(None, "", cpuFamily)
|
||||
env.collectDeclarations(program, options)
|
||||
|
||||
var unoptimizedSize = 0L
|
||||
// print unoptimized asm
|
||||
env.allPreallocatables.foreach {
|
||||
case f: NormalFunction =>
|
||||
val unoptimized = MosCompiler.compile(CompilationContext(f.environment, f, 0, options, Set()))
|
||||
unoptimizedSize += unoptimized.map(_.sizeInBytes).sum
|
||||
case d: InitializedArray =>
|
||||
unoptimizedSize += d.contents.length
|
||||
case d: InitializedMemoryVariable =>
|
||||
unoptimizedSize += d.typ.size
|
||||
}
|
||||
|
||||
if (!ErrorReporting.hasErrors) {
|
||||
val familyName = cpuFamily match {
|
||||
case CpuFamily.M6502 => "6502"
|
||||
case CpuFamily.I80 => "Z80"
|
||||
case _ => "unknown CPU"
|
||||
}
|
||||
fail("Failed: Compilation succeeded for " + familyName)
|
||||
}
|
||||
ErrorReporting.clearErrors()
|
||||
|
||||
case f: Failure[_, _] =>
|
||||
println(f.extra.toString)
|
||||
println(f.lastParser.toString)
|
||||
ErrorReporting.error("Syntax error: " + parserF.lastLabel, Some(parserF.lastPosition))
|
||||
fail("syntax error")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user