feat: merge master

Merge request lab2425_spring/WACC_37!25

Co-authored-by: Gleb Koval <gleb@koval.net>
This commit is contained in:
Ling, Alex 2025-02-21 23:09:13 +00:00
commit de181d161d
6 changed files with 280 additions and 87 deletions

View File

@ -45,6 +45,7 @@ compile_jvm:
- .scala-build/ - .scala-build/
test_jvm: test_jvm:
image: gumjoe/wacc-ci-scala:x86
stage: test stage: test
# Use our own runner (not cloud VM or shared) to ensure we have multiple cores. # Use our own runner (not cloud VM or shared) to ensure we have multiple cores.
tags: [ large ] tags: [ large ]

View File

@ -3,8 +3,8 @@
// dependencies // dependencies
//> using dep com.github.j-mie6::parsley::5.0.0-M10 //> using dep com.github.j-mie6::parsley::5.0.0-M10
//> using dep com.github.j-mie6::parsley-cats::1.3.0 //> using dep com.github.j-mie6::parsley-cats::1.5.0
//> using dep com.lihaoyi::os-lib::0.11.3 //> using dep com.lihaoyi::os-lib::0.11.4
//> using dep com.github.scopt::scopt::4.1.0 //> using dep com.github.scopt::scopt::4.1.0
//> using test.dep org.scalatest::scalatest::3.2.19 //> using test.dep org.scalatest::scalatest::3.2.19

View File

@ -4,6 +4,10 @@ import scala.collection.mutable
import parsley.{Failure, Success} import parsley.{Failure, Success}
import scopt.OParser import scopt.OParser
import java.io.File import java.io.File
import java.io.PrintStream
import assemblyIR as asm
import wacc.microWacc.IntLiter
case class CliConfig( case class CliConfig(
file: File = new File(".") file: File = new File(".")
@ -30,7 +34,9 @@ val cliParser = {
) )
} }
def compile(contents: String): Int = { def frontend(
contents: String
)(using stdout: PrintStream): Either[microWacc.Program, Int] = {
parser.parse(contents) match { parser.parse(contents) match {
case Success(prog) => case Success(prog) =>
given errors: mutable.Builder[Error, List[Error]] = List.newBuilder given errors: mutable.Builder[Error, List[Error]] = List.newBuilder
@ -39,6 +45,7 @@ def compile(contents: String): Int = {
val typedProg = typeChecker.check(prog) val typedProg = typeChecker.check(prog)
if (errors.result.nonEmpty) { if (errors.result.nonEmpty) {
given errorContent: String = contents given errorContent: String = contents
Right(
errors.result errors.result
.map { error => .map { error =>
printError(error) printError(error)
@ -48,19 +55,190 @@ def compile(contents: String): Int = {
} }
} }
.max() .max()
} else { )
println(typedProg) } else Left(typedProg)
0
}
case Failure(msg) => case Failure(msg) =>
println(msg) stdout.println(msg)
100 Right(100)
} }
} }
val s = "enter an integer to echo"
def backend(typedProg: microWacc.Program): List[asm.AsmLine] | String =
typedProg match {
case microWacc.Program(
Nil,
microWacc.Call(microWacc.Builtin.Exit, microWacc.IntLiter(v) :: Nil) :: Nil
) =>
s""".intel_syntax noprefix
.globl main
main:
mov edi, ${v}
call exit@plt
"""
case microWacc.Program(
Nil,
microWacc.Assign(microWacc.Ident("x", _), microWacc.IntLiter(1)) ::
microWacc.Call(microWacc.Builtin.Println, _) ::
microWacc.Assign(
microWacc.Ident("x", _),
microWacc.Call(microWacc.Builtin.ReadInt, Nil)
) ::
microWacc.Call(microWacc.Builtin.Println, microWacc.Ident("x", _) :: Nil) :: Nil
) =>
""".intel_syntax noprefix
.globl main
.section .rodata
# length of .L.str0
.int 24
.L.str0:
.asciz "enter an integer to echo"
.text
main:
push rbp
# push {rbx, r12}
sub rsp, 16
mov qword ptr [rsp], rbx
mov qword ptr [rsp + 8], r12
mov rbp, rsp
mov r12d, 1
lea rdi, [rip + .L.str0]
# statement primitives do not return results (but will clobber r0/rax)
call _prints
call _println
# load the current value in the destination of the read so it supports defaults
mov edi, r12d
call _readi
mov r12d, eax
mov edi, eax
# statement primitives do not return results (but will clobber r0/rax)
call _printi
call _println
mov rax, 0
# pop/peek {rbx, r12}
mov rbx, qword ptr [rsp]
mov r12, qword ptr [rsp + 8]
add rsp, 16
pop rbp
ret
.section .rodata
# length of .L._printi_str0
.int 2
.L._printi_str0:
.asciz "%d"
.text
_printi:
push rbp
mov rbp, rsp
# external calls must be stack-aligned to 16 bytes, accomplished by masking with fffffffffffffff0
and rsp, -16
mov esi, edi
lea rdi, [rip + .L._printi_str0]
# on x86, al represents the number of SIMD registers used as variadic arguments
mov al, 0
call printf@plt
mov rdi, 0
call fflush@plt
mov rsp, rbp
pop rbp
ret
.section .rodata
# length of .L._prints_str0
.int 4
.L._prints_str0:
.asciz "%.*s"
.text
_prints:
push rbp
mov rbp, rsp
# external calls must be stack-aligned to 16 bytes, accomplished by masking with fffffffffffffff0
and rsp, -16
mov rdx, rdi
mov esi, dword ptr [rdi - 4]
lea rdi, [rip + .L._prints_str0]
# on x86, al represents the number of SIMD registers used as variadic arguments
mov al, 0
call printf@plt
mov rdi, 0
call fflush@plt
mov rsp, rbp
pop rbp
ret
.section .rodata
# length of .L._println_str0
.int 0
.L._println_str0:
.asciz ""
.text
_println:
push rbp
mov rbp, rsp
# external calls must be stack-aligned to 16 bytes, accomplished by masking with fffffffffffffff0
and rsp, -16
lea rdi, [rip + .L._println_str0]
call puts@plt
mov rdi, 0
call fflush@plt
mov rsp, rbp
pop rbp
ret
.section .rodata
# length of .L._readi_str0
.int 2
.L._readi_str0:
.asciz "%d"
.text
_readi:
push rbp
mov rbp, rsp
# external calls must be stack-aligned to 16 bytes, accomplished by masking with fffffffffffffff0
and rsp, -16
# RDI contains the "original" value of the destination of the read
# allocate space on the stack to store the read: preserve alignment!
# the passed default argument should be stored in case of EOF
sub rsp, 16
mov dword ptr [rsp], edi
lea rsi, qword ptr [rsp]
lea rdi, [rip + .L._readi_str0]
# on x86, al represents the number of SIMD registers used as variadic arguments
mov al, 0
call scanf@plt
mov eax, dword ptr [rsp]
add rsp, 16
mov rsp, rbp
pop rbp
ret
"""
case _ => List()
}
def compile(filename: String, outFile: Option[File] = None)(using
stdout: PrintStream = Console.out
): Int =
frontend(os.read(os.Path(filename))) match {
case Left(typedProg) =>
val asmFile = outFile.getOrElse(File(filename.stripSuffix(".wacc") + ".s"))
backend(typedProg) match {
case s: String =>
os.write.over(os.Path(asmFile.getAbsolutePath), s)
case ops: List[asm.AsmLine] => {
writer.writeTo(ops, PrintStream(asmFile))
}
}
0
case Right(exitCode) => exitCode
}
def main(args: Array[String]): Unit = def main(args: Array[String]): Unit =
OParser.parse(cliParser, args, CliConfig()) match { OParser.parse(cliParser, args, CliConfig()) match {
case Some(config) => case Some(config) =>
System.exit(compile(os.read(os.Path(config.file.getAbsolutePath)))) compile(
config.file.getAbsolutePath,
outFile = Some(File(".", config.file.getName.stripSuffix(".wacc") + ".s"))
)
case None => case None =>
} }

View File

@ -2,6 +2,7 @@ package wacc
import wacc.ast.Position import wacc.ast.Position
import wacc.types._ import wacc.types._
import java.io.PrintStream
/** Error types for semantic errors /** Error types for semantic errors
*/ */
@ -23,39 +24,39 @@ enum Error {
* @param errorContent * @param errorContent
* Contents of the file to generate code snippets * Contents of the file to generate code snippets
*/ */
def printError(error: Error)(using errorContent: String): Unit = { def printError(error: Error)(using errorContent: String, stdout: PrintStream): Unit = {
println("Semantic error:") stdout.println("Semantic error:")
error match { error match {
case Error.DuplicateDeclaration(ident) => case Error.DuplicateDeclaration(ident) =>
printPosition(ident.pos) printPosition(ident.pos)
println(s"Duplicate declaration of identifier ${ident.v}") stdout.println(s"Duplicate declaration of identifier ${ident.v}")
highlight(ident.pos, ident.v.length) highlight(ident.pos, ident.v.length)
case Error.UndeclaredVariable(ident) => case Error.UndeclaredVariable(ident) =>
printPosition(ident.pos) printPosition(ident.pos)
println(s"Undeclared variable ${ident.v}") stdout.println(s"Undeclared variable ${ident.v}")
highlight(ident.pos, ident.v.length) highlight(ident.pos, ident.v.length)
case Error.UndefinedFunction(ident) => case Error.UndefinedFunction(ident) =>
printPosition(ident.pos) printPosition(ident.pos)
println(s"Undefined function ${ident.v}") stdout.println(s"Undefined function ${ident.v}")
highlight(ident.pos, ident.v.length) highlight(ident.pos, ident.v.length)
case Error.FunctionParamsMismatch(id, expected, got, funcType) => case Error.FunctionParamsMismatch(id, expected, got, funcType) =>
printPosition(id.pos) printPosition(id.pos)
println(s"Function expects $expected parameters, got $got") stdout.println(s"Function expects $expected parameters, got $got")
println( stdout.println(
s"(function ${id.v} has type (${funcType.params.mkString(", ")}) -> ${funcType.returnType})" s"(function ${id.v} has type (${funcType.params.mkString(", ")}) -> ${funcType.returnType})"
) )
highlight(id.pos, 1) highlight(id.pos, 1)
case Error.TypeMismatch(pos, expected, got, msg) => case Error.TypeMismatch(pos, expected, got, msg) =>
printPosition(pos) printPosition(pos)
println(s"Type mismatch: $msg\nExpected: $expected\nGot: $got") stdout.println(s"Type mismatch: $msg\nExpected: $expected\nGot: $got")
highlight(pos, 1) highlight(pos, 1)
case Error.SemanticError(pos, msg) => case Error.SemanticError(pos, msg) =>
printPosition(pos) printPosition(pos)
println(msg) stdout.println(msg)
highlight(pos, 1) highlight(pos, 1)
case wacc.Error.InternalError(pos, msg) => case wacc.Error.InternalError(pos, msg) =>
printPosition(pos) printPosition(pos)
println(s"Internal error: $msg") stdout.println(s"Internal error: $msg")
highlight(pos, 1) highlight(pos, 1)
} }
@ -70,7 +71,7 @@ def printError(error: Error)(using errorContent: String): Unit = {
* @param errorContent * @param errorContent
* Contents of the file to generate code snippets * Contents of the file to generate code snippets
*/ */
def highlight(pos: Position, size: Int)(using errorContent: String): Unit = { def highlight(pos: Position, size: Int)(using errorContent: String, stdout: PrintStream): Unit = {
val lines = errorContent.split("\n") val lines = errorContent.split("\n")
val preLine = if (pos.line > 1) lines(pos.line - 2) else "" val preLine = if (pos.line > 1) lines(pos.line - 2) else ""
@ -78,7 +79,7 @@ def highlight(pos: Position, size: Int)(using errorContent: String): Unit = {
val postLine = if (pos.line < lines.size) lines(pos.line) else "" val postLine = if (pos.line < lines.size) lines(pos.line) else ""
val linePointer = " " * (pos.column + 2) + ("^" * (size)) + "\n" val linePointer = " " * (pos.column + 2) + ("^" * (size)) + "\n"
println( stdout.println(
s" >$preLine\n >$midLine\n$linePointer >$postLine" s" >$preLine\n >$midLine\n$linePointer >$postLine"
) )
} }
@ -88,6 +89,6 @@ def highlight(pos: Position, size: Int)(using errorContent: String): Unit = {
* @param pos * @param pos
* Position of the error * Position of the error
*/ */
def printPosition(pos: Position): Unit = { def printPosition(pos: Position)(using stdout: PrintStream): Unit = {
println(s"(line ${pos.line}, column ${pos.column}):") stdout.println(s"(line ${pos.line}, column ${pos.column}):")
} }

View File

@ -3,6 +3,9 @@ package wacc
import org.scalatest.{ParallelTestExecution, BeforeAndAfterAll} import org.scalatest.{ParallelTestExecution, BeforeAndAfterAll}
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.Inspectors.forEvery import org.scalatest.Inspectors.forEvery
import java.io.File
import sys.process._
import java.io.PrintStream
class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll with ParallelTestExecution { class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll with ParallelTestExecution {
val files = val files =
@ -20,12 +23,51 @@ class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll with Paral
} }
// tests go here // tests go here
forEvery(files.filter { (filename, _) => forEvery(files) { (filename, expectedResult) =>
!fileIsDissallowed(filename) val baseFilename = filename.stripSuffix(".wacc")
}) { (filename, expectedResult) => given stdout: PrintStream = PrintStream(File(baseFilename + ".out"))
s"$filename" should "be parsed with correct result" in { val result = compile(filename)
val contents = os.read(os.Path(filename))
assert(expectedResult.contains(compile(contents))) s"$filename" should "be compiled with correct result" in {
assert(expectedResult.contains(result))
}
if (result == 0) it should "run with correct result" in {
if (fileIsDisallowedBackend(filename)) pending
// Retrieve contents to get input and expected output + exit code
val contents = scala.io.Source.fromFile(File(filename)).getLines.toList
val inputLine =
contents.find(_.matches("^# ?[Ii]nput:.*$")).map(_.split(":").last.strip).getOrElse("")
val outputLineIdx = contents.indexWhere(_.matches("^# ?[Oo]utput:.*$"))
val expectedOutput =
if (outputLineIdx == -1) ""
else
contents
.drop(outputLineIdx + 1)
.takeWhile(_.startsWith("#"))
.map(_.stripPrefix("#").stripLeading)
.mkString("\n")
val exitLineIdx = contents.indexWhere(_.matches("^# ?[Ee]xit:.*$"))
val expectedExit =
if (exitLineIdx == -1) 0
else contents(exitLineIdx + 1).stripPrefix("#").strip.toInt
// Assembly and link using gcc
val asmFilename = baseFilename + ".s"
val execFilename = baseFilename
val gccResult = s"gcc -o $execFilename -z noexecstack $asmFilename".!
assert(gccResult == 0)
// Run the executable with the provided input
val stdout = new StringBuilder
// val execResult = s"$execFilename".!(ProcessLogger(stdout.append(_)))
val execResult =
s"echo $inputLine" #| s"timeout 5s $execFilename" ! ProcessLogger(stdout.append(_))
assert(execResult == expectedExit)
assert(stdout.toString == expectedOutput)
} }
} }
@ -33,57 +75,27 @@ class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll with Paral
val d = java.io.File(dir) val d = java.io.File(dir)
os.walk(os.Path(d.getAbsolutePath)).filter { _.ext == "wacc" } os.walk(os.Path(d.getAbsolutePath)).filter { _.ext == "wacc" }
def fileIsDissallowed(filename: String): Boolean = def fileIsDisallowedBackend(filename: String): Boolean =
Seq( Seq(
// format: off // format: off
// disable formatting to avoid binPack // disable formatting to avoid binPack
// "wacc-examples/valid/advanced", "^.*wacc-examples/valid/advanced.*$",
// "wacc-examples/valid/array", "^.*wacc-examples/valid/array.*$",
// "wacc-examples/valid/basic/exit", "^.*wacc-examples/valid/basic/skip.*$",
// "wacc-examples/valid/basic/skip", "^.*wacc-examples/valid/expressions.*$",
// "wacc-examples/valid/expressions", "^.*wacc-examples/valid/function/nested_functions.*$",
// "wacc-examples/valid/function/nested_functions", "^.*wacc-examples/valid/function/simple_functions.*$",
// "wacc-examples/valid/function/simple_functions", "^.*wacc-examples/valid/if.*$",
// "wacc-examples/valid/if", "^.*wacc-examples/valid/IO/print.*$",
// "wacc-examples/valid/IO/print", "^.*wacc-examples/valid/IO/read(?!echoInt\\.wacc).*$",
// "wacc-examples/valid/IO/read", "^.*wacc-examples/valid/IO/IOLoop.wacc.*$",
// "wacc-examples/valid/IO/IOLoop.wacc", "^.*wacc-examples/valid/IO/IOSequence.wacc.*$",
// "wacc-examples/valid/IO/IOSequence.wacc", "^.*wacc-examples/valid/pairs.*$",
// "wacc-examples/valid/pairs", "^.*wacc-examples/valid/runtimeErr.*$",
// "wacc-examples/valid/runtimeErr", "^.*wacc-examples/valid/scope.*$",
// "wacc-examples/valid/scope", "^.*wacc-examples/valid/sequence.*$",
// "wacc-examples/valid/sequence", "^.*wacc-examples/valid/variables.*$",
// "wacc-examples/valid/variables", "^.*wacc-examples/valid/while.*$",
// "wacc-examples/valid/while",
// invalid (syntax)
// "wacc-examples/invalid/syntaxErr/array",
// "wacc-examples/invalid/syntaxErr/basic",
// "wacc-examples/invalid/syntaxErr/expressions",
// "wacc-examples/invalid/syntaxErr/function",
// "wacc-examples/invalid/syntaxErr/if",
// "wacc-examples/invalid/syntaxErr/literals",
// "wacc-examples/invalid/syntaxErr/pairs",
// "wacc-examples/invalid/syntaxErr/print",
// "wacc-examples/invalid/syntaxErr/sequence",
// "wacc-examples/invalid/syntaxErr/variables",
// "wacc-examples/invalid/syntaxErr/while",
// invalid (semantic)
// "wacc-examples/invalid/semanticErr/array",
// "wacc-examples/invalid/semanticErr/exit",
// "wacc-examples/invalid/semanticErr/expressions",
// "wacc-examples/invalid/semanticErr/function",
// "wacc-examples/invalid/semanticErr/if",
// "wacc-examples/invalid/semanticErr/IO",
// "wacc-examples/invalid/semanticErr/multiple",
// "wacc-examples/invalid/semanticErr/pairs",
// "wacc-examples/invalid/semanticErr/print",
// "wacc-examples/invalid/semanticErr/read",
// "wacc-examples/invalid/semanticErr/scope",
// "wacc-examples/invalid/semanticErr/variables",
// "wacc-examples/invalid/semanticErr/while",
// invalid (whack)
// "wacc-examples/invalid/whack"
// format: on // format: on
// format: on ).find(filename.matches).isDefined
).find(filename.contains).isDefined
} }

1
wacc.target Normal file
View File

@ -0,0 +1 @@
x86-64