package fos import scala.util.parsing.combinator.syntactical.StandardTokenParsers import scala.util.parsing.input.* /** This object implements a parser and evaluator for the NB * language of booleans and numbers found in Chapter 3 of * the TAPL book. */ object Arithmetic extends StandardTokenParsers { lexical.reserved ++= List("true", "false", "if", "then", "else", "succ", "pred", "iszero") import lexical.NumericLit /** term ::= 'true' | 'false' | 'if' term 'then' term 'else' term | numericLit | 'succ' term | 'pred' term | 'iszero' term */ def NumericRec(c:Int,s:Term): (Int,Term) = { if(c<=0){ (0,s) }else{ NumericRec(c-1,Succ(s)) } } def term: Parser[Term] = { "true" ^^^ True | "false" ^^^ False | ("if" ~> term) ~ ("then" ~> term) ~ ("else" ~> term) ^^ { case cond ~ t1 ~ t2 => If(cond, t1, t2) } | numericLit ^^ { case v if (v.toInt >=0) => NumericRec(v.toInt,Zero)._2} | ("succ" ~> term) ^^ { Succ(_) } | ("pred" ~> term) ^^ { Pred(_) } | ("iszero" ~> term) ^^ { IsZero(_) } | failure("Unknown Term") } case class NoReductionPossible(t: Term) extends Exception(t.toString) /** Return a list of at most n terms, each being one step of reduction. */ def path(t: Term, n: Int = 64): List[Term] = if (n <= 0) Nil else t :: { try { path(reduce(t), n - 1) } catch { case NoReductionPossible(t1) => Nil } } def isNumeric(t: Term): Boolean = t match { case Zero => true case Pred(v) => isNumeric(v) case Succ(v) => isNumeric(v) case _ => false; } /** Perform one step of reduction, when possible. * If reduction is not possible NoReductionPossible exception * with corresponding irreducible term should be thrown. */ def reduce(t: Term): Term = t match { case If(cond, t1, t2) => if(cond == True) t1 else if (cond == False) t2 else If(reduce(cond), t1, t2) case IsZero(z) => z match { case Zero => True case Succ(_) => False case other => IsZero(reduce(other)) } case Succ(s) => s match { case Zero => throw NoReductionPossible(Zero) case other => Succ(reduce(other)) } case Pred(p) => p match { case Zero => Zero case Succ(nv) if (isNumeric(nv)) => nv case other => Pred(reduce(other)) } case other => throw NoReductionPossible(other) } case class TermIsStuck(t: Term) extends Exception(t.toString) /** Perform big step evaluation (result is always a value.) * If evaluation is not possible TermIsStuck exception with * corresponding inner irreducible term should be thrown. */ def eval(t: Term): Term = t match { case True => True case False => False case If(cond, t1, t2) => eval(cond) match { case True => eval(t1) case False => eval(t2) case _ => throw TermIsStuck(t) } case Zero => Zero case Succ(v) => eval(v) match { case Zero => Zero case Pred(nv) => eval(nv) case _ => throw TermIsStuck(t) } case Pred(v) => eval(v) match { case Zero => Zero case Succ(nv) => eval(nv) case _ => throw TermIsStuck(t) } case IsZero(v) => eval(v) match { case Zero => True case Succ(nv) => False case _ => throw TermIsStuck(t) } } def main(args: Array[String]): Unit = { val stdin = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)) val tokens = new lexical.Scanner(stdin.readLine()) phrase(term)(tokens) match { case Success(trees, _) => for (t <- path(trees)) println(t) try { print("Big step: ") println(eval(trees)) } catch { case TermIsStuck(t) => println("Stuck term: " + t) } case e => println(e) } } }