updated
authorChristian Urban <christian.urban@kcl.ac.uk>
Tue, 19 Sep 2023 09:54:41 +0100
changeset 920 7af2eea19646
parent 919 53f08d873e09
child 921 bb54e7aa1a3f
updated
cws/cw04.pdf
cws/cw04.tex
langs.sty
solutions/cw2/collatz.while
solutions/cw2/factors.while
solutions/cw2/fib.while
solutions/cw2/lexer.sc
solutions/cw3/lexer.sc
solutions/cw4/compiler.sc
solutions/cw4/lexer.sc
solutions/cw4/parser.sc
solutions/cw5/fun_llvm.sc
solutions/cw5/fun_parser.sc
solutions/cw5/fun_tokens.sc
Binary file cws/cw04.pdf has changed
--- a/cws/cw04.tex	Sun Sep 17 19:12:57 2023 +0100
+++ b/cws/cw04.tex	Tue Sep 19 09:54:41 2023 +0100
@@ -22,9 +22,11 @@
 language you like, but you need to submit the source code with
 which you answered the questions, otherwise a mark of 0\% will
 be awarded. You should use the lexer and parser from the
-previous courseworks. Please package \emph{everything}(!) in
-a zip-file that creates a directory with the name
-\texttt{YournameYourFamilyname} on my end.
+previous courseworks.
+
+%Please package \emph{everything}(!) in
+%a zip-file that creates a directory with the name
+%\texttt{YournameYourFamilyname} on my end.
 
 \subsection*{Disclaimer\alert}
 
@@ -135,11 +137,11 @@
 %will \emph{only} be judged according to the answers. You can
 %submit your answers in a txt-file or as pdf.\bigskip
 
-\subsection*{Question 0 (Unmarked)}
-
-Please include  in the PDF at the beginning your email address, your student
-number and whether you are BSc / MSci student and for the latter in which
-year your are in. Thanks! 
+%\subsection*{Question 0 (Unmarked)}
+%
+%Please include  in the PDF at the beginning your email address, your student
+%number and whether you are BSc / MSci student and for the latter in which
+%year your are in. Thanks! 
 
 \subsection*{Question 1}
 
@@ -233,16 +235,43 @@
 Explain your decision and indicate what this program would
 print out.
 
-\subsection*{Question 4 (Advanced)}
+\subsection*{Question 4}
+
+Extend the lexer and parser to add a \texttt{break} keyword.  Modify
+the compiler such that when a \texttt{break}-statement is encountered
+the code should jump out of the ``enclosing'' for-loop, or in case it
+is not inside a for-loop to the end of the program. For example the
+program
+
+\begin{center}
+\begin{minipage}{12cm}
+\begin{lstlisting}[language=While, numbers=none]
+for i := 0 upto 10 do {
+  write i;
+  write "\n"
+};
 
-Extend the lexer and parser in order to add a \texttt{break} keyword.
-Modify the compiler such that when a \texttt{break} is encountered the
-code should jump out of the ``enclosing'' while loop, or in case it
-is not inside a while loop to the end of the
-program.
+for i := 0 upto 10 do {
+  if i > 4 then break else skip;
+  write i;
+  write "\n"
+};
 
-\bigskip\noindent
-\textcolor{red}{ADD EXAMPLE PROGRAMS}
+write "Should print\n";
+break;
+write "Should not print\n"
+\end{lstlisting}
+\end{minipage}
+\end{center}
+
+\noindent
+should print out 1 to 10 with the first for-loop, but only 1
+to 4 in the second. Similarly it should print out \code{"Should print"},
+but not \code{"Should not print"}. For this you need to add
+a label to the end of every for-loop and also to the end of the
+whole program just in case you need to jump to that label via a
+\code{break}.
+
 
 \subsection*{Further Information}
 
--- a/langs.sty	Sun Sep 17 19:12:57 2023 +0100
+++ b/langs.sty	Tue Sep 19 09:54:41 2023 +0100
@@ -56,7 +56,7 @@
 }[keywords,comments,strings]
 
 \lstdefinelanguage{While}{
-  morekeywords={if,then,else,while,do,true,false,write,upto,read,for,skip,new},
+  morekeywords={if,then,else,while,do,true,false,write,upto,read,for,skip,new,break},
   morecomment=[l]{//},
   morecomment=[n]{/*}{*/},
   morestring=[b]",
--- a/solutions/cw2/collatz.while	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw2/collatz.while	Tue Sep 19 09:54:41 2023 +0100
@@ -2,7 +2,7 @@
 read n;
 while n > 1 do {
   if n % 2 == 0 
-  then n := n / 2 
-  else n := 3 * n + 1;
+  then n := n/2 
+  else n := 3*n+1
 };
-write "Yes\n";
+write "Yes\n"
--- a/solutions/cw2/factors.while	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw2/factors.while	Tue Sep 19 09:54:41 2023 +0100
@@ -3,12 +3,9 @@
 
 write "Input n please";
 read n;
-write "The factors of n are\n";
+write "The factors of n are:\n";
 f := 2;
-while n != 1 do {
-    while (n / f) * f == n do {
-        write f; write "\n";
-        n := n / f
-    };
-    f := f + 1
-}
\ No newline at end of file
+while (f < n / 2 + 1) do {
+  if ((n / f) * f == n) then  { write(f); write "\n" } else { skip };
+  f := f + 1
+}
--- a/solutions/cw2/fib.while	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw2/fib.while	Tue Sep 19 09:54:41 2023 +0100
@@ -1,7 +1,7 @@
-write "Fib";
+write "Fib: ";
 read n;  
-minus1 := 1;
-minus2 := 0;
+minus1 := 0;
+minus2 := 1;
 while n > 0 do {
        temp := minus2;
        minus2 := minus1 + minus2;
--- a/solutions/cw2/lexer.sc	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw2/lexer.sc	Tue Sep 19 09:54:41 2023 +0100
@@ -1,5 +1,7 @@
-import scala.language.implicitConversions    
-import scala.language.reflectiveCalls
+// CW 2
+//======
+
+
 
 // Rexp
 abstract class Rexp
@@ -27,7 +29,7 @@
 case class Rec(x: String, v: Val) extends Val
 
 
-// Convenience typing
+// Convenience for typing
 def charlist2rexp(s : List[Char]): Rexp = s match {
   case Nil => ONE
   case c::Nil => CHAR(c)
@@ -207,17 +209,18 @@
 // Language specific code
 val KEYWORD : Rexp = "while" | "if" | "then" | "else" | "do" | "for" | "to" | "true" | "false" | "read" | "write" | "skip" 
 val OP : Rexp = "+" | "-" | "*" | "%" | "/" | "==" | "!=" | ">" | "<" | ">=" | "<=" | ":=" | "&&" | "||"
-val LET: Rexp = RANGE(('A' to 'Z').toSet ++ ('a' to 'z'))
-val SYM : Rexp = LET | RANGE(Set('.', '_', '>', '<', '=', ';', ',', ':', ')', '('))
+val LET: Rexp = RANGE(('A' to 'Z').toSet ++ ('a' to 'z').toSet)
+val SYM : Rexp = RANGE(Set('.', '_', '>', '<', '=', ';', ',', ':'))
 val PARENS : Rexp = "(" | "{" | ")" | "}"
 val SEMI : Rexp = ";"
 val WHITESPACE : Rexp = PLUS(" ") | "\n" | "\t" | "\r"
 val DIGIT : Rexp = RANGE(('0' to '9').toSet)
 val DIGIT1 : Rexp = RANGE(('1' to '9').toSet)
-val STRING : Rexp = "\"" ~ (SYM | " " | "\\n" | DIGIT).% ~ "\""
+val STRING : Rexp = "\"" ~ (LET | DIGIT | SYM | PARENS | " " | "\\n").% ~ "\""
 val ID : Rexp = LET ~ (LET | "_" | DIGIT).%
 val NUM : Rexp = "0" | (DIGIT1 ~ DIGIT.%)
-val COMMENT : Rexp = "//" ~ (SYM | " " | DIGIT).% ~ ("\n" | "\r\n") 
+val EOL : Rexp = "\n" | "\r\n"
+val COMMENT : Rexp = "//" ~ (LET | DIGIT | SYM | " ").% ~ EOL 
 
 val WHILE_REGS = (("k" $ KEYWORD) | 
                   ("o" $ OP) | 
@@ -227,7 +230,7 @@
                   ("w" $ WHITESPACE) | 
                   ("i" $ ID) | 
                   ("n" $ NUM) |
-		              ("c" $ COMMENT)).%
+		  ("c" $ COMMENT)).%
 
 def esc(raw: String): String = {
   import scala.reflect.runtime.universe._
@@ -263,6 +266,7 @@
 
 
 // Q2 Tests
+
 lex_simp(NTIMES("a", 3), "aaa".toList)
 lex_simp(NTIMES(("a" | ONE), 3), "aa".toList)
 
@@ -312,7 +316,7 @@
 println(tokenise(prog3))
 
 
-println("MY TESTS")
+// More tests
 
 println(lex_simp("x" $ OPTIONAL("a"), "a".toList))
 println(lex_simp("x" $ OPTIONAL("a"), "".toList))
--- a/solutions/cw3/lexer.sc	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw3/lexer.sc	Tue Sep 19 09:54:41 2023 +0100
@@ -1,5 +1,6 @@
-import scala.language.implicitConversions    
-import scala.language.reflectiveCalls
+// Lexer from CW2
+//================
+
 
 // Rexp
 abstract class Rexp
@@ -27,7 +28,7 @@
 case class Rec(x: String, v: Val) extends Val
 
 
-// Convenience typing
+// Convenience for typing
 def charlist2rexp(s : List[Char]): Rexp = s match {
   case Nil => ONE
   case c::Nil => CHAR(c)
@@ -136,7 +137,7 @@
 
   case (RANGE(_), Empty) => Chr(c)
   case (PLUS(r), Sequ(v1, Stars(vs))) => Stars(inj(r, c, v1)::vs)
-  case (OPTIONAL(r), Left(v1)) => Stars(List(inj(r, c, v1)))
+  case (OPTIONAL(r), v1) => Stars(List(inj(r, c, v1)))
   case (NTIMES(r, n), Sequ(v1, Stars(vs))) => Stars(inj(r, c, v1)::vs)
 }
 
--- a/solutions/cw4/compiler.sc	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw4/compiler.sc	Tue Sep 19 09:54:41 2023 +0100
@@ -92,7 +92,7 @@
   x ++ "_" ++ counter.toString()
 }
 
-implicit def string_interpolations(sc: StringContext) = new {
+extension (sc: StringContext) {
     def i(args: Any*): String = "   " ++ sc.s(args:_*) ++ "\n"
     def l(args: Any*): String = sc.s(args:_*) ++ ":\n"
 }
@@ -138,7 +138,7 @@
     compile_aexp(a1, env) ++ compile_aexp(a2, env) ++ i"${compile_bop(op)} $jmp"
 }
 
-def compile_stmt(s: Stmt, env: Env) : (String, Env) = s match {
+def compile_stmt(s: Stmt, env: Env, break: String) : (String, Env) = s match {
   case Skip => ("", env)
   case Assign(x, a) => {
     val index = env.getOrElse(x, env.keys.size)
@@ -147,8 +147,8 @@
   case If(b, bl1, bl2) => {
     val if_else = Fresh("If_else")
     val if_end = Fresh("If_end")
-    val (instrs1, env1) = compile_block(bl1, env)
-    val (instrs2, env2) = compile_block(bl2, env1)
+    val (instrs1, env1) = compile_block(bl1, env, break)
+    val (instrs2, env2) = compile_block(bl2, env1, break)
     (compile_bexp(b, env, if_else) ++
      instrs1 ++
      i"goto $if_end" ++
@@ -159,7 +159,7 @@
   case While(b, bl) => {
     val loop_begin = Fresh("Loop_begin")
     val loop_end = Fresh("Loop_end")
-    val (instrs1, env1) = compile_block(bl, env)
+    val (instrs1, env1) = compile_block(bl, env, break)
     (l"$loop_begin" ++
      compile_bexp(b, env, loop_end) ++
      instrs1 ++
@@ -167,17 +167,18 @@
      l"$loop_end", env1)
   }
   case For(id, lower, upper, code) => {
-      val (assignment_code, env1) = compile_stmt(Assign(id, lower), env)  // id := lower;
+      val (assignment_code, env1) = compile_stmt(Assign(id, lower), env, break)  // id := lower;
       val while_equivalent = While(
           Or(Bop("<", Var(id), upper), Bop("==", Var(id), upper)),    // while id <= upper do {
           code ++                                                     //    code  
           List(
             Assign(id, Aop("+", Var(id), Num(1)))                     //    id := id + 1
           ))                                                          // };
-
-      val (while_code, env2) = compile_stmt(while_equivalent, env1)
-      (assignment_code ++ while_code, env2)
+      val new_break = Fresh("for_break")
+      val (while_code, env2) = compile_stmt(while_equivalent, env1, new_break)
+      (assignment_code ++ while_code ++ l"$new_break", env2)
   }
+  case Break => (i"goto $break", env)
   case WriteId(x) => (i"iload ${env(x)} \t\t; $x" ++ 
      i"invokestatic XXX/XXX/write(I)V", env)
   case WriteString(x) => (s"   ldc ${x}\n" ++
@@ -189,38 +190,33 @@
   }
 }
 
-def compile_block(bl: Block, env: Env) : (String, Env) = bl match {
+def compile_block(bl: Block, env: Env, break: String) : (String, Env) = bl match {
   case Nil => ("", env)
   case s::bl => {
-    val (instrs1, env1) = compile_stmt(s, env)
-    val (instrs2, env2) = compile_block(bl, env1)
+    val (instrs1, env1) = compile_stmt(s, env, break)
+    val (instrs2, env2) = compile_block(bl, env1, break)
     (instrs1 ++ instrs2, env2)
   }
 }
 
 def compile(bl: Block, class_name: String) : String = {
-  val instructions = compile_block(bl, Map.empty)._1
-  (beginning ++ instructions ++ ending).replaceAllLiterally("XXX", class_name)
+  val prog_end = Fresh("Prog_end")
+  val instructions = compile_block(bl, Map.empty, prog_end)._1
+  (beginning ++ instructions ++ l"$prog_end" ++ ending).replaceAllLiterally("XXX", class_name)
 }
 
 // Compiling and running
 
-import scala.util._
-import scala.sys.process._
-import scala.io
 
-def compile_tofile(bl: Block, class_name: String) = {
-  val output = compile(bl, class_name)
-  val fw = new java.io.FileWriter(class_name + ".j") 
-  fw.write(output) 
-  fw.close()
-}
+def compile_to_file(bl: Block, class_name: String) : Unit = 
+  os.write.over(os.pwd / s"$class_name.j", compile(bl, class_name))  
 
 def compile_all(bl: Block, class_name: String) : Unit = {
-  compile_tofile(bl, class_name)
-  println("compiled ")
-  val test = ("java -jar jasmin.jar " + class_name + ".j").!!
-  println("assembled ")
+  println(s"Start of compilation")
+  compile_to_file(bl, class_name)
+  println(s"generated $class_name.j file")
+  os.proc("java", "-jar", "jasmin.jar", s"$class_name.j").call()
+  println(s"generated $class_name.class file ")
 }
 
 def time_needed[T](i: Int, code: => T) = {
@@ -231,10 +227,10 @@
 }
 
 def compile_run(bl: Block, class_name: String) : Unit = {
-  println("Start compilation")
   compile_all(bl, class_name)
   println("running")
-  println("Time: " + time_needed(1, ("java " + class_name + "/" + class_name).!))
+  os.proc("java", s"${class_name}/${class_name}").call(stdout = os.Inherit)
+  println(s"done.")
 }
 
 // ---- Q1
@@ -280,4 +276,8 @@
 }""")).head, "nestedloop") */
 
 
-compile_run(Stmts.parse_all(tokenise(os.read(os.pwd / "collatz2.while"))).head, "collatz2")
+//compile_run(Stmts.parse_all(tokenise(os.read(os.pwd / "collatz2.while"))).head, "collatz2")
+
+
+println(tokenise(os.read(os.pwd / "forloop.while")))
+compile_run(Stmts.parse_all(tokenise(os.read(os.pwd / "forloop.while"))).head, "forloop")
\ No newline at end of file
--- a/solutions/cw4/lexer.sc	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw4/lexer.sc	Tue Sep 19 09:54:41 2023 +0100
@@ -1,5 +1,5 @@
-import scala.language.implicitConversions    
-import scala.language.reflectiveCalls
+// Lexer from CW2 with additions to the rules
+//============================================
 
 // Rexp
 abstract class Rexp
@@ -27,7 +27,7 @@
 case class Rec(x: String, v: Val) extends Val
 
 
-// Convenience typing
+// Convenience for typing
 def charlist2rexp(s : List[Char]): Rexp = s match {
   case Nil => ONE
   case c::Nil => CHAR(c)
@@ -37,13 +37,13 @@
 implicit def string2rexp(s : String) : Rexp = 
   charlist2rexp(s.toList)
 
-implicit def RexpOps(r: Rexp) = new {
+extension (r: Rexp) {
   def | (s: Rexp) = ALT(r, s)
   def % = STAR(r)
   def ~ (s: Rexp) = SEQ(r, s)
 }
 
-implicit def stringOps(s: String) = new {
+extension (s: String) {
   def | (r: Rexp) = ALT(s, r)
   def | (r: String) = ALT(s, r)
   def % = STAR(s)
@@ -82,7 +82,7 @@
   case RECD(_, r1) => der(c, r1)
   case RANGE(s) => if (s.contains(c)) ONE else ZERO 
   case PLUS(r1) => SEQ(der(c, r1), STAR(r1))
-  case OPTIONAL(r1) => ALT(der(c, r1), ZERO)
+  case OPTIONAL(r1) => der(c, r1)
   case NTIMES(r, i) => 
     if (i == 0) ZERO else SEQ(der(c, r), NTIMES(r, i - 1))
 }
@@ -119,7 +119,7 @@
   case RECD(x, r) => Rec(x, mkeps(r))
 
   case PLUS(r) => Stars(List(mkeps(r)))   // the first copy must match the empty string
-  case OPTIONAL(r) => Right(Empty)
+  case OPTIONAL(r) => if (nullable(r)) Stars(List(mkeps(r))) else Stars(Nil) 
   case NTIMES(r, i) => Stars(List.fill(i)(mkeps(r)))
 }
 
@@ -136,7 +136,7 @@
 
   case (RANGE(_), Empty) => Chr(c)
   case (PLUS(r), Sequ(v1, Stars(vs))) => Stars(inj(r, c, v1)::vs)
-  case (OPTIONAL(r), Left(v1)) => Left(inj(r, c, v1))
+  case (OPTIONAL(r), v1) => Stars(List(inj(r, c, v1)))
   case (NTIMES(r, n), Sequ(v1, Stars(vs))) => Stars(inj(r, c, v1)::vs)
 }
 
@@ -199,20 +199,21 @@
 def lexing_simp(r: Rexp, s: String) = env(lex_simp(r, s.toList))
 
 // Language specific code
-val KEYWORD : Rexp = "while" | "if" | "then" | "else" | "do" | "for" | "to" | "true" | "false" | "read" | "write" | "skip" 
+// Language specific code
+val KEYWORD : Rexp = "while" | "if" | "then" | "else" | "do" | "for" | "upto" | "true" | "false" | "read" | "write" | "skip" | "break"
 val OP : Rexp = "+" | "-" | "*" | "%" | "/" | "==" | "!=" | ">" | "<" | ">=" | "<=" | ":=" | "&&" | "||"
-val LET: Rexp = RANGE(('A' to 'Z').toSet ++ ('a' to 'z'))
-val SYM : Rexp = LET | RANGE(Set('.', '_', '>', '<', '=', ';', ',', ':'))
+val LET: Rexp = RANGE(('A' to 'Z').toSet ++ ('a' to 'z').toSet)
+val SYM : Rexp = RANGE(Set('.', '_', '>', '<', '=', ';', ',', ':', ')', '('))
 val PARENS : Rexp = "(" | "{" | ")" | "}"
 val SEMI : Rexp = ";"
 val WHITESPACE : Rexp = PLUS(" ") | "\n" | "\t" | "\r"
 val DIGIT : Rexp = RANGE(('0' to '9').toSet)
 val DIGIT1 : Rexp = RANGE(('1' to '9').toSet)
-val STRING : Rexp = "\"" ~ (SYM | " " | "\\n" | DIGIT).% ~ "\""
+val STRING : Rexp = "\"" ~ (LET | SYM | DIGIT | " " | "\\n").% ~ "\""
 val ID : Rexp = LET ~ (LET | "_" | DIGIT).%
 val NUM : Rexp = "0" | (DIGIT1 ~ DIGIT.%)
 val EOL : Rexp = "\n" | "\r\n"
-val COMMENT : Rexp = "//" ~ (SYM | PARENS | " " | DIGIT).% ~ EOL
+val COMMENT : Rexp = "//" ~ (LET | SYM | PARENS | " " | DIGIT).% ~ EOL 
 
 val WHILE_REGS = (("k" $ KEYWORD) | 
                   ("o" $ OP) | 
@@ -224,6 +225,8 @@
                   ("n" $ NUM) |
 		  ("c" $ COMMENT)).%
 
+
+
 // Token
 abstract class Token extends Serializable 
 case class T_KEYWORD(s: String) extends Token
--- a/solutions/cw4/parser.sc	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw4/parser.sc	Tue Sep 19 09:54:41 2023 +0100
@@ -3,33 +3,65 @@
 import $file.lexer
 import lexer._ 
 
+case class ~[+A, +B](x: A, y: B)
 
-case class ~[+A, +B](_1: A, _2: B)
-type IsSeq[A] = A => Seq[_]
+// parser combinators
+
+abstract class Parser[I, T](using is: I => Seq[_])  {
+  def parse(in: I): Set[(T, I)]  
+
+  def parse_all(in: I) : Set[T] =
+    for ((hd, tl) <- parse(in); 
+        if is(tl).isEmpty) yield hd
+}
 
-abstract class Parser[I : IsSeq, T] {
-  def parse(ts: I): Set[(T, I)]
+// alternative parser
+class AltParser[I, T](p: => Parser[I, T], 
+                      q: => Parser[I, T])(using I => Seq[_]) extends Parser[I, T] {
+  def parse(in: I) = p.parse(in) ++ q.parse(in)   
+}
 
-  def parse_all(ts: I) : Set[T] =
-    for ((head, tail) <- parse(ts); if tail.isEmpty) yield head
+// sequence parser
+class SeqParser[I, T, S](p: => Parser[I, T], 
+                         q: => Parser[I, S])(using I => Seq[_]) extends Parser[I, ~[T, S]] {
+  def parse(in: I) = 
+    for ((hd1, tl1) <- p.parse(in); 
+         (hd2, tl2) <- q.parse(tl1)) yield (new ~(hd1, hd2), tl2)
 }
 
-class SeqParser[I : IsSeq, T, S](p: => Parser[I, T], q: => Parser[I, S]) extends Parser[I, ~[T, S]] {
-  def parse(sb: I) = 
-    for ((head1, tail1) <- p.parse(sb); 
-         (head2, tail2) <- q.parse(tail1)) yield (new ~(head1, head2), tail2)
+// map parser
+class MapParser[I, T, S](p: => Parser[I, T], 
+                         f: T => S)(using I => Seq[_]) extends Parser[I, S] {
+  def parse(in: I) = for ((hd, tl) <- p.parse(in)) yield (f(hd), tl)
+}
+
+// more convenient syntax for parser combinators
+extension [I, T](p: Parser[I, T])(using I => Seq[_]) {
+  def ||(q : => Parser[I, T]) = new AltParser[I, T](p, q)
+  def ~[S] (q : => Parser[I, S]) = new SeqParser[I, T, S](p, q)
+  def map[S](f: => T => S) = new MapParser[I, T, S](p, f)
 }
 
-class AltParser[I : IsSeq, T](p: => Parser[I, T], q: => Parser[I, T]) extends Parser[I, T] {
-  def parse(sb: I) = p.parse(sb) ++ q.parse(sb)   
+/*
+// atomic parser for (particular) strings
+case class StrParser(s: String) extends Parser[String, String] {
+  def parse(sb: String) = {
+    val (prefix, suffix) = sb.splitAt(s.length)
+    if (prefix == s) Set((prefix, suffix)) else Set()
+  }
 }
 
-class FunParser[I : IsSeq, T, S](p: => Parser[I, T], f: T => S) extends Parser[I, S] {
-  def parse(sb: I) = 
-    for ((head, tail) <- p.parse(sb)) yield (f(head), tail)
-}
+extension (sc: StringContext) 
+  def p(args: Any*) = StrParser(sc.s(args:_*))
+*/
 
-// New parser that takes as input a list of tokens
+case class TokenParser(t: Token) extends Parser[List[Token], Token] {
+    def parse(in: List[Token]) = {
+      // an example of an atomic parser for characters
+      if (!in.isEmpty && in.head == t) Set((t, in.tail)) else Set()
+    }
+}   
+
 case class TokenListParser(ts: List[Token]) extends Parser[List[Token], List[Token]] {
     def parse(tsb: List[Token]) = {
         val (prefix, suffix) = tsb.splitAt(ts.length)
@@ -39,34 +71,17 @@
 
 // Implicit definitions to go from a token 
 // or a list of tokens to a TokenListParser
-implicit def token2parser(t: Token) = TokenListParser(List(t))
-implicit def tokenList2parser(ts: List[Token]) = TokenListParser(ts)
+implicit def token2parser(t: Token) : Parser[List[Token], Token] = 
+  TokenParser(t)
 
-implicit def ParserOps[I : IsSeq, T](p: Parser[I, T]) = new {
-  def || (q : => Parser[I, T]) = new AltParser[I, T](p, q)
-  def ==>[S] (f: => T => S) = new FunParser[I, T, S](p, f)
-  def ~[S] (q : => Parser[I, S]) = new SeqParser[I, T, S](p, q)
+extension (t: Token) {
+    def || (q : => Parser[List[Token], Token]) = 
+      new AltParser[List[Token], Token](t, q)
+    def ~[S](q : => Parser[List[Token], S]) = 
+      new SeqParser[List[Token], Token, S](t, q)  
 }
 
-implicit def TokenOps(t: Token) = new {
-    def || (q : => Parser[List[Token], List[Token]]) = new AltParser[List[Token], List[Token]](List(t), q)
-    def || (qs : List[Token]) = new AltParser[List[Token], List[Token]](List(t), qs)
-    def ==>[S] (f: => List[Token] => S) = new FunParser[List[Token], List[Token], S](List(t), f)
-    def ~[S](q : => Parser[List[Token], S]) =
-        new SeqParser[List[Token], List[Token], S](List(t), q)
-    def ~ (qs : List[Token]) =
-        new SeqParser[List[Token], List[Token], List[Token]](List(t), qs)
-}
 
-implicit def TokenListOps(ts: List[Token]) = new {
-    def || (q : => Parser[List[Token], List[Token]]) = new AltParser[List[Token], List[Token]](ts, q)
-    def || (qs : List[Token]) = new AltParser[List[Token], List[Token]](ts, qs)
-    def ==>[S] (f: => List[Token] => S) = new FunParser[List[Token], List[Token], S](ts, f)
-    def ~[S](q : => Parser[List[Token], S]) =
-        new SeqParser[List[Token], List[Token], S](ts, q)
-    def ~ (qs : List[Token]) =
-        new SeqParser[List[Token], List[Token], List[Token]](ts, qs)
-}
 
 // Abstract Syntax Trees
 abstract class Stmt
@@ -83,6 +98,7 @@
 case class WriteId(s: String) extends Stmt  // for printing values of variables
 case class WriteString(s: String) extends Stmt  // for printing words
 case class For(counter: String, lower: AExp, upper: AExp, code: Block) extends Stmt
+case object Break extends Stmt
 
 
 case class Var(s: String) extends AExp
@@ -117,46 +133,51 @@
 }
 
 // WHILE Language Parsing
+
+// WHILE Language Parsing
 lazy val AExp: Parser[List[Token], AExp] = 
-  (Te ~ T_OP("+") ~ AExp) ==> { case x ~ _ ~ z => Aop("+", x, z): AExp } ||
-  (Te ~ T_OP("-") ~ AExp) ==> { case x ~ _ ~ z => Aop("-", x, z): AExp } || Te
+  (Te ~ T_OP("+") ~ AExp).map{ case x ~ _ ~ z => Aop("+", x, z): AExp } ||
+  (Te ~ T_OP("-") ~ AExp).map{ case x ~ _ ~ z => Aop("-", x, z): AExp } || Te
 lazy val Te: Parser[List[Token], AExp] = 
-  (Fa ~ T_OP("*") ~ Te) ==> { case x ~ _ ~ z => Aop("*", x, z): AExp } || 
-  (Fa ~ T_OP("/") ~ Te) ==> { case x ~ _ ~ z => Aop("/", x, z): AExp } || 
-  (Fa ~ T_OP("%") ~ Te) ==> { case x ~ _ ~ z => Aop("%", x, z): AExp } || Fa  
+  (Fa ~ T_OP("*") ~ Te).map{ case x ~ _ ~ z => Aop("*", x, z): AExp } || 
+  (Fa ~ T_OP("/") ~ Te).map{ case x ~ _ ~ z => Aop("/", x, z): AExp } || 
+  (Fa ~ T_OP("%") ~ Te).map{ case x ~ _ ~ z => Aop("%", x, z): AExp } || Fa  
 lazy val Fa: Parser[List[Token], AExp] = 
-   (T_PAREN("(") ~ AExp ~ T_PAREN(")")) ==> { case _ ~ y ~ _ => y } || 
-   IdParser() ==> Var  || 
-   NumParser() ==> Num
+   (T_PAREN("(") ~ AExp ~ T_PAREN(")")).map{ case _ ~ y ~ _ => y } || 
+   IdParser().map{Var(_)}  || 
+   NumParser().map{Num(_)}
 
 lazy val BExp: Parser[List[Token], BExp] = 
-   (AExp ~ T_OP("==") ~ AExp) ==> { case x ~ _ ~ z => Bop("==", x, z): BExp } || 
-   (AExp ~ T_OP("!=") ~ AExp) ==> { case x ~ _ ~ z => Bop("!=", x, z): BExp } || 
-   (AExp ~ T_OP("<") ~ AExp) ==> { case x ~ _ ~ z => Bop("<", x, z): BExp } || 
-   (AExp ~ T_OP(">") ~ AExp) ==> { case x ~ _ ~ z => Bop(">", x, z): BExp } ||
-   (T_PAREN("(") ~ BExp ~ List(T_PAREN(")"), T_OP("&&")) ~ BExp) ==> { case _ ~ y ~ _ ~ v => And(y, v): BExp } ||
-   (T_PAREN("(") ~ BExp ~ List(T_PAREN(")"), T_OP("||")) ~ BExp) ==> { case _ ~ y ~ _ ~ v => Or(y, v): BExp } ||
-   (T_KEYWORD("true") ==> (_ => True: BExp )) || 
-   (T_KEYWORD("false") ==> (_ => False: BExp )) ||
-   (T_PAREN("(") ~ BExp ~ T_PAREN(")")) ==> { case _ ~ x ~ _ => x }
+   (AExp ~ T_OP("==") ~ AExp).map{ case x ~ _ ~ z => Bop("==", x, z): BExp } || 
+   (AExp ~ T_OP("!=") ~ AExp).map{ case x ~ _ ~ z => Bop("!=", x, z): BExp } || 
+   (AExp ~ T_OP("<") ~ AExp).map{ case x ~ _ ~ z => Bop("<", x, z): BExp } || 
+   (AExp ~ T_OP(">") ~ AExp).map{ case x ~ _ ~ z => Bop(">", x, z): BExp } ||
+   (T_PAREN("(") ~ BExp ~ T_PAREN(")") ~ T_OP("&&") ~ BExp).map{ case _ ~ y ~ _ ~ _ ~ v => And(y, v): BExp } ||
+   (T_PAREN("(") ~ BExp ~ T_PAREN(")") ~ T_OP("||") ~ BExp).map{ case _ ~ y ~ _ ~ _ ~ v => Or(y, v): BExp } ||
+   (T_KEYWORD("true").map(_ => True: BExp )) || 
+   (T_KEYWORD("false").map(_ => False: BExp )) ||
+   (T_PAREN("(") ~ BExp ~ T_PAREN(")")).map{ case _ ~ x ~ _ => x }
 
 lazy val Stmt: Parser[List[Token], Stmt] =
-    T_KEYWORD("skip") ==> (_ => Skip: Stmt) ||
-    (IdParser() ~ T_OP(":=") ~ AExp) ==> { case id ~ _ ~ z => Assign(id, z): Stmt } ||
-    (T_KEYWORD("if") ~ BExp ~ T_KEYWORD("then") ~ Block ~ T_KEYWORD("else") ~ Block) ==> { case _ ~ y ~ _ ~ u ~ _ ~ w => If(y, u, w): Stmt } ||
-    (T_KEYWORD("while") ~ BExp ~ T_KEYWORD("do") ~ Block) ==> { case _ ~ y ~ _ ~ w => While(y, w) : Stmt } ||
-    (T_KEYWORD("read") ~ IdParser()) ==> { case _ ~ id => Read(id): Stmt} ||
-    (T_KEYWORD("write") ~ IdParser()) ==> { case _ ~ id => WriteId(id): Stmt} ||
-    (T_KEYWORD("write") ~ StringParser()) ==> { case _ ~ s => WriteString(s): Stmt} ||
-    (T_KEYWORD("for") ~ IdParser() ~ T_OP(":=") ~ AExp ~ T_KEYWORD("upto") ~ AExp ~ T_KEYWORD("do") ~ Block) ==> {
-      case _ ~ id ~ _ ~ lower ~ _ ~ upper ~ _ ~ blck => For(id, lower, upper, blck): Stmt
-    }
+    T_KEYWORD("skip").map(_ => Skip: Stmt) ||
+    T_KEYWORD("break").map(_ => Break: Stmt) ||
+    (IdParser() ~ T_OP(":=") ~ AExp).map{ case id ~ _ ~ z => Assign(id, z): Stmt } ||
+    (T_KEYWORD("if") ~ BExp ~ T_KEYWORD("then") ~ Block ~ T_KEYWORD("else") ~ Block).map{ case _ ~ y ~ _ ~ u ~ _ ~ w => If(y, u, w): Stmt } ||
+    (T_KEYWORD("while") ~ BExp ~ T_KEYWORD("do") ~ Block).map{ case _ ~ y ~ _ ~ w => While(y, w) : Stmt } ||
+    (T_KEYWORD("for") ~ IdParser() ~ T_OP(":=") ~ AExp ~T_KEYWORD("upto") ~ AExp ~ T_KEYWORD("do") ~ Block).map{ 
+        case _ ~ id ~ _ ~ low ~ _ ~ high ~ _ ~ bl => For(id, low, high, bl) : Stmt } ||
+    (T_KEYWORD("read") ~ IdParser()).map{ case _ ~ id => Read(id): Stmt} ||
+    (T_KEYWORD("write") ~ IdParser()).map{ case _ ~ id => WriteId(id): Stmt} ||
+    (T_KEYWORD("write") ~ StringParser()).map{ case _ ~ s => WriteString(s): Stmt} || 
+    (T_KEYWORD("write") ~ T_PAREN("(") ~ IdParser() ~ T_PAREN(")")).map{ case _ ~ _ ~ id ~ _ => WriteId(id): Stmt} ||
+    (T_KEYWORD("write") ~  T_PAREN("(") ~ StringParser() ~ T_PAREN(")")).map{ case _ ~ _ ~ s ~ _ => WriteString(s): Stmt}
 
 lazy val Stmts: Parser[List[Token], Block] =
-    (Stmt ~ T_SEMI ~ Stmts) ==> { case x ~ _ ~ z => x :: z : Block } ||
-    (Stmt ==> (s => List(s) : Block))
+    (Stmt ~ T_SEMI ~ Stmts).map{ case x ~ _ ~ z => x :: z : Block } ||
+    (Stmt.map(s => List(s) : Block))
 
 lazy val Block: Parser[List[Token], Block] =
-    (T_PAREN("{") ~ Stmts ~ T_PAREN("}")) ==> { case x ~ y ~ z => y} ||
-    (Stmt ==> (s => List(s)))
+    (T_PAREN("{") ~ Stmts ~ T_PAREN("}")).map{ case x ~ y ~ z => y} ||
+    (Stmt.map(s => List(s)))
 
+
--- a/solutions/cw5/fun_llvm.sc	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw5/fun_llvm.sc	Tue Sep 19 09:54:41 2023 +0100
@@ -195,10 +195,9 @@
 
 // convenient string interpolations 
 // for instructions, labels and methods
-import scala.language.implicitConversions
-import scala.language.reflectiveCalls
+
 
-implicit def string_inters(sc: StringContext) = new {
+extension (sc: StringContext) {
     def i(args: Any*): String = "   " ++ sc.s(args:_*) ++ "\n"
     def l(args: Any*): String = sc.s(args:_*) ++ ":\n"
     def m(args: Any*): String = sc.s(args:_*) ++ "\n"
--- a/solutions/cw5/fun_parser.sc	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw5/fun_parser.sc	Tue Sep 19 09:54:41 2023 +0100
@@ -15,46 +15,44 @@
 // case x ~ y ~ z => ...
 case class ~[+A, +B](x: A, y: B)
 
-// constraint for the input
-type IsSeq[A] = A => Seq[_]
-
-abstract class Parser[I : IsSeq, T]{
-  def parse(in: I): Set[(T, I)]
+// parser combinators
+abstract class Parser[I, T](using is: I => Seq[_])  {
+  def parse(in: I): Set[(T, I)]  
 
   def parse_all(in: I) : Set[T] =
-    for ((hd, tl) <- parse(in);
-        if tl.isEmpty) yield hd
+    for ((hd, tl) <- parse(in); 
+        if is(tl).isEmpty) yield hd
 }
 
-// parser combinators
+// alternative parser
+class AltParser[I, T](p: => Parser[I, T], 
+                      q: => Parser[I, T])(using I => Seq[_]) extends Parser[I, T] {
+  def parse(in: I) = p.parse(in) ++ q.parse(in)   
+}
 
 // sequence parser
-class SeqParser[I : IsSeq, T, S](p: => Parser[I, T],
-                                 q: => Parser[I, S]) extends Parser[I, ~[T, S]] {
-  def parse(in: I) =
-    for ((hd1, tl1) <- p.parse(in);
+class SeqParser[I, T, S](p: => Parser[I, T], 
+                         q: => Parser[I, S])(using I => Seq[_]) extends Parser[I, ~[T, S]] {
+  def parse(in: I) = 
+    for ((hd1, tl1) <- p.parse(in); 
          (hd2, tl2) <- q.parse(tl1)) yield (new ~(hd1, hd2), tl2)
 }
 
-// alternative parser
-class AltParser[I : IsSeq, T](p: => Parser[I, T],
-                              q: => Parser[I, T]) extends Parser[I, T] {
-  def parse(in: I) = p.parse(in) ++ q.parse(in)
-}
-
 // map parser
-class MapParser[I : IsSeq, T, S](p: => Parser[I, T],
-                                 f: T => S) extends Parser[I, S] {
+class MapParser[I, T, S](p: => Parser[I, T], 
+                         f: T => S)(using I => Seq[_]) extends Parser[I, S] {
   def parse(in: I) = for ((hd, tl) <- p.parse(in)) yield (f(hd), tl)
 }
 
+
 // more convenient syntax for parser combinators
-implicit def ParserOps[I : IsSeq, T](p: Parser[I, T]) = new {
+extension [I, T](p: Parser[I, T])(using I => Seq[_]) {
   def ||(q : => Parser[I, T]) = new AltParser[I, T](p, q)
   def ~[S] (q : => Parser[I, S]) = new SeqParser[I, T, S](p, q)
   def map[S](f: => T => S) = new MapParser[I, T, S](p, f)
 }
 
+
 // -------------------------------------------------
 // atomic parsers
 
@@ -146,8 +144,8 @@
   }
 }
 
-implicit def parser_interpolation(sc: StringContext) = new {
-  def p(args: Any*) = StrParser(sc.s(args:_*))
+extension (sc: StringContext) {
+   def p(args: Any*) = StrParser(sc.s(args:_*))
 }
 
 
--- a/solutions/cw5/fun_tokens.sc	Sun Sep 17 19:12:57 2023 +0100
+++ b/solutions/cw5/fun_tokens.sc	Tue Sep 19 09:54:41 2023 +0100
@@ -33,9 +33,6 @@
 case class Left(v: Val) extends Val
 case class Right(v: Val) extends Val
 case class Stars(vs: List[Val]) extends Val
-case class Opt(v: Val) extends Val
-case class Pls(vs: List[Val]) extends Val
-case class Nt(vs: List[Val]) extends Val
 case class Rec(x: String, v: Val) extends Val
 
 // some convenience for typing in regular expressions
@@ -45,30 +42,26 @@
   case c::vs => SEQ(CHAR(c), charlist2rexp(vs))
 }
 
-implicit def string2rexp(s : String) : Rexp =
+implicit def string2rexp(s : String) : Rexp = 
   charlist2rexp(s.toList)
 
-implicit def RexpOps(r: Rexp) = new {
+extension (r: Rexp) {
   def | (s: Rexp) = ALT(r, s)
   def % = STAR(r)
-  def ? = OPTIONAL(r)
   def + = PLUS(r)
-  def ^ (n: Int) = NTIMES(r, n)
   def ~ (s: Rexp) = SEQ(r, s)
 }
 
-implicit def stringOps(s: String) = new {
+extension (s: String) {
   def | (r: Rexp) = ALT(s, r)
   def | (r: String) = ALT(s, r)
   def % = STAR(s)
-  def ? = OPTIONAL(s)
-  def + = PLUS(s)
-  def ^ (n: Int) = NTIMES(s, n)
   def ~ (r: Rexp) = SEQ(s, r)
   def ~ (r: String) = SEQ(s, r)
   def $ (r: Rexp) = RECD(s, r)
 }
 
+
 def nullable(r: Rexp) : Boolean = r match {
   case ZERO => false
   case ONE => true
@@ -107,9 +100,6 @@
   case Right(v) => flatten(v)
   case Sequ(v1, v2) => flatten(v1) ++ flatten(v2)
   case Stars(vs) => vs.map(flatten).mkString
-  case Opt(v) => flatten(v)
-  case Pls(vs) => vs.map(flatten).mkString
-  case Nt(vs) => vs.map(flatten).mkString
   case Rec(_, v) => flatten(v)
 }
 
@@ -122,9 +112,6 @@
   case Right(v) => env(v)
   case Sequ(v1, v2) => env(v1) ::: env(v2)
   case Stars(vs) => vs.flatMap(env)
-  case Opt(v) => env(v)
-  case Pls(vs) => vs.flatMap(env)
-  case Nt(vs) => vs.flatMap(env)
   case Rec(x, v) => (x, flatten(v))::env(v)
 }
 
@@ -132,20 +119,21 @@
 // The injection and mkeps part of the lexer
 //===========================================
 
+// Mkeps
 def mkeps(r: Rexp) : Val = r match {
   case ONE => Empty
-  case RANGE(chars) => throw new Exception("lexing error")  // this will never be called but the coursework asks for it so...
-  case ALT(r1, r2) =>
+  case ALT(r1, r2) => 
     if (nullable(r1)) Left(mkeps(r1)) else Right(mkeps(r2))
   case SEQ(r1, r2) => Sequ(mkeps(r1), mkeps(r2))
   case STAR(r) => Stars(Nil)
-  case OPTIONAL(r) => Opt(Empty)
-  case PLUS(r) => Pls(List(mkeps(r))) // scala define a list with one element
-  case NTIMES(r, n) => if (n == 0) Nt(Nil) else Nt(List.fill(n)(mkeps(r))) // wrong
   case RECD(x, r) => Rec(x, mkeps(r))
-  case _ => throw new Exception("lexing error")
+
+  case PLUS(r) => Stars(List(mkeps(r)))   // the first copy must match the empty string
+  case OPTIONAL(r) => if (nullable(r)) Stars(List(mkeps(r))) else Stars(Nil)
+  case NTIMES(r, i) => Stars(List.fill(i)(mkeps(r)))
 }
 
+// Inj
 def inj(r: Rexp, c: Char, v: Val) : Val = (r, v) match {
   case (STAR(r), Sequ(v1, Stars(vs))) => Stars(inj(r, c, v1)::vs)
   case (SEQ(r1, r2), Sequ(v1, v2)) => Sequ(inj(r1, c, v1), v2)
@@ -153,14 +141,16 @@
   case (SEQ(r1, r2), Right(v2)) => Sequ(mkeps(r1), inj(r2, c, v2))
   case (ALT(r1, r2), Left(v1)) => Left(inj(r1, c, v1))
   case (ALT(r1, r2), Right(v2)) => Right(inj(r2, c, v2))
-  case (CHAR(d), Empty) => Chr(c)
-  case (RANGE(chars), Empty) => Chr(c)
-  case (OPTIONAL(r1), v) => Opt(inj(r1, c, v))
-  case (PLUS(r1), Sequ(v1, Stars(vs))) => Pls(inj(r1, c, v1)::vs)
-  case (NTIMES(r1, n), Sequ(v1, Nt(vs))) => Nt(inj(r1, c, v1)::vs)
+  case (CHAR(d), Empty) => Chr(c) 
   case (RECD(x, r1), _) => Rec(x, inj(r1, c, v))
+
+  case (RANGE(_), Empty) => Chr(c)
+  case (PLUS(r), Sequ(v1, Stars(vs))) => Stars(inj(r, c, v1)::vs)
+  case (OPTIONAL(r), v1) => Stars(List(inj(r, c, v1)))
+  case (NTIMES(r, n), Sequ(v1, Stars(vs))) => Stars(inj(r, c, v1)::vs)
 }
 
+
 // some "rectification" functions for simplification
 def F_ID(v: Val): Val = v
 def F_RIGHT(f: Val => Val) = (v:Val) => Right(f(v))