diff --git a/src/main/wacc/Error.scala b/src/main/wacc/Error.scala
index c627246..5aa0ad7 100644
--- a/src/main/wacc/Error.scala
+++ b/src/main/wacc/Error.scala
@@ -6,9 +6,56 @@ import wacc.types._
 enum Error {
   case DuplicateDeclaration(ident: ast.Ident)
   case UndefinedIdentifier(ident: ast.Ident, identType: renamer.IdentType)
-
   case FunctionParamsMismatch(pos: Position, expected: Int, got: Int)
   case SemanticError(pos: Position, msg: String)
   case TypeMismatch(pos: Position, expected: SemType, got: SemType, msg: String)
   case InternalError(pos: Position, msg: String)
 }
+
+def printError(error: Error)(using errorContent: String): Unit = {
+  println("Semantic error:")
+  error match {
+    case Error.DuplicateDeclaration(ident) =>
+      printPosition(ident.pos)
+      println(s"Duplicate declaration of identifier ${ident.v}")
+      highlight(ident.pos, ident.v.length)
+    case Error.UndefinedIdentifier(ident, identType) =>
+      printPosition(ident.pos)
+      println(s"Undefined ${identType.toString.toLowerCase()} ${ident.v}")
+      highlight(ident.pos, ident.v.length)
+    case Error.FunctionParamsMismatch(pos, expected, got) =>
+      printPosition(pos)
+      println(s"Function expects $expected parameters, got $got")
+      highlight(pos, 1)
+    case Error.TypeMismatch(pos, expected, got, msg) =>
+      printPosition(pos)
+      println(msg)
+      highlight(pos, 1)
+    case Error.SemanticError(pos, msg) =>
+      printPosition(pos)
+      println(msg)
+      highlight(pos, 1)
+    case wacc.Error.InternalError(pos, msg) =>
+      printPosition(pos)
+      println(s"Internal error: $msg")
+      highlight(pos, 1)
+  }
+
+}
+
+def highlight(pos: Position, size: Int)(using errorContent: String): Unit = {
+  val lines = errorContent.split("\n")
+
+  val preLine = if (pos.line > 1) lines(pos.line - 2) else ""
+  val midLine = lines(pos.line - 1)
+  val postLine = if (pos.line < lines.size) lines(pos.line) else ""
+  val linePointer = " " * (pos.column + 2) + ("^" * (size)) + "\n"
+
+  println(
+    s"  >$preLine\n  >$midLine\n$linePointer  >$postLine"
+  )
+}
+
+def printPosition(pos: Position): Unit = {
+  println(s"(line ${pos.line}, column ${pos.column}):")
+}
diff --git a/src/main/wacc/Main.scala b/src/main/wacc/Main.scala
index 59796ff..93bc158 100644
--- a/src/main/wacc/Main.scala
+++ b/src/main/wacc/Main.scala
@@ -38,7 +38,8 @@ def compile(contents: String): Int = {
       given ctx: typeChecker.TypeCheckerCtx = typeChecker.TypeCheckerCtx(names, errors)
       typeChecker.check(prog)
       if (errors.result.nonEmpty) {
-        errors.result.foreach(println)
+        given errorContent: String = contents
+        errors.result.foreach(printError)
         200
       } else 0
     case Failure(msg) =>
diff --git a/src/main/wacc/lexer.scala b/src/main/wacc/lexer.scala
index 4a810c0..1c609a9 100644
--- a/src/main/wacc/lexer.scala
+++ b/src/main/wacc/lexer.scala
@@ -31,7 +31,9 @@ val errConfig = new ErrorConfig {
     "fst" -> Label("pair extraction"),
     "snd" -> Label("pair extraction"),
     "false" -> Label("boolean literal"),
-    "true" -> Label("boolean literal")
+    "true" -> Label("boolean literal"),
+    "=" -> Label("assignment"),
+    "[" -> Label("array index")
   )
 }
 object lexer {
@@ -83,8 +85,8 @@ object lexer {
   val errTokens = Seq(
     lexer.nonlexeme.names.identifier.map(v => s"identifier $v"),
     lexer.nonlexeme.integer.decimal32[Int].map(n => s"integer $n"),
-    lexer.nonlexeme.character.ascii.map(c => s"character literal $c"),
-    lexer.nonlexeme.string.ascii.map(s => s"string literal $s"),
+    (lexer.nonlexeme.character.ascii).map(c => s"character literal \'$c\'"),
+    lexer.nonlexeme.string.ascii.map(s => s"string literal \"$s\""),
     character.whitespace.map(_ => "")
   ) ++ desc.symbolDesc.hardKeywords.map { k =>
     lexer.nonlexeme.symbol(k).as(s"keyword $k")
diff --git a/src/main/wacc/parser.scala b/src/main/wacc/parser.scala
index 5751732..dac9fcd 100644
--- a/src/main/wacc/parser.scala
+++ b/src/main/wacc/parser.scala
@@ -118,7 +118,9 @@ object parser {
     "begin" ~> many(
       atomic(`<type>`.label("function declaration") <~> `<ident>` <~ "(") <**> `<partial-func-decl>`
     ).label("function declaration"),
-    `<stmt>`.label("main program body") <~ "end"
+    (atomic(`<ident>` <~ "(") ~> fail("function is missing return type") | `<stmt>`.label(
+      "main program body"
+    )) <~ "end"
   )
   private lazy val `<partial-func-decl>` =
     FuncDecl(