millfork/src/main/scala/millfork/parser/MfParser.scala

982 lines
47 KiB
Scala

package millfork.parser
import java.lang.Long.parseLong
import java.nio.file.{Files, Paths}
import java.util
import fastparse.all._
import fastparse.core.Parsed.Failure
import millfork.assembly.Elidability
import millfork.env._
import millfork.error.{ConsoleLogger, Logger}
import millfork.node._
import millfork.output.{DivisibleAlignment, MemoryAlignment, NoAlignment}
import millfork.{CompilationFlag, CompilationOptions, Confusables, SeparatedList}
import scala.collection.immutable.BitSet
/**
* @author Karol Stasiak
*/
abstract class MfParser[T](fileId: String, input: String, currentDirectory: String, options: CompilationOptions, featureConstants: Map[String, Long]) {
import MfParser._
var lastPosition = Position(fileId, 1, 1, 0)
var lastLabel = ""
protected val log: Logger = options.log
def allowIntelHexAtomsInAssembly: Boolean
val enableDebuggingOptions: Boolean = options.flag(CompilationFlag.EnableInternalTestSyntax)
private def getCharacterNameSafe(c: Char): String = {
try {
"U+%04X %s".format(c.toInt, Character.getName(c))
} catch {
case _: Throwable => c match {
case '\n' => "U+000A LINE FEED"
case '\r' => "U+000D CARRIAGE RETURN"
case _ => "U+%04X"
}
}
}
def toAst: Parsed[Program] = {
val parse = program.parse(input + "\n\n\n")
parse match {
case _: Failure[_, _] =>
if (lastPosition.cursor >= 0 && lastPosition.cursor < input.length) {
val c = input(lastPosition.cursor)
if (c >= 0x100 || c < 0x20 || c == '`') {
log.error("Invalid character %s".format(getCharacterNameSafe(c)), Some(lastPosition))
Confusables.map.get(c) match {
case Some(ascii) =>
log.info(s"Did you mean: $ascii")
case _ =>
}
}
}
case _ =>
}
parse
}
private val lineStarts: Array[Int] = (-1 +: input.zipWithIndex.filter(_._1 == '\n').map(_._2).map(_+1)).toArray
def position(label: String = ""): P[Position] = Index.map(i => indexToPosition(i, label))
def indexToPosition(i: Int, label: String): Position = {
var lineNumber = util.Arrays.binarySearch(lineStarts, i)
if (lineNumber < 0) {
lineNumber = - lineNumber - 2
}
val columnNumber = i - lineStarts(lineNumber) + 1
lineNumber += 1
val newPosition = Position(fileId, lineNumber, columnNumber, i)
if (newPosition.cursor > lastPosition.cursor) {
lastPosition = newPosition
lastLabel = label
}
newPosition
}
val comment: P[Unit] = P("//" ~ CharsWhile(c => c != '\n' && c != '\r', min = 0) ~ ("\r\n" | "\r" | "\n"))
val semicolon: P[Unit] = P(";" ~ CharsWhileIn("; \t", min = 0) ~ position("line break after a semicolon").map(_ => ()) ~ (comment | "\r\n" | "\r" | "\n").opaque("<line break>"))
val semicolonComment: P[Unit] = P(";" ~ CharsWhile(c => c != '\n' && c != '\r' && c != '{' && c != '}', min = 0) ~ position("line break instead of braces").map(_ => ()) ~ ("\r\n" | "\r" | "\n").opaque("<line break>"))
val AWS: P[Unit] = P((CharIn(" \t\n\r") | semicolon | comment).rep(min = 0)).opaque("<any whitespace>")
val AWS_asm: P[Unit] = P((CharIn(" \t\n\r") | semicolonComment | comment).rep(min = 0)).opaque("<any whitespace>")
val Before_EOL: P[Unit] = HWS ~ &("\r" | "\n" | ";" | "//").opaque("<line break>")
val EOL: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | semicolon | comment).opaque("<first line break>") ~ AWS).opaque("<line break>")
val EOL_asm: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | semicolonComment | comment).opaque("<first line break>") ~ AWS_asm).opaque("<line break>")
val EOLOrComma: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | "," | semicolon | comment).opaque("<first line break or comma>") ~ AWS).opaque("<line break or comma>")
val elidable: P[Elidability.Value] = (("!" | "?").! ~/ HWS).?.map{
case Some("?") => Elidability.Elidable
case Some("!") => Elidability.Volatile
case _ => Elidability.Fixed
}
val externFunctionBody: P[Option[List[Statement]]] = P("extern" ~/ PassWith(None))
val bankDeclaration: P[Option[String]] = ("segment" ~/ AWS ~/ "(" ~/ AWS ~/ identifier ~/ AWS ~/ ")" ~/ AWS).?
val breakStatement: P[Seq[ExecutableStatement]] = ("break" ~ !letterOrDigit ~/ HWS ~ identifier.?).map(l => Seq(BreakStatement(l.getOrElse(""))))
val continueStatement: P[Seq[ExecutableStatement]] = ("continue" ~ !letterOrDigit ~/ HWS ~ identifier.?).map(l => Seq(ContinueStatement(l.getOrElse(""))))
val forDirection: P[ForDirection.Value] =
("parallel" ~ HWS ~ "to").!.map(_ => ForDirection.ParallelTo) |
("parallel" ~ HWS ~ "until").!.map(_ => ForDirection.ParallelUntil) |
"until".!.map(_ => ForDirection.Until) |
"to".!.map(_ => ForDirection.To) |
("down" ~/ HWS ~/ "to").!.map(_ => ForDirection.DownTo)
private def flags_(allowed: String*): P[Set[String]] = (StringIn(allowed: _*).! ~ SWS).rep(min = 0).map(_.toSet).opaque("<flags>")
val variableFlags: P[Set[String]] = flags_("const", "static", "volatile", "stack", "register")
val functionFlags: P[Set[String]] = flags_("extern", "asm", "inline", "interrupt", "macro", "noinline", "reentrant", "kernal_interrupt", "const")
val codec: P[TextCodecWithFlags] = P(position("text codec identifier") ~ identifier.?.map(_.getOrElse(""))).map { case (position, encoding) =>
val lenient = options.flag(CompilationFlag.LenientTextEncoding)
encoding match {
case "" | "default" => TextCodecWithFlags(options.platform.defaultCodec, nullTerminated = false, lengthPrefixed = false, lenient = lenient)
case "z" | "defaultz" => TextCodecWithFlags(options.platform.defaultCodec, nullTerminated = true, lengthPrefixed = false, lenient = lenient)
case "p" | "pdefault" => TextCodecWithFlags(options.platform.defaultCodec, nullTerminated = false, lengthPrefixed = true, lenient = lenient)
case "pz" | "pdefaultz" => TextCodecWithFlags(options.platform.defaultCodec, nullTerminated = true, lengthPrefixed = true, lenient = lenient)
case "scr" => TextCodecWithFlags(options.platform.screenCodec, nullTerminated = false, lengthPrefixed = false, lenient = lenient)
case "scrz" => TextCodecWithFlags(options.platform.screenCodec, nullTerminated = true, lengthPrefixed = false, lenient = lenient)
case "pscr" => TextCodecWithFlags(options.platform.screenCodec, nullTerminated = false, lengthPrefixed = true, lenient = lenient)
case "pscrz" => TextCodecWithFlags(options.platform.screenCodec, nullTerminated = true, lengthPrefixed = true, lenient = lenient)
case _ => options.textCodecRepository.forName(encoding, Some(position), log)
}
}
// def operator: P[String] = P(CharsWhileIn("!-+*/><=~|&^", min=1).!) // TODO: only valid operators
val charAtom: P[LiteralExpression] = for {
p <- position()
c <- "'" ~/ CharPred(c => c >= ' ' && c != '\'' && !invalidCharLiteralTypes(Character.getType(c))).rep.! ~/ "'"
TextCodecWithFlags(co, zt, pascal, lenient) <- HWS ~ codec
} yield {
if (zt) {
log.error("Zero-terminated encoding is not a valid encoding for a character literal", Some(p))
}
if (pascal) {
log.error("Length-prefixed encoding is not a valid encoding for a character literal", Some(p))
}
co.encode(options.log, Some(p), c.codePoints().toArray.toList, options, lenient = lenient) match {
case List(value) =>
LiteralExpression(value, 1)
case _ =>
log.error(s"Character `$c` cannot be encoded as one byte", Some(p))
LiteralExpression(co.stringTerminator.head, 1)
}
}
//noinspection NameBooleanParameters
val variableAtom: P[Expression] = identifier.map{ i =>
featureConstants.get(i) match {
case Some(value) => LiteralExpression(value, size(value, false, false, false, false, false, false, false))
case None => VariableExpression(i)
}
}
val localLabelAtom : P[Expression] = ("." ~ identifier).!.map(VariableExpression)
val textLiteral: P[List[Expression]] = P(position() ~ doubleQuotedString ~/ HWS ~ codec).map {
case (p, s, TextCodecWithFlags(co, zt, lp, lenient)) =>
var characters = co.encode(options.log, None, s.codePoints().toArray.toList, options, lenient = lenient).map(c => LiteralExpression(c, 1).pos(p))
if (lp) {
val sizeof = co.stringTerminator.length
val codeUnitCount = characters.length / sizeof
val maxAllowed = 1.<<(8*sizeof) - 1
if (codeUnitCount > maxAllowed) {
log.error(s"Length-prefixed string too long, the length is $codeUnitCount, maximum allowed is $maxAllowed", Some(p))
}
characters = (0 until sizeof).map(i => LiteralExpression(codeUnitCount.>>>(8*i).&(0xff), 1)).toList ++ characters
}
if (zt) characters ++= co.stringTerminator.map(nul => LiteralExpression(nul, 1))
characters
}
val textLiteralAtom: P[TextLiteralExpression] = textLiteral.map(TextLiteralExpression)
val literalAtom: P[LiteralExpression] = binaryAtom | hexAtom | octalAtom | quaternaryAtom | decimalAtom | charAtom
val literalAtomWithIntel: P[LiteralExpression] = binaryAtom | hexAtom | octalAtom | quaternaryAtom | intelHexAtom | decimalAtom | charAtom
val atom: P[Expression] = P(position() ~ (variableAtom | localLabelAtom | literalAtom | textLiteralAtom)).map{case (p,a) => a.pos(p)}
val atomWithIntel: P[Expression] = P(position() ~ (variableAtom | localLabelAtom | literalAtomWithIntel | textLiteralAtom)).map{case (p,a) => a.pos(p)}
val quotedAtom: P[String] = variableAtom.! | literalAtomWithIntel.map{
case LiteralExpression(value, _) => value.toString
case x => x.toString
} | textLiteralAtom.!
val importStatement: P[Seq[ImportStatement]] = ("import" ~ !letterOrDigit ~/ SWS ~/
identifier.rep(min = 1, sep = "/") ~ HWS ~ ("<" ~/ HWS ~/ quotedAtom.rep(min = 1, sep = HWS ~ "," ~/ HWS) ~/ HWS ~/ ">" ~/ Pass).?).
map{case (name, params) => Seq(ImportStatement(name.mkString("/"), params.getOrElse(Nil).toList))}
val optimizationHintsDeclaration: P[Set[String]] =
("!" ~/ HWS ~/ identifier ~/ "").rep(min = 0, sep = AWS).map { _.toSet }
val globalVariableDefinition: P[Seq[BankedDeclarationStatement]] = variableDefinition(true)
val localVariableDefinition: P[Seq[DeclarationStatement]] = variableDefinition(false)
def singleVariableDefinition: P[(Position, String, Option[Expression], Option[Expression], Set[String], Option[MemoryAlignment])] = for {
p <- position()
name <- identifier ~/ HWS ~/ Pass
alignment1 <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ HWS
optimizationHints <- optimizationHintsDeclaration ~/ HWS
alignment2 <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ HWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).?.opaque("<address>") ~ HWS
initialValue <- ("=" ~/ AWS ~/ mfExpression(1, false)).? ~/ HWS // TODO
} yield {
if (alignment1.isDefined && alignment2.isDefined) log.error(s"Cannot define the alignment multiple times", Some(p))
val alignment = alignment1.orElse(alignment2)
(p, name, addr, initialValue, optimizationHints, alignment)
}
def variableDefinition(implicitlyGlobal: Boolean): P[Seq[BankedDeclarationStatement]] = for {
p <- position()
bank <- bankDeclaration
flags <- variableFlags ~ HWS
typ <- identifier ~/ SWS
if typ != "array"
vars <- singleVariableDefinition.rep(min = 1, sep = "," ~/ HWS)
_ <- Before_EOL ~/ ""
} yield {
vars.map { case (p, name, addr, initialValue, optimizationHints, alignment) => VariableDeclarationStatement(name, typ,
bank,
global = implicitlyGlobal || flags("static"),
stack = flags("stack"),
constant = flags("const"),
volatile = flags("volatile"),
register = flags("register"),
initialValue, addr, optimizationHints, alignment).pos(p)
}
}
val paramDefinition: P[ParameterDeclaration] = for {
p <- position()
typ <- identifier ~/ SWS ~/ Pass
name <- identifier ~/ Pass
} yield {
if (name == "register" || name == "const" || name == "ref" || name == "call") {
log.error(s"Invalid parameter name: `$name`. Did you mean writing a macro?", Some(p))
}
ParameterDeclaration(typ, ByVariable(name)).pos(p)
}
def asmExpression: P[Expression] = (position() ~ NoCut(
("<" ~/ HWS ~ mfExpression(mathLevel, allowIntelHexAtomsInAssembly)).map(e => HalfWordExpression(e, hiByte = false)) |
(">" ~/ HWS ~ mfExpression(mathLevel, allowIntelHexAtomsInAssembly)).map(e => HalfWordExpression(e, hiByte = true)) |
mfExpression(mathLevel, allowIntelHexAtomsInAssembly)
)).map { case (p, e) => e.pos(p) }
def asmExpressionWithParens: P[(Expression, Boolean)] = (position() ~ NoCut(
("(" ~ HWS ~ asmExpression ~ HWS ~ ")").map(_ -> true) |
asmExpression.map(_ -> false)
)).map { case (p, e) => e._1.pos(p) -> e._2 }
def asmExpressionWithParensOrApostrophe: P[(Expression, Boolean)] = (position() ~ NoCut(
("(" ~ HWS ~ asmExpression ~ HWS ~ ")").map(_ -> true) |
(asmExpression ~ "'").map(_ -> true) |
asmExpression.map(_ -> false)
)).map { case (p, e) => e._1.pos(p) -> e._2 }
def appcRegister: P[ParamPassingConvention]
val appcComplex: P[ParamPassingConvention] =
for {
pos <- position("passing method")
keyword <- (("const" | "ref" | "register" | "call").! ~ !letterOrDigit ~/ Pass).? ~/ Pass
_ <- if (keyword.contains("call")) {log.error(s"Invalid assembly macro parameter passing convention: `call`", Some(pos)) ; Pass } else Pass
if !keyword.contains("call")
_ <- position("register name")
register <- keyword match {
case Some("register") => (AWS ~ "(" ~/ AWS ~ appcRegister ~/ AWS ~ ")" ~/ AWS).map(Some(_))
case Some(_) => SWS.map(_ => None)
case None => Pass.map(_ => None)
}
_ <- position("parameter name")
ident <- identifier
} yield ((keyword, register, ident) match {
case (None, _, name) => ByVariable(name)
case (Some("const"), _, name) => ByConstant(name)
case (Some("ref"), _, name) => ByReference(name)
case (Some("register"), Some(reg), _) => reg
case x => log.fatal(s"Unknown assembly parameter passing convention: `$x`")
})
val asmParamDefinition: P[ParameterDeclaration] = for {
p <- position()
typ <- identifier ~ SWS ~/ Pass
appc <- appcRegister | appcComplex
} yield ParameterDeclaration(typ, appc).pos(p)
val macroParamDefinition: P[ParameterDeclaration] = for {
p <- position()
typ <- identifier ~ SWS ~/ Pass
pos <- position("passing method")
keyword <- ( ("const" | "ref" | "call" | "register").! ~ !letterOrDigit ~/ Pass).?
_ <- if (keyword.contains("register")) {log.error(s"Invalid non-assembly macro parameter passing convention: `register`. Did you forget `asm`?", Some(pos)) ; Pass } else Pass
if !keyword.contains("register")
_ <- if (keyword.isDefined) SWS else Pass
_ <- position("parameter name")
name <- identifier
} yield ParameterDeclaration(typ, (keyword match {
case None => ByReference(name)
case Some("ref") => ByReference(name)
case Some("const") => ByConstant(name)
case Some("call") => ByLazilyEvaluableExpressionVariable(name)
case Some(x) => log.fatal(s"Invalid non-assembly macro parameter passing convention: `$x`")
})).pos(p)
def arrayListElement: P[ArrayContents] = arrayStringContents | arrayProcessedContents | arrayLoopContents | arrayFileContents | mfExpression(nonStatementLevel, false).map(e => LiteralContents(List(e)))
def arrayProcessedContents: P[ArrayContents] = for {
_ <- "@" ~/ HWS
filter <- identifier
_ <- AWS
contents <- arrayContents
} yield ProcessedContents(filter, contents)
def arrayListContents: P[ArrayContents] = ("[" ~/ AWS ~/ arrayListElement.rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ "]" ~/ Pass).map(c => CombinedContents(c.toList))
// TODO: should reserve the `file` identifier here?
val arrayFileContents: P[ArrayContents] = for {
p <- "file" ~ HWS ~/ "(" ~/ HWS ~/ position("file name")
filePath <- doubleQuotedString ~/ HWS
_ <- position("file start")
optStart <- ("," ~/ HWS ~/ literalAtom ~/ HWS ~/ Pass).?
_ <- position("slice length")
optLength <- ("," ~/ HWS ~/ literalAtom ~/ HWS ~/ Pass).?
_ <- position("closing parentheses")
_ <- ")" ~/ Pass
} yield {
val data = Files.readAllBytes(Paths.get(currentDirectory, filePath))
val slice: Array[Byte] = (optStart.map(_.value.toInt), optLength.map(_.value.toInt)) match {
case (Some(start), Some(length)) =>
if (data.length < start) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset $start", Some(p))
Array.fill(length)(0.toByte)
} else if (data.length < start + length) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset plus length ${start + length}", Some(p))
Array.fill(length)(0.toByte)
} else {
data.slice(start, start + length)
}
case (Some(start), None) =>
if (data.length < start) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset $start", Some(p))
Array[Byte](0)
} else {
data.drop(start)
}
case (None, None) => data
case _ => throw new IllegalStateException("error parsing file()")
}
LiteralContents(slice.map(c => LiteralExpression(c & 0xff, 1)).toList)
}
def arrayStringContents: P[ArrayContents] = textLiteral.map(LiteralContents)
def arrayLoopContents: P[ArrayContents] = for {
identifier <- "for" ~ SWS ~/ identifier ~/ HWS ~ "," ~/ HWS ~ Pass
start <- mfExpression(nonStatementLevel, false) ~ HWS ~ "," ~/ HWS ~/ Pass
pos <- position("loop direction")
direction <- forDirection ~/ HWS ~/ "," ~/ HWS ~/ Pass
end <- mfExpression(nonStatementLevel, false, allowTopLevelIndexing = false)
body <- AWS ~ arrayContents
} yield {
val fixedDirection = direction match {
case ForDirection.ParallelUntil =>
if (options.flag(CompilationFlag.FallbackValueUseWarning)) log.warn("`paralleluntil` is not allowed in array definitions, assuming `until`", Some(pos))
ForDirection.Until
case ForDirection.ParallelTo =>
if (options.flag(CompilationFlag.FallbackValueUseWarning)) log.warn("`parallelto` is not allowed in array definitions, assuming `to`", Some(pos))
ForDirection.To
case x => x
}
ForLoopContents(identifier, start, end, fixedDirection, body)
}
def arrayContents: P[ArrayContents] = arrayProcessedContents | arrayListContents | arrayLoopContents | arrayFileContents | arrayStringContents
def arrayContentsForAsm: P[RawBytesStatement] = (arrayListContents | arrayStringContents).map(c => RawBytesStatement(c, options.isBigEndian))
val aliasDefinition: P[Seq[AliasDefinitionStatement]] = for {
p <- position()
name <- "alias" ~ !letterOrDigit ~/ SWS ~ identifier ~ HWS
target <- "=" ~/ AWS ~/ identifier ~/ HWS
important <- "!".!.? ~/ HWS
} yield Seq(AliasDefinitionStatement(name, target, important.isDefined).pos(p))
def fastAlignmentForArrays: MemoryAlignment
def fastAlignmentForFunctions: MemoryAlignment
def alignmentDeclaration(fast: MemoryAlignment): P[MemoryAlignment] = (position() ~ "align" ~/ AWS ~/ "(" ~/ AWS ~/ atom ~/ AWS ~/ ")").map {
case (_, LiteralExpression(1, _)) => NoAlignment
case (pos, LiteralExpression(n, _)) =>
if (n >= 1 && n <= 0x8000 & (n & (n - 1)) == 0) DivisibleAlignment(n.toInt)
else {
log.error("Invalid alignment: " + n, Some(pos))
NoAlignment
}
case (pos, VariableExpression("fast")) => fast
case (pos, _) =>
log.error("Invalid alignment", Some(pos))
NoAlignment
}
val arrayDefinition: P[Seq[ArrayDeclarationStatement]] = for {
p <- position()
bank <- bankDeclaration
const <- ("const".! ~ HWS).?
_ <- "array" ~ !letterOrDigit
elementType <- ("(" ~/ AWS ~/ identifier ~ AWS ~ ")").? ~/ HWS
name <- identifier ~/ HWS
length <- ("[" ~/ AWS ~/ mfExpression(nonStatementLevel, false) ~ AWS ~ "]").? ~ HWS
alignment1 <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ HWS
optimizationHints <- optimizationHintsDeclaration ~/ HWS
alignment2 <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ HWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).? ~/ HWS
contents <- ("=" ~/ AWS ~/ arrayContents).? ~/ HWS
} yield {
if (alignment1.isDefined && alignment2.isDefined) log.error(s"Cannot define the alignment multiple times", Some(p))
val alignment = alignment1.orElse(alignment2)
Seq(ArrayDeclarationStatement(name, bank, length, elementType.getOrElse("byte"), addr, const.isDefined, contents, optimizationHints, alignment, options.isBigEndian).pos(p))
}
def tightMfExpression(allowIntelHex: Boolean, allowTopLevelIndexing: Boolean): P[Expression] = {
val a = if (allowIntelHex) atomWithIntel else atom
if (allowTopLevelIndexing)
mfExpressionWrapper[Expression](mfParenExpr(allowIntelHex) | derefExpression | functionCall(allowIntelHex) | a)
else
mfParenExpr(allowIntelHex) | derefExpression | functionCall(allowIntelHex) | a
}
def tightMfExpressionButNotCall(allowIntelHex: Boolean, allowTopLevelIndexing: Boolean): P[Expression] = {
val a = if (allowIntelHex) atomWithIntel else atom
if (allowTopLevelIndexing)
mfExpressionWrapper[Expression](mfParenExpr(allowIntelHex) | derefExpression | a)
else
mfParenExpr(allowIntelHex) | derefExpression | a
}
def mfExpression(level: Int, allowIntelHex: Boolean, allowTopLevelIndexing: Boolean = true): P[Expression] = {
val allowedOperators = mfOperatorsDropFlatten(level)
def inner: P[SeparatedList[(Boolean, Expression), String]] = {
for {
minus <- ("-".rep(min = 1).!.map(_.length().&(1).==(1)) ~/ HWS).?.map(_.getOrElse(false))
head <- tightMfExpression(allowIntelHex, allowTopLevelIndexing) ~/ HWS
maybeOperator <- (StringIn(allowedOperators: _*).! ~ !CharIn(Seq('/', '=', '-', '+', ':', '>', '<', '\''))).map(op => if (mfOperatorNormalizations.contains(op)) mfOperatorNormalizations(op) else op).?
maybeTail <- maybeOperator.fold[P[Option[List[(String, (Boolean, Expression))]]]](Pass.map(_ => None))(o => (AWS ~/ inner ~/ HWS).map(x2 => Some((o -> x2.head) :: x2.tail)))
} yield {
maybeTail.fold[SeparatedList[(Boolean, Expression), String]](SeparatedList.of(minus -> head))(t => SeparatedList(minus -> head, t))
}
}
def p(list: SeparatedList[(Boolean, Expression), String], level: Int): Expression =
if (level == mfOperators.length) {
if (list.head._1) {
LiteralExpression(0, 1) #-# list.head._2
} else {
list.head._2
}
} else {
val xs = list.split(mfOperators(level).toSet(_))
xs.separators.distinct match {
case Nil =>
if (xs.tail.nonEmpty)
log.error("Too many different operators; consider using parentheses for disambiguation", xs.head.head._2.position)
p(xs.head, level + 1)
case List("+") | List("-") | List("+", "-") | List("-", "+") =>
SumExpression(xs.toPairList("+").map {
case (op, value) =>
if (value.count(_._1) > 0) {
if (value.size == 1) {
log.error("Too many different operators; consider using parentheses for disambiguation", xs.head.head._2.position)
}
(op == "+", p(value.map(p => (false, p._2)), level + 1))
} else {
(op == "-", p(value, level + 1))
}
}, decimal = false).pos(list.head._2.position)
case List("+'") | List("-'") | List("+'", "-'") | List("-'", "+'") =>
SumExpression(xs.toPairList("+").map { case (op, value) =>
if (value.exists(_._1)) log.error("Too many different operators; consider using parentheses for disambiguation", xs.head.head._2.position)
(op == "-'", p(value, level + 1))
}, decimal = true).pos(list.head._2.position)
case List(":") =>
if (xs.size != 2) {
log.error("The `:` operator can have only two arguments", xs.head.head._2.position)
LiteralExpression(0, 1)
} else {
SeparateBytesExpression(p(xs.head, level + 1), p(xs.tail.head._2, level + 1)).pos(list.head._2.position)
}
case List(eq) if level == 0 =>
if (xs.size != 2) {
log.error(s"The `$eq` operator can have only two arguments", xs.head.head._2.position)
LiteralExpression(0, 1)
} else {
FunctionCallExpression(eq, xs.items.map(value => p(value, level + 1))).pos(list.head._2.position)
}
case List(op) =>
FunctionCallExpression(op, xs.items.map(value => p(value, level + 1))).pos(list.head._2.position)
case _ =>
log.error("Too many different operators; consider using parentheses for disambiguation", xs.head.head._2.position)
LiteralExpression(0, 1)
}
}
inner.map(x => p(x, 0))
}
def index: P[Expression] = HWS ~ "[" ~/ AWS ~/ mfExpression(nonStatementLevel, false) ~ AWS ~/ "]" ~/ Pass
def mfExpressionWrapper[E <: Expression](inner: P[E]): P[E] = for {
expr <- inner
firstIndices <- index.rep
fieldPath <- (HWS ~ (("->".! ~/ AWS) | ".".!) ~/ AWS ~/ identifier ~/ index.rep).rep
} yield (expr, firstIndices, fieldPath) match {
case (_, Seq(), Seq()) => expr
case (VariableExpression(vname), Seq(i), Seq()) => IndexedExpression(vname, i).pos(expr.position).asInstanceOf[E]
case _ =>
val fixedFieldPath = fieldPath.flatMap { e =>
e match {
case (".", "pointer", _) => Seq(e)
case (".", f, _) if f.startsWith("pointer.") => Seq(e)
case (".", "addr", _) => Seq(e)
case (".", f, _) if f.startsWith("addr.") => Seq(e)
case (".", f, i) => Seq((".", "pointer", Nil), ("->", f, i))
case _ => Seq(e)
}
}
IndirectFieldExpression(expr, firstIndices, fixedFieldPath.map {case (a,b,c) => (a == ".", b, c)}).pos(expr.position).asInstanceOf[E]
}
// def mfLhsExpression: P[LhsExpression] = for {
// (p, left) <- position() ~ mfLhsExpressionSimple
// rightOpt <- (HWS ~ ":" ~/ HWS ~ mfLhsExpressionSimple).?
// } yield rightOpt.fold(left)(right => SeparateBytesExpression(left, right).pos(p))
def mfLhsExpressionSimple: P[LhsExpression] =
mfExpressionWrapper[LhsExpression](derefExpression | (position() ~ identifier).map{case (p,n) => VariableExpression(n).pos(p)} ~ HWS)
def mfLhsExpression: P[LhsExpression] =
mfExpression(nonStatementLevel, false).filter(_.isInstanceOf[LhsExpression]).map(_.asInstanceOf[LhsExpression])
def mfParenExpr(allowIntelHex: Boolean): P[Expression] = P("(" ~/ AWS ~/ mfExpression(nonStatementLevel, allowIntelHex) ~ AWS ~/ ")")
def functionCall(allowIntelHex: Boolean): P[FunctionCallExpression] = for {
p <- position()
name <- identifier
params <- HWS ~ "(" ~/ AWS ~/ mfExpression(nonStatementLevel, allowIntelHex).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ ""
} yield FunctionCallExpression(name, params.toList).pos(p)
val derefExpression: P[DerefDebuggingExpression] = for {
p <- position()
if enableDebuggingOptions
yens <- CharsWhileIn(Seq('¥')).! ~/ AWS
inner <- mfParenExpr(false)
} yield DerefDebuggingExpression(inner, yens.length).pos(p)
val expressionStatement: P[Seq[ExecutableStatement]] = mfExpression(0, false).map {
case FunctionCallExpression("=", List(t: LhsExpression, s)) =>
Seq(Assignment(t, s).pos(t.position))
case x@FunctionCallExpression("=", exprs) =>
log.error("Invalid left-hand-side of an assignment", x.position)
exprs.map(ExpressionStatement)
case x =>
Seq(ExpressionStatement(x).pos(x.position))
}
def keywordStatement: P[Seq[ExecutableStatement]] = P(
returnOrDispatchStatement |
gotoStatement |
labelStatement |
ifStatement |
whileStatement |
forEachStatement |
forStatement |
doWhileStatement |
breakStatement |
continueStatement |
inlineAssembly)
def executableStatement: P[Seq[ExecutableStatement]] = (position() ~ P(keywordStatement | expressionStatement)).map { case (p, s) => s.map(_.pos(p)) }
def asmLabel: P[ExecutableStatement]
def asmStatement: P[ExecutableStatement]
def statement: P[Seq[Statement]] = (position() ~ P(keywordStatement | arrayDefinition | localVariableDefinition | expressionStatement)).map { case (p, s) => s.map(_.pos(p)) }
def asmStatements: P[List[ExecutableStatement]] = ("{" ~/ AWS_asm ~/ (asmLabel.rep() ~ asmStatement.?).rep(sep = NoCut(EOL_asm) ~ !"}" ~/ Pass) ~/ AWS_asm ~/ "}" ~/ Pass).map(e => e.flatMap(x => (x._1 ++ x._2.toSeq).toList)).map(_.toList)
def statements: P[List[Statement]] = ("{" ~/ AWS ~ statement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.flatten.toList)
def mfFunctionSmallBody: P[List[Statement]] = for {
_ <- "=" ~/ AWS
expression <- mfExpression(nonStatementLevel, false)
} yield List(ReturnStatement(Some(expression)).pos(expression.position))
def mfFunctionBody: P[List[Statement]] = statements | mfFunctionSmallBody
def executableStatements: P[Seq[ExecutableStatement]] = ("{" ~/ AWS ~/ executableStatement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~ "}").map(_.flatten)
val dispatchLabel: P[ReturnDispatchLabel] =
("default" ~ !letterOrDigit ~/ AWS ~/ ("(" ~/ position("default branch range") ~ AWS ~/ mfExpression(nonStatementLevel, false).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?).map{
case None => DefaultReturnDispatchLabel(None, None)
case Some((_, Seq())) => DefaultReturnDispatchLabel(None, None)
case Some((_, Seq(e))) => DefaultReturnDispatchLabel(None, Some(e))
case Some((_, Seq(s, e))) => DefaultReturnDispatchLabel(Some(s), Some(e))
case Some((pos, _)) =>
log.error("Invalid default branch declaration", Some(pos))
DefaultReturnDispatchLabel(None, None)
} | mfExpression(nonStatementLevel, false).rep(min = 0, sep = AWS ~ "," ~/ AWS).map(exprs => StandardReturnDispatchLabel(exprs.toList))
val dispatchBranch: P[ReturnDispatchBranch] = for {
pos <- position()
l <- dispatchLabel ~/ HWS ~/ "@" ~/ HWS
f <- tightMfExpressionButNotCall(false, allowTopLevelIndexing = false) ~/ HWS
parameters <- ("(" ~/ position("dispatch actual parameters") ~ AWS ~/ mfExpression(nonStatementLevel, false).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?
} yield ReturnDispatchBranch(l, f, parameters.map(_._2.toList).getOrElse(Nil)).pos(pos)
val dispatchStatementBody: P[Seq[ExecutableStatement]] = for {
indexer <- "[" ~/ AWS ~/ mfExpression(nonStatementLevel, false) ~/ AWS ~/ "]" ~/ AWS
_ <- position("dispatch statement body")
parameters <- ("(" ~/ position("dispatch parameters") ~ AWS ~/ mfLhsExpression.rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?
_ <- AWS ~/ position("dispatch statement body") ~/ "{" ~/ AWS
branches <- dispatchBranch.rep(sep = EOL ~ !"}" ~/ Pass)
_ <- AWS ~/ "}"
} yield Seq(ReturnDispatchStatement(indexer, parameters.map(_._2.toList).getOrElse(Nil), branches.toList))
val returnOrDispatchStatement: P[Seq[ExecutableStatement]] = "return" ~ !letterOrDigit ~/ HWS ~ (dispatchStatementBody | mfExpression(nonStatementLevel, false).?.map(ReturnStatement).map(Seq(_)))
val gotoStatement: P[Seq[ExecutableStatement]] = "goto" ~ !letterOrDigit ~/ HWS ~ mfExpression(nonStatementLevel, false).map(GotoStatement).map(Seq(_))
val labelStatement: P[Seq[ExecutableStatement]] = "label" ~ !letterOrDigit ~/ HWS ~ identifier.map(LabelStatement).map(Seq(_))
def ifStatement: P[Seq[ExecutableStatement]] = for {
condition <- "if" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
thenBranch <- AWS ~/ executableStatements
elseBranch <- (AWS ~ "else" ~/ AWS ~/ ((for{
p <- position()
s <- ifStatement
} yield s.map(_.pos(p))) | executableStatements)).?
} yield Seq(IfStatement(condition, thenBranch.toList, elseBranch.getOrElse(Nil).toList))
def whileStatement: P[Seq[ExecutableStatement]] = for {
condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
body <- AWS ~ executableStatements
} yield Seq(WhileStatement(condition, body.toList, Nil))
def forStatement: P[Seq[ExecutableStatement]] = for {
identifier <- "for" ~/ SWS ~/ identifier ~/ HWS ~ "," ~/ HWS ~ Pass
start <- mfExpression(nonStatementLevel, false) ~ HWS ~ "," ~/ HWS ~/ Pass
direction <- forDirection ~/ HWS ~/ "," ~/ HWS ~/ Pass
end <- mfExpression(nonStatementLevel, false)
body <- AWS ~ executableStatements
} yield Seq(ForStatement(identifier, start, end, direction, body.toList))
def forEachStatement: P[Seq[ExecutableStatement]] = for {
id <- "for" ~ SWS ~ identifier ~ HWS ~ ("," ~ HWS ~ identifier ~ HWS).? ~ ":" ~/ HWS ~ Pass
values <- ("[" ~/ AWS ~/ mfExpression(0, false).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ "]" ~/ "").map(seq => Right(seq.toList)) | mfExpression(0, false).map(Left(_))
body <- AWS ~ executableStatements
} yield Seq(ForEachStatement(id._1, id._2, values, body.toList))
def inlineAssembly: P[Seq[ExecutableStatement]] = "asm" ~ !letterOrDigit ~/ AWS ~ asmStatements
//noinspection MutatorLikeMethodIsParameterless
def doWhileStatement: P[Seq[ExecutableStatement]] = for {
body <- "do" ~ !letterOrDigit ~/ AWS ~ executableStatements ~/ AWS
condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
} yield Seq(DoWhileStatement(body.toList, Nil, condition))
val functionDefinition: P[Seq[BankedDeclarationStatement]] = for {
p <- position()
bank <- bankDeclaration
flags <- functionFlags ~ HWS
returnType <- identifier ~ SWS
if !Environment.neverValidTypeIdentifiers(returnType)
name <- identifier ~ HWS
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else if (flags("macro")) macroParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS
alignment1 <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ AWS
optimizationHints <- optimizationHintsDeclaration ~/ HWS
alignment2 <- alignmentDeclaration(fastAlignmentForFunctions).? ~/ AWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).?.opaque("<address>") ~/ AWS
statements <- (externFunctionBody | (if (flags("asm")) asmStatements else mfFunctionBody).map(l => Some(l))) ~/ Pass
} yield {
if (alignment1.isDefined && alignment2.isDefined) log.error(s"Cannot define the alignment multiple times", Some(p))
val alignment = alignment1.orElse(alignment2)
if (flags("extern")) log.error("The extern keyword should go at the end of a function declaration", Some(p))
if (flags("interrupt") && flags("macro")) log.error(s"Interrupt function `$name` cannot be macros", Some(p))
if (flags("kernal_interrupt") && flags("macro")) log.error(s"Kernal interrupt function `$name` cannot be macros", Some(p))
if (flags("interrupt") && flags("reentrant")) log.error(s"Interrupt function `$name` cannot be reentrant", Some(p))
if (flags("interrupt") && flags("kernal_interrupt")) log.error(s"Interrupt function `$name` cannot be a Kernal interrupt", Some(p))
if (flags("macro") && flags("reentrant")) log.error("Reentrant and macro exclude each other", Some(p))
if (flags("inline") && flags("noinline")) log.error("Noinline and inline exclude each other", Some(p))
if (flags("macro") && flags("noinline")) log.error("Noinline and macro exclude each other", Some(p))
if (flags("inline") && flags("macro")) log.error("Macro and inline exclude each other", Some(p))
if (flags("interrupt") && returnType != "void") log.error(s"Interrupt function `$name` has to return void", Some(p))
if (flags("const") && returnType == "void") log.error(s"Const-pure function `$name` cannot return void", Some(p))
if (flags("const") && flags("interrupt")) log.error(s"Const-pure function `$name` cannot be an interrupt", Some(p))
if (flags("const") && flags("kernal_interrupt")) log.error(s"Const-pure function `$name` cannot be a Kernal interrupt", Some(p))
if (flags("const") && flags("macro")) log.error(s"Const-pure function `$name` cannot be a macro", Some(p))
if (flags("const") && flags("asm")) log.error(s"Const-pure function `$name` cannot contain assembly", Some(p))
if (addr.isEmpty && statements.isEmpty) log.error(s"Extern function `$name` must have an address", Some(p))
if (addr.isDefined && alignment.isDefined) log.error(s"Function `$name` has both address and alignment", Some(p))
if (statements.isEmpty && alignment.isDefined) log.error(s"Extern function `$name` cannot have alignment", Some(p))
if (statements.isEmpty && !flags("asm") && params.nonEmpty) log.error(s"Extern non-asm function `$name` cannot have parameters", Some(p))
if (flags("asm")) validateAsmFunctionBody(p, flags, name, statements)
if (flags("macro")) {
statements.flatMap(_.find(_.isInstanceOf[VariableDeclarationStatement])) match {
case Some(s) =>
log.error(s"Macro functions cannot declare variables", s.position)
case None =>
}
statements.flatMap(_.find(_.isInstanceOf[ArrayDeclarationStatement])) match {
case Some(s) =>
log.error(s"Macro functions cannot declare arrays", s.position)
case None =>
}
}
Seq(FunctionDeclarationStatement(name, returnType, params.toList,
bank,
addr,
optimizationHints,
alignment,
statements,
flags("macro"),
if (flags("inline")) Some(true) else if (flags("noinline")) Some(false) else None,
flags("asm"),
flags("interrupt"),
flags("kernal_interrupt"),
flags("const") && !flags("asm"),
flags("reentrant")).pos(p))
}
def validateAsmFunctionBody(p: Position, flags: Set[String], name: String, statements: Option[List[Statement]])
val enumVariant: P[(String, Option[Expression])] = for {
name <- identifier ~/ HWS
value <- ("=" ~/ AWS ~/ mfExpression(1, false)).? ~ 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 compoundTypeField: P[FieldDesc] = ("array".! ~ !letterOrDigit ~ HWS ~/ Pass).?.flatMap {
case None =>
(identifier ~/ HWS ~ identifier ~/ HWS).map {
case (typ, name) => FieldDesc(typ, name, None)
}
case Some(_) =>
(("(" ~/ AWS ~/ identifier ~ AWS ~ ")").? ~/ HWS ~/
identifier ~ HWS ~
"[" ~/ AWS ~/ mfExpression(nonStatementLevel, false) ~ AWS ~ "]" ~/ HWS
).map{
case (elementType, name, length) =>
FieldDesc(elementType.getOrElse("byte"), name, Some(length))
}
}
val compoundTypeFields: P[List[FieldDesc]] =
("{" ~/ AWS ~ compoundTypeField.rep(sep = NoCut(EOLOrComma) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList)
val structDefinition: P[Seq[StructDefinitionStatement]] = {
(position() ~ "struct" ~ !letterOrDigit ~/ SWS ~/
position("struct name").map(_ => ()) ~ identifier ~/ HWS ~
alignmentDeclaration(NoAlignment).? ~/ HWS ~
position("struct definition block").map(_ => ()) ~
compoundTypeFields ~/ Pass).map{
case (p, name, align, fields) =>
Seq(StructDefinitionStatement(name, fields, align).pos(p))
}
}
val unionDefinition: P[Seq[UnionDefinitionStatement]] = {
(position() ~ "union" ~ !letterOrDigit ~/ SWS ~/
position("union name").map(_ => ()) ~ identifier ~/ HWS ~
alignmentDeclaration(NoAlignment).? ~/ HWS ~
position("union definition block").map(_ => ()) ~
compoundTypeFields ~/ Pass).map{
case (p, name, align, fields) =>
Seq(UnionDefinitionStatement(name, fields, align).pos(p))
}
}
val segmentBlock: P[Seq[BankedDeclarationStatement]] = for {
(_, bankName) <- "segment" ~ AWS ~ "(" ~ AWS ~ position("segment name") ~ identifier ~ AWS ~ ")" ~ AWS ~ "{" ~/ AWS
body <- locatableDefinition.rep(sep = EOL)
_ <- AWS ~ "}" ~/ Pass
} yield {
body.flatten.map { stmt =>
if (stmt.bank.isEmpty) stmt.withChangedBank(bankName)
else stmt
}
}
def checkForNonlocatableDefinitions: P[Seq[BankedDeclarationStatement]] =
((StringIn("alias", "enum", "struct", "union", "import").! ~ SWS) ~/ position()).map{ x =>
log.fatal(s"`${x._1}` statements are not allowed inside segment blocks", Some(x._2))
}
def locatableDefinition: P[Seq[BankedDeclarationStatement]] = checkForNonlocatableDefinitions | segmentBlock | arrayDefinition | functionDefinition | globalVariableDefinition
val program: Parser[Program] = for {
_ <- Start ~/ AWS ~/ position("top level statement")
definitions <- (importStatement | aliasDefinition | enumDefinition | structDefinition | unionDefinition | locatableDefinition).rep(sep = EOL)
_ <- AWS ~ End
} yield Program(definitions.flatten.toList)
}
object MfParser {
val SWS: P[Unit] = P(CharsWhileIn(" \t", min = 1)).opaque("<horizontal whitespace>")
val HWS: P[Unit] = P(CharsWhileIn(" \t", min = 0)).opaque("<horizontal whitespace>")
val letter: P[String] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_").!)
val mosOpcodeLetter: P[String] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012").!)
val octalDigit: P[String] = P(CharIn("01234567").!)
val letterOrDigit: P[Unit] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_.$1234567890"))
val realLetterOrDigit: P[Unit] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_.1234567890"))
val identifierTail: P[String] =
CharsWhileIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_.1234567890", min = 1).rep(min = 1, sep = "$").!
val identifier: P[String] = (letter ~ ("$".? ~ identifierTail).?).!.map(_.intern()).opaque("<identifier>")
val doubleQuotedString: P[String] = P("\"" ~/ CharsWhile(c => c != '\"' && c != '\n' && c != '\r').?.! ~ "\"")
def size(value: Long, wordLiteral: Boolean, int24Literal: Boolean, int32Literal: Boolean, int40Literal: Boolean, int48Literal: Boolean, int56Literal: Boolean, int64Literal: Boolean): Int = {
val w = value > 0xff || value < -0x80 || wordLiteral
val f = value > 0xffff || value < -0x8000 || int24Literal
val l = value > 0xffffff || value < -0x800000 || int32Literal
val q5 = value > 0xFFFFffffL || value < -0x80000000L || int40Literal
val q6 = value > 0xffFFFFffffL || value < -0x8000000000L || int48Literal
val q7 = value > 0xffffFFFFffffL || value < -0x800000000000L || int56Literal
val q8 = value > 0xFFffffFFFFffffL || value < -0x8000000000000L || int64Literal
if (q8) 8 else if (q7) 7 else if (q6) 6 else if (q5) 5 else if (l) 4 else if (f) 3 else if (w) 2 else 1
}
def sign(abs: Long, minus: Boolean): Long = if (minus) -abs else abs
val invalidCharLiteralTypes: BitSet = BitSet(
Character.LINE_SEPARATOR,
Character.PARAGRAPH_SEPARATOR,
Character.CONTROL,
Character.PRIVATE_USE,
Character.SURROGATE,
Character.UNASSIGNED)
val decimalAtom: P[LiteralExpression] =
for {
minus <- "-".!.?
s <- CharsWhileIn("1234567890", min = 1).!.opaque("<decimal digits>") ~ !("x" | "b")
} yield {
val abs = parseLong(s, 10)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 3, s.length > 5, s.length > 7, s.length > 10, s.length > 13, s.length > 15, s.length > 17))
}
val binaryAtom: P[LiteralExpression] =
for {
minus <- "-".!.?
_ <- P("0b" | "%") ~/ Pass
s <- CharsWhileIn("01", min = 1).!.opaque("<binary digits>")
} yield {
val abs = parseLong(s, 2)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 8, s.length > 16, s.length > 24, s.length > 32, s.length > 40, s.length > 48, s.length > 52))
}
val hexAtom: P[LiteralExpression] =
for {
minus <- "-".!.?
_ <- P("0x" | "0X" | "$") ~/ Pass
s <- CharsWhileIn("1234567890abcdefABCDEF", min = 1).!.opaque("<hex digits>")
} yield {
val abs = parseLong(s, 16)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 2, s.length > 4, s.length > 6, s.length > 8, s.length > 10, s.length > 12, s.length > 14))
}
val intelHexAtom: P[LiteralExpression] =
for {
minus <- "-".!.?
head <- CharIn("0123456789").!
tail <- CharsWhileIn("1234567890abcdefABCDEF", min = 1).!.opaque("<hex digits>")
_ <- P("h" | "H")
} yield {
// check for marking zero:
val s = if (head == "0" && tail.nonEmpty && tail.head >'9') tail else head + tail
val abs = parseLong(s, 16)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 2, s.length > 4, s.length > 6, s.length > 8, s.length > 10, s.length > 12, s.length > 14))
}
val octalAtom: P[LiteralExpression] =
for {
minus <- "-".!.?
_ <- P("0o" | "0O") ~/ Pass
s <- CharsWhileIn("01234567", min = 1).!.opaque("<octal digits>")
} yield {
val abs = parseLong(s, 8)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 3, s.length > 6, s.length > 8, s.length > 11, s.length > 14, s.length > 16, s.length > 19))
}
val quaternaryAtom: P[LiteralExpression] =
for {
minus <- "-".!.?
_ <- P("0q" | "0Q") ~/ Pass
s <- CharsWhileIn("0123", min = 1).!.opaque("<quaternary digits>")
} yield {
val abs = parseLong(s, 4)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 4, s.length > 8, s.length > 12, s.length > 16, s.length > 20, s.length > 24, s.length > 28))
}
val mfOperators = List(
List("+=", "-=", "+'=", "-'=", "^=", "&=", "|=", "*=", "*'=", "<<=", ">>=", "<<'=", ">>'=", "/=", "%%=", "=", "$*=", "$+=", "$-=", "$<<=", "$>>="),
List("||", "^^"),
List("&&"),
List("==", "<=", ">=", "!=", "<", ">"),
List(":"),
List("+'", "-'", "<<'", ">>'", ">>>>", "+", "-", "&", "|", "^", "<<", ">>", "$+", "$-", "$<<", "$>>"),
List("*'", "$*", "*", "/", "%%"))
val mfOperatorNormalizations = Map(
"$+" -> "+'",
"$-" -> "-'",
"$+=" -> "+'=",
"$-=" -> "-'=",
"$<<" -> "<<'",
"$>>" -> ">>'",
"$<<=" -> "<<'=",
"$>>=" -> ">>'=",
"$*" -> "*'",
"$*=" -> "*'=",
)
val mfOperatorsDropFlatten: IndexedSeq[List[String]] = mfOperators.indices.map(i => mfOperators.drop(i).flatten)
val nonStatementLevel = 1 // everything but not `=`
val mathLevel = 4 // the `:` operator
val minusLevel = 5 // the `-` operator
}