import Mlang.Interpreter namespace Mlang inductive Token where | int : Int → Token | bool : Bool → Token | string : String → Token | ident : String → Token | lparen : Token | rparen : Token | lbracket : Token | rbracket : Token | lbrace : Token | rbrace : Token | semicolon : Token | comma : 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 | kwPmap : Token | kwNull : 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 _ | .string _ | .ident _ | .lparen | .lbracket | .lbrace | .kwNull => 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 | "pmap" => .kwPmap | "null" => .kwNull | "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 takeStringChars : List Char → ParseM (List Char × List Char) | [] => throw (.lexError "unterminated string literal") | '"' :: rest => pure ([], rest) | '\\' :: '"' :: rest => do let (chars, tail) ← takeStringChars rest pure ('"' :: chars, tail) | '\\' :: 'n' :: rest => do let (chars, tail) ← takeStringChars rest pure ('\n' :: chars, tail) | c :: rest => do let (chars, tail) ← takeStringChars rest pure (c :: chars, tail) def lexString (chars : List Char) : ParseM (Token × List Char) := do let (content, rest) ← takeStringChars chars pure (.string (charsToString content), rest) 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 let (tok, rest) ← lexString cs let toks ← lexChars rest pure (tok :: toks) | '(', _ => do pure (.lparen :: (← lexChars cs)) | ')', _ => do pure (.rparen :: (← lexChars cs)) | '[', _ => do pure (.lbracket :: (← lexChars cs)) | ']', _ => do pure (.rbracket :: (← lexChars cs)) | '{', _ => do pure (.lbrace :: (← lexChars cs)) | '}', _ => do pure (.rbrace :: (← lexChars cs)) | ';', _ => do pure (.semicolon :: (← lexChars cs)) | ',', _ => do pure (.comma :: (← 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 inductive ReplCommand where | bind : Name → Expr → ReplCommand | evalProgram : List Expr → ReplCommand deriving Repr, Inhabited 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}") def threadStep (current : Expr) (stepName : Name) : Expr := .app (.var stepName) current def threadTemplateStep (current : Expr) (step : Expr) : Expr := match step with | .app fn arg => .app (.app fn current) arg | .var _ => .app step current | _ => .app step current 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) | .ident "String" :: rest => pure (.string, rest) | .ident "Data" :: rest => pure (.data, rest) | .ident "List" :: rest => pure (.list, rest) | .ident "Result" :: rest => pure (.result, rest) | .ident "Error" :: rest => pure (.error, 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 | .thinArrow :: rest => do let (start, rest) ← parseThreadSeed rest parseThreadTail start rest | .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 match rest with | .ident errName :: .fatArrow :: rest => do let (handler, rest) ← parseExpr rest pure (.tryE body errName handler, rest) | tok :: _ => throw (.unexpectedToken s!"expected handler binder, got {reprStr tok}") | [] => throw (.unexpectedEof "expected handler binder") | .kwPmap :: .ident itemName :: .kwIn :: rest => do let (collection, rest) ← parseExpr rest let rest ← expectToken .fatArrow rest let (body, rest) ← parseExpr rest pure (.parMapE itemName collection body, 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 parseThreadTail (current : Expr) (tokens : ParserState) : ParseM (Expr × ParserState) := do match tokens with | .ident stepName :: rest => parseThreadTail (threadStep current stepName) rest | .lparen :: _ => do let (step, rest) ← parseAtom tokens parseThreadTail (threadTemplateStep current step) rest | _ => pure (current, tokens) partial def parseThreadSeed (tokens : ParserState) : ParseM (Expr × ParserState) := do parseAtom 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 parseArrayItems (items : List Expr) : ParserState → ParseM (Expr × ParserState) | .rbracket :: rest => pure (.arrayE items.reverse, rest) | .comma :: rest => do let (item, rest) ← parseExpr rest parseArrayItems (item :: items) rest | tok :: _ => throw (.unexpectedToken s!"expected ',' or ']', got {reprStr tok}") | [] => throw (.unexpectedEof "unterminated array") partial def parseArray : ParserState → ParseM (Expr × ParserState) | .rbracket :: rest => pure (.arrayE [], rest) | tokens => do let (first, rest) ← parseExpr tokens parseArrayItems [first] rest partial def parseFieldKey : ParserState → ParseM (String × ParserState) | .ident name :: rest => pure (name, rest) | .string name :: rest => pure (name, rest) | tok :: _ => throw (.unexpectedToken s!"expected field key, got {reprStr tok}") | [] => throw (.unexpectedEof "expected field key") partial def parseRecordFields (fields : List (String × Expr)) : ParserState → ParseM (Expr × ParserState) | .rbrace :: rest => pure (.recordE fields.reverse, rest) | .comma :: rest => do let (name, rest) ← parseFieldKey rest let rest ← expectToken .eq rest let (value, rest) ← parseExpr rest parseRecordFields ((name, value) :: fields) rest | tok :: _ => throw (.unexpectedToken s!"expected ',' or '}}', got {reprStr tok}") | [] => throw (.unexpectedEof "unterminated record") partial def parseRecord : ParserState → ParseM (Expr × ParserState) | .rbrace :: rest => pure (.recordE [], rest) | tokens => do let (name, rest) ← parseFieldKey tokens let rest ← expectToken .eq rest let (value, rest) ← parseExpr rest parseRecordFields [(name, value)] rest partial def parseAtom : ParserState → ParseM (Expr × ParserState) | .int n :: rest => pure (.int n, rest) | .bool b :: rest => pure (.bool b, rest) | .string s :: rest => pure (.string s, rest) | .kwNull :: rest => pure (.null, rest) | .ident name :: rest => pure (.var name, rest) | .lbracket :: rest => parseArray rest | .lbrace :: rest => parseRecord 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 partial def parseProgramTokens : ParserState → ParseM (List Expr) | [] => pure [] | .semicolon :: rest => parseProgramTokens rest | tokens => do let (expr, rest) ← parseExpr tokens match rest with | [] => pure [expr] | .semicolon :: more => let tail ← parseProgramTokens more pure (expr :: tail) | _ => throw (.trailingTokens rest) def parseProgram (input : String) : ParseM (List Expr) := do let tokens ← lex input parseProgramTokens tokens def parseReplCommand (input : String) : ParseM ReplCommand := do let tokens ← lex input match tokens with | .kwLet :: .ident name :: .eq :: rest => match parseExpr rest with | .ok (value, []) => pure (.bind name value) | _ => let program ← parseProgramTokens tokens pure (.evalProgram program) | _ => let program ← parseProgramTokens tokens pure (.evalProgram program) def parse (input : String) : ParseM Expr := do let program ← parseProgram input match program with | [expr] => pure expr | [] => throw (ParseError.unexpectedEof "expected expression") | _ => throw (ParseError.unexpectedToken "expected a single expression") end Mlang