import Mlang.Interpreter

namespace Mlang

inductive Token where
  | int : Int → Token
  | bool : Bool → Token
  | ident : String → Token
  | lparen : Token
  | rparen : Token
  | colon : Token
  | plus : Token
  | minus : Token
  | star : Token
  | slash : Token
  | eq : Token
  | thinArrow : Token
  | fatArrow : Token
  | kwLet : Token
  | kwIn : Token
  | kwIf : Token
  | kwThen : Token
  | kwElse : Token
  | kwFun : Token
  | kwTry : Token
  | kwWith : Token
  deriving Repr, BEq, Inhabited

inductive ParseError where
  | lexError : String → ParseError
  | unexpectedEof : String → ParseError
  | unexpectedToken : String → ParseError
  | trailingTokens : List Token → ParseError
  deriving Repr, Inhabited

abbrev ParseM := Except ParseError

def isDigit (c : Char) : Bool :=
  '0' <= c && c <= '9'

def isIdentStart (c : Char) : Bool :=
  c.isAlpha || c = '_'

def isIdentContinue (c : Char) : Bool :=
  isIdentStart c || isDigit c

def isAtomStart : Token → Bool
  | .int _ | .bool _ | .ident _ | .lparen => true
  | _ => false

partial def spanChars (p : Char → Bool) : List Char → List Char × List Char
  | [] => ([], [])
  | c :: cs =>
      if p c then
        let (taken, rest) := spanChars p cs
        (c :: taken, rest)
      else
        ([], c :: cs)

def charsToString (chars : List Char) : String :=
  String.ofList chars

def lexIdent (chars : List Char) : Token × List Char :=
  let (nameChars, rest) := spanChars isIdentContinue chars
  let name := charsToString nameChars
  let token :=
    match name with
    | "let" => .kwLet
    | "in" => .kwIn
    | "if" => .kwIf
    | "then" => .kwThen
    | "else" => .kwElse
    | "fun" => .kwFun
    | "try" => .kwTry
    | "with" => .kwWith
    | "true" => .bool true
    | "false" => .bool false
    | _ => .ident name
  (token, rest)

def lexNumber (chars : List Char) : ParseM (Token × List Char) := do
  let (digits, rest) := spanChars isDigit chars
  match String.toInt? (charsToString digits) with
  | some n => pure (.int n, rest)
  | none => throw (.lexError "invalid integer literal")

partial def lexChars : List Char → ParseM (List Token)
  | [] => pure []
  | c :: cs =>
      if c.isWhitespace then
        lexChars cs
      else if isDigit c then do
        let (tok, rest) ← lexNumber (c :: cs)
        let toks ← lexChars rest
        pure (tok :: toks)
      else if isIdentStart c then do
        let (tok, rest) := lexIdent (c :: cs)
        let toks ← lexChars rest
        pure (tok :: toks)
      else
        match c, cs with
        | '(', _ => do pure (.lparen :: (← lexChars cs))
        | ')', _ => do pure (.rparen :: (← lexChars cs))
        | ':', _ => do pure (.colon :: (← lexChars cs))
        | '+', _ => do pure (.plus :: (← lexChars cs))
        | '-', '>' :: rest => do pure (.thinArrow :: (← lexChars rest))
        | '-', _ => do pure (.minus :: (← lexChars cs))
        | '*', _ => do pure (.star :: (← lexChars cs))
        | '/', _ => do pure (.slash :: (← lexChars cs))
        | '=', '>' :: rest => do pure (.fatArrow :: (← lexChars rest))
        | '=', _ => do pure (.eq :: (← lexChars cs))
        | _, _ => throw (.lexError s!"unexpected character '{c}'")

def lex (input : String) : ParseM (List Token) :=
  lexChars input.toList

abbrev ParserState := List Token

def expectToken (expected : Token) : ParserState → ParseM ParserState
  | tok :: rest =>
      if tok == expected then
        pure rest
      else
        throw (.unexpectedToken s!"expected {reprStr expected}, got {reprStr tok}")
  | [] => throw (.unexpectedEof s!"expected {reprStr expected}")

mutual
  partial def parseTy (tokens : ParserState) : ParseM (Ty × ParserState) := do
    let (lhs, rest) ← parseTyAtom tokens
    match rest with
    | .thinArrow :: rest =>
        let (rhs, rest) ← parseTy rest
        pure (.funTy lhs [] rhs, rest)
    | _ => pure (lhs, rest)

  partial def parseTyAtom : ParserState → ParseM (Ty × ParserState)
    | .ident "Int" :: rest => pure (.int, rest)
    | .ident "Bool" :: rest => pure (.bool, rest)
    | .lparen :: rest => do
        let (ty, rest) ← parseTy rest
        let rest ← expectToken .rparen rest
        pure (ty, rest)
    | tok :: _ => throw (.unexpectedToken s!"expected type, got {reprStr tok}")
    | [] => throw (.unexpectedEof "expected type")
end

mutual
  partial def parseExpr (tokens : ParserState) : ParseM (Expr × ParserState) := do
    match tokens with
    | .kwLet :: .ident name :: .eq :: rest => do
        let (value, rest) ← parseExpr rest
        let rest ← expectToken .kwIn rest
        let (body, rest) ← parseExpr rest
        pure (.letE name value body, rest)
    | .kwIf :: rest => do
        let (cond, rest) ← parseExpr rest
        let rest ← expectToken .kwThen rest
        let (thenBranch, rest) ← parseExpr rest
        let rest ← expectToken .kwElse rest
        let (elseBranch, rest) ← parseExpr rest
        pure (.ifE cond thenBranch elseBranch, rest)
    | .kwTry :: rest => do
        let (body, rest) ← parseExpr rest
        let rest ← expectToken .kwWith rest
        let (handler, rest) ← parseExpr rest
        pure (.tryE body handler, rest)
    | .kwFun :: .lparen :: .ident name :: .colon :: rest => do
        let (paramTy, rest) ← parseTy rest
        let rest ← expectToken .rparen rest
        let rest ← expectToken .fatArrow rest
        let (body, rest) ← parseExpr rest
        pure (.lam name paramTy body, rest)
    | _ =>
        parseEq tokens

  partial def parseEq (tokens : ParserState) : ParseM (Expr × ParserState) := do
    let (lhs, rest) ← parseAddSub tokens
    match rest with
    | .eq :: rest =>
        let (rhs, rest) ← parseEq rest
        pure (Expr.eq lhs rhs, rest)
    | _ => pure (lhs, rest)

  partial def parseAddSub (tokens : ParserState) : ParseM (Expr × ParserState) := do
    let (first, rest) ← parseMul tokens
    parseAddSubTail first rest

  partial def parseAddSubTail (lhs : Expr) (tokens : ParserState) : ParseM (Expr × ParserState) := do
    match tokens with
    | .plus :: rest => do
        let (rhs, rest) ← parseMul rest
        parseAddSubTail (.add lhs rhs) rest
    | .minus :: rest => do
        let (rhs, rest) ← parseMul rest
        parseAddSubTail (.sub lhs rhs) rest
    | _ => pure (lhs, tokens)

  partial def parseMul (tokens : ParserState) : ParseM (Expr × ParserState) := do
    let (first, rest) ← parseApp tokens
    parseMulTail first rest

  partial def parseMulTail (lhs : Expr) (tokens : ParserState) : ParseM (Expr × ParserState) := do
    match tokens with
    | .star :: rest => do
        let (rhs, rest) ← parseApp rest
        parseMulTail (.mul lhs rhs) rest
    | .slash :: rest => do
        let (rhs, rest) ← parseApp rest
        parseMulTail (.div lhs rhs) rest
    | _ => pure (lhs, tokens)

  partial def parseApp (tokens : ParserState) : ParseM (Expr × ParserState) := do
    let (fn, rest) ← parseAtom tokens
    parseAppTail fn rest

  partial def parseAppTail (fn : Expr) (tokens : ParserState) : ParseM (Expr × ParserState) := do
    match tokens with
    | tok :: _ =>
        if isAtomStart tok then do
          let (arg, rest) ← parseAtom tokens
          parseAppTail (.app fn arg) rest
        else
          pure (fn, tokens)
    | [] => pure (fn, [])

  partial def parseAtom : ParserState → ParseM (Expr × ParserState)
    | .int n :: rest => pure (.int n, rest)
    | .bool b :: rest => pure (.bool b, rest)
    | .ident name :: rest => pure (.var name, rest)
    | .lparen :: rest => do
        let (expr, rest) ← parseExpr rest
        let rest ← expectToken .rparen rest
        pure (expr, rest)
    | tok :: _ => throw (.unexpectedToken s!"expected expression, got {reprStr tok}")
    | [] => throw (.unexpectedEof "expected expression")
end

def parse (input : String) : ParseM Expr := do
  let tokens ← lex input
  let (expr, rest) ← parseExpr tokens
  match rest with
  | [] => pure expr
  | _ => throw (.trailingTokens rest)

end Mlang
