From 061303ccc06b72752872e038b924cc08a21132b1 Mon Sep 17 00:00:00 2001 From: vvsob Date: Thu, 25 Dec 2025 19:00:01 +0300 Subject: [PATCH 01/10] add fancy prompt before input --- app/Main.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Main.hs b/app/Main.hs index 6966ae6..a93fb36 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,6 +1,7 @@ import Lox.Scanner import Lox.Parser import Lox.Interpreter +import System.IO run :: String -> IO () run source = print result @@ -9,4 +10,4 @@ run source = print result tokens = scanTokensFromSource source main :: IO () -main = getLine >>= run +main = putStr ">> " >> hFlush stdout >> getLine >>= run From 75a33c2d94e27ef8479e9f0420425c66dd5b6419 Mon Sep 17 00:00:00 2001 From: vvsob Date: Mon, 29 Dec 2025 23:57:15 +0300 Subject: [PATCH 02/10] awful lot of error handling --- app/Main.hs | 16 ++++++-- src/Lox/Interpreter.hs | 4 +- src/Lox/Parser.hs | 90 ++++++++++++++++++++++++++---------------- src/Lox/Scanner.hs | 60 +++++++++++++++------------- 4 files changed, 103 insertions(+), 67 deletions(-) diff --git a/app/Main.hs b/app/Main.hs index a93fb36..ff2ce5f 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -4,10 +4,18 @@ import Lox.Interpreter import System.IO run :: String -> IO () -run source = print result - where result = eval expr - expr = parse tokens - tokens = scanTokensFromSource source +run source = do + let tokensMaybe = scanTokensFromSource source + case tokensMaybe of + Left UnexpectedCharacterError -> putStrLn "Unexpected character" + Right tokens -> do + let exprMaybe = parse tokens + case exprMaybe of + Left ExpectedExpressionError -> putStrLn "Expected expression" + Left MismatchedParenthesesError -> putStrLn "Mismatched parentheses" + Right expr -> do + result <- eval expr + print result main :: IO () main = putStr ">> " >> hFlush stdout >> getLine >>= run diff --git a/src/Lox/Interpreter.hs b/src/Lox/Interpreter.hs index 29476bd..840b75d 100644 --- a/src/Lox/Interpreter.hs +++ b/src/Lox/Interpreter.hs @@ -8,8 +8,8 @@ import Control.Monad.State data InterpreterState = InterpreterState -eval :: Expr -> Object -eval expr = evalState (interpret expr) InterpreterState +eval :: Expr -> IO Object +eval expr = return $ evalState (interpret expr) InterpreterState interpret :: Expr -> State InterpreterState Object interpret (Literal value) = return value diff --git a/src/Lox/Parser.hs b/src/Lox/Parser.hs index d149c8c..1966224 100644 --- a/src/Lox/Parser.hs +++ b/src/Lox/Parser.hs @@ -1,4 +1,5 @@ module Lox.Parser ( + ParserError (..), parse ) where @@ -9,6 +10,8 @@ import Lox.Expr data ParserState = ParserState {tokens :: [Token]} +data ParserError = MismatchedParenthesesError | ExpectedExpressionError + -- expression → equality ; -- equality → comparison ( ( "!=" | "==" ) comparison )* ; -- comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ; @@ -19,63 +22,82 @@ data ParserState = ParserState {tokens :: [Token]} -- primary → NUMBER | STRING | "true" | "false" | "nil" -- | "(" expression ")" ; -parse :: [Token] -> Expr +parse :: [Token] -> Either ParserError Expr parse tokens = evalState expression (ParserState {tokens=tokens}) -expression :: State ParserState Expr +expression :: State ParserState (Either ParserError Expr) expression = equality -equality :: State ParserState Expr +equality :: State ParserState (Either ParserError Expr) equality = do - expr <- comparison - mergeExpressionMaybe expr <$> matchTail [BANG_EQUAL, EQUAL_EQUAL] comparison + exprMaybe <- comparison + case exprMaybe of + Left err -> return $ Left err + Right expr -> fmap (mergeExpressionMaybe expr) <$> matchTail [BANG_EQUAL, EQUAL_EQUAL] comparison -comparison :: State ParserState Expr +comparison :: State ParserState (Either ParserError Expr) comparison = do - expr <- term - mergeExpressionMaybe expr <$> matchTail [GREATER, GREATER_EQUAL, LESS, LESS_EQUAL] term + exprMaybe <- term + case exprMaybe of + Left err -> return $ Left err + Right expr -> fmap (mergeExpressionMaybe expr) <$> matchTail [GREATER, GREATER_EQUAL, LESS, LESS_EQUAL] term -term :: State ParserState Expr +term :: State ParserState (Either ParserError Expr) term = do - expr <- factor - mergeExpressionMaybe expr <$> matchTail [MINUS, PLUS] factor + exprMaybe <- factor + case exprMaybe of + Left err -> return $ Left err + Right expr -> fmap (mergeExpressionMaybe expr) <$> matchTail [MINUS, PLUS] factor -factor :: State ParserState Expr +factor :: State ParserState (Either ParserError Expr) factor = do - expr <- unary - mergeExpressionMaybe expr <$> matchTail [SLASH, STAR] unary + exprMaybe <- unary + case exprMaybe of + Left err -> return $ Left err + Right expr -> fmap (mergeExpressionMaybe expr) <$> matchTail [SLASH, STAR] unary -unary :: State ParserState Expr +unary :: State ParserState (Either ParserError Expr) unary = do maybeOperator <- matchToken [BANG, MINUS] case maybeOperator of Nothing -> primary - Just op -> Unary op <$> unary + Just op -> do + exprMaybe <- unary + case exprMaybe of + Left err -> return $ Left err + Right expr -> return $ Right $ Unary op expr -primary :: State ParserState Expr +primary :: State ParserState (Either ParserError Expr) primary = do token <- advance case tokenType token of - FALSE -> return $ Literal $ BoolObject False - TRUE -> return $ Literal $ BoolObject True - NIL -> return $ Literal NullObject - NUMBER -> return $ Literal $ tokenObject token - STRING -> return $ Literal $ tokenObject token + FALSE -> return $ Right $ Literal $ BoolObject False + TRUE -> return $ Right $ Literal $ BoolObject True + NIL -> return $ Right $ Literal NullObject + NUMBER -> return $ Right $ Literal $ tokenObject token + STRING -> return $ Right $ Literal $ tokenObject token LEFT_PAREN -> do - expr <- expression - consume RIGHT_PAREN "Expected '(' after ')'" - return $ Grouping expr - _ -> error "Expected expression" + exprMaybe <- expression + case exprMaybe of + Left err -> return $ Left err + Right expr -> do + consume RIGHT_PAREN MismatchedParenthesesError + return $ Right $ Grouping expr + _ -> return $ Left ExpectedExpressionError -matchTail :: [TokenType] -> State ParserState Expr -> State ParserState (Maybe (Token, Expr)) +matchTail :: [TokenType] -> State ParserState (Either ParserError Expr) -> State ParserState (Either ParserError (Maybe (Token, Expr))) matchTail tokenTypes f = do maybeOperator <- matchToken tokenTypes case maybeOperator of - Nothing -> return Nothing + Nothing -> return $ Right Nothing Just op -> do - expr <- comparison - rest <- matchTail tokenTypes f - return $ Just (op, mergeExpressionMaybe expr rest) + exprMaybe <- comparison + restMaybe <- matchTail tokenTypes f + case (exprMaybe, restMaybe) of + (Left err, _) -> return $ Left err + (_, Left err) -> return $ Left err + (Right expr, Right rest) -> return $ Right $ Just (op, mergeExpressionMaybe expr rest) + mergeExpressionMaybe :: Expr -> Maybe (Token, Expr) -> Expr mergeExpressionMaybe expr Nothing = expr mergeExpressionMaybe left (Just (op, right)) = Binary left op right @@ -91,10 +113,10 @@ check t = do atEnd <- isAtEnd if atEnd then return False else (== t) . tokenType <$> peek -consume :: TokenType -> String -> State ParserState Token -consume t msg = do +consume :: TokenType -> ParserError -> State ParserState (Either ParserError Token) +consume t err = do isOk <- check t - if isOk then advance else error msg + if isOk then Right <$> advance else return $ Left err advance :: State ParserState Token advance = state (\s@ParserState {tokens=(t:ts)} -> (t, s {tokens = ts})) diff --git a/src/Lox/Scanner.hs b/src/Lox/Scanner.hs index 91dfbda..2570af4 100644 --- a/src/Lox/Scanner.hs +++ b/src/Lox/Scanner.hs @@ -2,6 +2,7 @@ module Lox.Scanner ( TokenType (..), Object (..), Token (..), + ScannerError (..), scanTokensFromSource ) where @@ -44,21 +45,24 @@ data Token = Token { data ScannerState = ScannerState {source :: String, current :: String, lineNumber :: Int} +data ScannerError = UnexpectedCharacterError + emptyScannerState :: String -> ScannerState emptyScannerState source = ScannerState {source=source, current="", lineNumber=1} -scanTokensFromSource :: String -> [Token] +scanTokensFromSource :: String -> Either ScannerError [Token] scanTokensFromSource source = evalState scanTokens (emptyScannerState source) -scanTokens :: State ScannerState [Token] +scanTokens :: State ScannerState (Either ScannerError [Token]) scanTokens = do atEnd <- isAtEnd - if atEnd then return <$> addToken EOF else do + if atEnd then return . return <$> addToken EOF else do maybeToken <- scanToken case maybeToken of - Nothing -> scanTokens - Just t -> (t :) <$> scanTokens + Right Nothing -> scanTokens + Right (Just t) -> fmap (fmap (t :)) scanTokens + Left err -> return $ Left err isAtEnd :: State ScannerState Bool isAtEnd = gets scannerIsAtEnd @@ -66,32 +70,34 @@ isAtEnd = gets scannerIsAtEnd scannerIsAtEnd :: ScannerState -> Bool scannerIsAtEnd ScannerState {source=source} = null source -scanToken :: State ScannerState (Maybe Token) +scanToken :: State ScannerState (Either ScannerError (Maybe Token)) scanToken = do resetCurrent c <- advance + let ok = Right . Just + let nothing = Right Nothing case c of - '(' -> Just <$> addToken LEFT_PAREN - ')' -> Just <$> addToken RIGHT_PAREN - '{' -> Just <$> addToken LEFT_BRACE - '}' -> Just <$> addToken RIGHT_BRACE - ',' -> Just <$> addToken COMMA - '.' -> Just <$> addToken DOT - '-' -> Just <$> addToken MINUS - '+' -> Just <$> addToken PLUS - ';' -> Just <$> addToken SEMICOLON - '*' -> Just <$> addToken STAR - '!' -> Just <$> ifM (match '=') (addToken BANG_EQUAL) (addToken BANG) - '=' -> Just <$> ifM (match '=') (addToken EQUAL_EQUAL) (addToken EQUAL) - '<' -> Just <$> ifM (match '=') (addToken LESS_EQUAL) (addToken LESS) - '>' -> Just <$> ifM (match '=') (addToken GREATER_EQUAL) (addToken GREATER) - '/' -> ifM (match '/') (advanceLine >> return Nothing) (Just <$> addToken SLASH) - '"' -> Just <$> scanString - ' ' -> return Nothing - '\r' -> return Nothing - '\t' -> return Nothing - '\n' -> return Nothing - c -> if isDigit c then Just <$> scanNumber else if isAlpha c then Just <$> scanIdentifier else error "Unexpected character" + '(' -> ok <$> addToken LEFT_PAREN + ')' -> ok <$> addToken RIGHT_PAREN + '{' -> ok <$> addToken LEFT_BRACE + '}' -> ok <$> addToken RIGHT_BRACE + ',' -> ok <$> addToken COMMA + '.' -> ok <$> addToken DOT + '-' -> ok <$> addToken MINUS + '+' -> ok <$> addToken PLUS + ';' -> ok <$> addToken SEMICOLON + '*' -> ok <$> addToken STAR + '!' -> ok <$> ifM (match '=') (addToken BANG_EQUAL) (addToken BANG) + '=' -> ok <$> ifM (match '=') (addToken EQUAL_EQUAL) (addToken EQUAL) + '<' -> ok <$> ifM (match '=') (addToken LESS_EQUAL) (addToken LESS) + '>' -> ok <$> ifM (match '=') (addToken GREATER_EQUAL) (addToken GREATER) + '/' -> ifM (match '/') (advanceLine >> return nothing) (ok <$> addToken SLASH) + '"' -> ok <$> scanString + ' ' -> return nothing + '\r' -> return nothing + '\t' -> return nothing + '\n' -> return nothing + c -> if isDigit c then ok <$> scanNumber else if isAlpha c then ok <$> scanIdentifier else return $ Left UnexpectedCharacterError scanString :: State ScannerState Token scanString = do From f5e7b7c091ee8fe8c2adbbb8804df356581f088d Mon Sep 17 00:00:00 2001 From: vvsob Date: Tue, 30 Dec 2025 01:27:01 +0300 Subject: [PATCH 03/10] expressions --- app/Main.hs | 9 +++---- src/Lox/Expr.hs | 6 +++++ src/Lox/Interpreter.hs | 38 ++++++++++++++++++++-------- src/Lox/Parser.hs | 57 +++++++++++++++++++++++++++++++++++++++--- src/Lox/Scanner.hs | 6 ++--- 5 files changed, 95 insertions(+), 21 deletions(-) diff --git a/app/Main.hs b/app/Main.hs index ff2ce5f..a4dff38 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -9,13 +9,12 @@ run source = do case tokensMaybe of Left UnexpectedCharacterError -> putStrLn "Unexpected character" Right tokens -> do - let exprMaybe = parse tokens - case exprMaybe of + let stmtMaybe = parse tokens + case stmtMaybe of Left ExpectedExpressionError -> putStrLn "Expected expression" Left MismatchedParenthesesError -> putStrLn "Mismatched parentheses" - Right expr -> do - result <- eval expr - print result + Left ExpectedSemicolonError -> putStrLn "Expected semicolon" + Right statements -> runStatements statements main :: IO () main = putStr ">> " >> hFlush stdout >> getLine >>= run diff --git a/src/Lox/Expr.hs b/src/Lox/Expr.hs index 7a6c174..e181e66 100644 --- a/src/Lox/Expr.hs +++ b/src/Lox/Expr.hs @@ -1,9 +1,15 @@ module Lox.Expr ( + Stmt (..), Expr (..) ) where import Lox.Scanner +data Stmt = + Expression Expr | + Print Expr + deriving Show + data Expr = Literal Object | Unary Token Expr | diff --git a/src/Lox/Interpreter.hs b/src/Lox/Interpreter.hs index 840b75d..598f8ed 100644 --- a/src/Lox/Interpreter.hs +++ b/src/Lox/Interpreter.hs @@ -1,30 +1,48 @@ module Lox.Interpreter ( + runStatements, eval ) where import Lox.Expr import Lox.Scanner import Control.Monad.State +import Control.Monad -data InterpreterState = InterpreterState +data InterpreterState = InterpreterState (IO ()) + +emptyInterpreter :: InterpreterState +emptyInterpreter = InterpreterState (return ()) + +runStatements :: [Stmt] -> IO () +runStatements s = io + where InterpreterState io = execState (interpret s) emptyInterpreter + +interpret :: [Stmt] -> State InterpreterState () +interpret = foldr ((>>) . execute) (return ()) + +execute :: Stmt -> State InterpreterState () +execute (Print expr) = do + value <- evalFrom expr + modify (\(InterpreterState s) -> InterpreterState (s >> print value)) +execute (Expression value) = void $ evalFrom value eval :: Expr -> IO Object -eval expr = return $ evalState (interpret expr) InterpreterState +eval expr = return $ evalState (evalFrom expr) $ InterpreterState (return ()) -interpret :: Expr -> State InterpreterState Object -interpret (Literal value) = return value -interpret (Grouping expr) = interpret expr -interpret (Unary op expr) = do - right <- interpret expr +evalFrom :: Expr -> State InterpreterState Object +evalFrom (Literal value) = return value +evalFrom (Grouping expr) = evalFrom expr +evalFrom (Unary op expr) = do + right <- evalFrom expr case (tokenType op, right) of (MINUS, NumberObject x) -> return $ NumberObject (-x) (BANG, NullObject) -> return $ BoolObject False (BANG, BoolObject x) -> return $ BoolObject (not x) (BANG, _) -> return $ BoolObject True _ -> error "Type error" -interpret (Binary leftExpr op rightExpr) = do - left <- interpret leftExpr - right <- interpret rightExpr +evalFrom (Binary leftExpr op rightExpr) = do + left <- evalFrom leftExpr + right <- evalFrom rightExpr case (tokenType op, left, right) of (PLUS, NumberObject x, NumberObject y) -> return $ NumberObject (x + y) (MINUS, NumberObject x, NumberObject y) -> return $ NumberObject (x - y) diff --git a/src/Lox/Parser.hs b/src/Lox/Parser.hs index 1966224..377d22e 100644 --- a/src/Lox/Parser.hs +++ b/src/Lox/Parser.hs @@ -10,7 +10,19 @@ import Lox.Expr data ParserState = ParserState {tokens :: [Token]} -data ParserError = MismatchedParenthesesError | ExpectedExpressionError +data ParserError = MismatchedParenthesesError + | ExpectedExpressionError + | ExpectedSemicolonError + deriving Show + +-- program → statement* EOF ; +-- +-- statement → exprStmt +-- | printStmt ; +-- +-- exprStmt → expression ";" ; +-- printStmt → "print" expression ";" ; + -- expression → equality ; -- equality → comparison ( ( "!=" | "==" ) comparison )* ; @@ -22,8 +34,47 @@ data ParserError = MismatchedParenthesesError | ExpectedExpressionError -- primary → NUMBER | STRING | "true" | "false" | "nil" -- | "(" expression ")" ; -parse :: [Token] -> Either ParserError Expr -parse tokens = evalState expression (ParserState {tokens=tokens}) +parse :: [Token] -> Either ParserError [Stmt] +parse tokens = evalState program (ParserState {tokens=tokens}) + +program :: State ParserState (Either ParserError [Stmt]) +program = do + atEnd <- isAtEnd + if atEnd then return $ Right [] else do + headMaybe <- statement + case headMaybe of + Left err -> return $ Left err + Right head -> do + tailMaybe <- program + case tailMaybe of + Left err -> return $ Left err + Right tail -> return $ Right $ head : tail + +statement :: State ParserState (Either ParserError Stmt) +statement = do + printMaybe <- matchToken [PRINT] + case printMaybe of + Just _ -> printStatement + _ -> expressionStatement + +printStatement :: State ParserState (Either ParserError Stmt) +printStatement = do + valueMaybe <- expression + semicolonMaybe <- consume SEMICOLON ExpectedSemicolonError + case (valueMaybe, semicolonMaybe) of + (Left err, _) -> return $ Left err + (_, Left err) -> return $ Left err + (Right value, Right _) -> return $ Right $ Print value + +expressionStatement :: State ParserState (Either ParserError Stmt) +expressionStatement = do + valueMaybe <- expression + semicolonMaybe <- consume SEMICOLON ExpectedSemicolonError + case (valueMaybe, semicolonMaybe) of + (Left err, _) -> return $ Left err + (_, Left err) -> return $ Left err + (Right value, Right _) -> return $ Right $ Expression value + expression :: State ParserState (Either ParserError Expr) expression = equality diff --git a/src/Lox/Scanner.hs b/src/Lox/Scanner.hs index 2570af4..44ace2f 100644 --- a/src/Lox/Scanner.hs +++ b/src/Lox/Scanner.hs @@ -31,7 +31,7 @@ data Object = NullObject instance Show Object where show NullObject = "Nil" - show (StringObject s) = show s + show (StringObject s) = s show (NumberObject x) = show x show (BoolObject False) = "false" show (BoolObject True) = "true" @@ -45,7 +45,7 @@ data Token = Token { data ScannerState = ScannerState {source :: String, current :: String, lineNumber :: Int} -data ScannerError = UnexpectedCharacterError +data ScannerError = UnexpectedCharacterError deriving Show emptyScannerState :: String -> ScannerState emptyScannerState source = @@ -96,7 +96,7 @@ scanToken = do ' ' -> return nothing '\r' -> return nothing '\t' -> return nothing - '\n' -> return nothing + '\n' -> modify (\s@(ScannerState {lineNumber=n}) -> s {lineNumber=n+1}) >> return nothing c -> if isDigit c then ok <$> scanNumber else if isAlpha c then ok <$> scanIdentifier else return $ Left UnexpectedCharacterError scanString :: State ScannerState Token From cf9ae469ba7a8bcc42b3d7db007d02b80ec3259f Mon Sep 17 00:00:00 2001 From: vvsob Date: Tue, 30 Dec 2025 02:56:06 +0300 Subject: [PATCH 04/10] variables --- crafting-interpreters-hs.cabal | 4 +- src/Lox/Environment.hs | 25 +++++++++++ src/Lox/Expr.hs | 15 ++++--- src/Lox/Interpreter.hs | 37 ++++++++++++----- src/Lox/Parser.hs | 76 +++++++++++++++++++++++++++------- 5 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 src/Lox/Environment.hs diff --git a/crafting-interpreters-hs.cabal b/crafting-interpreters-hs.cabal index 260e578..9c148c7 100644 --- a/crafting-interpreters-hs.cabal +++ b/crafting-interpreters-hs.cabal @@ -54,8 +54,8 @@ common warnings ghc-options: -Wall library - exposed-modules: Lox.Scanner, Lox.Expr, Lox.Parser, Lox.Interpreter - build-depends: base ^>=4.18.3.0, mtl, extra + exposed-modules: Lox.Scanner, Lox.Expr, Lox.Parser, Lox.Interpreter, Lox.Environment + build-depends: base ^>=4.18.3.0, mtl, extra, containers hs-source-dirs: src default-language: Haskell2010 diff --git a/src/Lox/Environment.hs b/src/Lox/Environment.hs new file mode 100644 index 0000000..cd1c35e --- /dev/null +++ b/src/Lox/Environment.hs @@ -0,0 +1,25 @@ +module Lox.Environment ( + Environment, + emptyEnvironment, + define, + get, + assign +) where + +import Data.Map +import Lox.Scanner + +data Environment = Environment {variables :: Map String Object} + +emptyEnvironment :: Environment +emptyEnvironment = Environment {variables=empty} + +define :: String -> Object -> Environment -> Environment +define key value env@Environment {variables=variables} = env {variables=insert key value variables} + +get :: String -> Environment -> Maybe Object +get key Environment {variables=variables} = variables !? key + +assign :: String -> Object -> Environment -> (Bool, Environment) +assign key value env@Environment {variables=variables} = + if member key variables then (True, env {variables=insert key value variables}) else (False, env) diff --git a/src/Lox/Expr.hs b/src/Lox/Expr.hs index e181e66..4e89fe1 100644 --- a/src/Lox/Expr.hs +++ b/src/Lox/Expr.hs @@ -6,13 +6,16 @@ module Lox.Expr ( import Lox.Scanner data Stmt = - Expression Expr | - Print Expr + ExpressionStmt Expr | + PrintStmt Expr | + VariableStmt Token Expr deriving Show data Expr = - Literal Object | - Unary Token Expr | - Binary Expr Token Expr | - Grouping Expr + LiteralExpr Object | + UnaryExpr Token Expr | + BinaryExpr Expr Token Expr | + GroupingExpr Expr | + VariableExpr Token | + AssignmentExpr Token Expr deriving Show diff --git a/src/Lox/Interpreter.hs b/src/Lox/Interpreter.hs index 598f8ed..25ce85a 100644 --- a/src/Lox/Interpreter.hs +++ b/src/Lox/Interpreter.hs @@ -5,34 +5,49 @@ module Lox.Interpreter ( import Lox.Expr import Lox.Scanner +import Lox.Environment import Control.Monad.State import Control.Monad -data InterpreterState = InterpreterState (IO ()) +data InterpreterState = InterpreterState {io :: IO (), environment :: Environment} emptyInterpreter :: InterpreterState -emptyInterpreter = InterpreterState (return ()) +emptyInterpreter = InterpreterState {io=return (), environment=emptyEnvironment} runStatements :: [Stmt] -> IO () runStatements s = io - where InterpreterState io = execState (interpret s) emptyInterpreter + where InterpreterState {io=io} = execState (interpret s) emptyInterpreter interpret :: [Stmt] -> State InterpreterState () interpret = foldr ((>>) . execute) (return ()) execute :: Stmt -> State InterpreterState () -execute (Print expr) = do +execute (PrintStmt expr) = do value <- evalFrom expr - modify (\(InterpreterState s) -> InterpreterState (s >> print value)) -execute (Expression value) = void $ evalFrom value + modify (\s@(InterpreterState {io=io}) -> s {io=io >> print value}) +execute (ExpressionStmt value) = void $ evalFrom value +execute (VariableStmt name expr) = do + value <- evalFrom expr + modify (\s@(InterpreterState {environment=env}) -> s {environment=define (tokenLexeme name) value env}) eval :: Expr -> IO Object -eval expr = return $ evalState (evalFrom expr) $ InterpreterState (return ()) +eval expr = return $ evalState (evalFrom expr) emptyInterpreter evalFrom :: Expr -> State InterpreterState Object -evalFrom (Literal value) = return value -evalFrom (Grouping expr) = evalFrom expr -evalFrom (Unary op expr) = do +evalFrom (LiteralExpr value) = return value +evalFrom (VariableExpr name) = do + maybeObject <- gets (\(InterpreterState {environment=env}) -> Lox.Environment.get (tokenLexeme name) env) + case maybeObject of + Nothing -> error "Undefined variable" + Just object -> return object +evalFrom (AssignmentExpr name expr) = do + value <- evalFrom expr + success <- state $ f value + if success then return value else error "Undefined variable" + where f value s@InterpreterState {environment=env} = let (success, newEnv) = assign (tokenLexeme name) value env in (success, s {environment=newEnv}) + +evalFrom (GroupingExpr expr) = evalFrom expr +evalFrom (UnaryExpr op expr) = do right <- evalFrom expr case (tokenType op, right) of (MINUS, NumberObject x) -> return $ NumberObject (-x) @@ -40,7 +55,7 @@ evalFrom (Unary op expr) = do (BANG, BoolObject x) -> return $ BoolObject (not x) (BANG, _) -> return $ BoolObject True _ -> error "Type error" -evalFrom (Binary leftExpr op rightExpr) = do +evalFrom (BinaryExpr leftExpr op rightExpr) = do left <- evalFrom leftExpr right <- evalFrom rightExpr case (tokenType op, left, right) of diff --git a/src/Lox/Parser.hs b/src/Lox/Parser.hs index 377d22e..4f91f8a 100644 --- a/src/Lox/Parser.hs +++ b/src/Lox/Parser.hs @@ -5,6 +5,8 @@ module Lox.Parser ( import Control.Monad import Control.Monad.State +import Data.Either +import Data.Maybe import Lox.Scanner import Lox.Expr @@ -13,18 +15,25 @@ data ParserState = ParserState {tokens :: [Token]} data ParserError = MismatchedParenthesesError | ExpectedExpressionError | ExpectedSemicolonError + | ExpectedVariableName + | InvalidAssignmentTarget deriving Show --- program → statement* EOF ; +-- program → declaration* EOF ; +-- +-- declaration → varDecl +-- | statement ; -- -- statement → exprStmt -- | printStmt ; -- -- exprStmt → expression ";" ; -- printStmt → "print" expression ";" ; +-- varDecl → "var" IDENTIFIER ( "=" expression )? ";" ; - --- expression → equality ; +-- expression → assignment ; +-- assignment → IDENTIFIER "=" assignment +-- | equality ; -- equality → comparison ( ( "!=" | "==" ) comparison )* ; -- comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ; -- term → factor ( ( "-" | "+" ) factor )* ; @@ -32,7 +41,7 @@ data ParserError = MismatchedParenthesesError -- unary → ( "!" | "-" ) unary -- | primary ; -- primary → NUMBER | STRING | "true" | "false" | "nil" --- | "(" expression ")" ; +-- | "(" expression ")" | IDENTIFIER; parse :: [Token] -> Either ParserError [Stmt] parse tokens = evalState program (ParserState {tokens=tokens}) @@ -41,7 +50,7 @@ program :: State ParserState (Either ParserError [Stmt]) program = do atEnd <- isAtEnd if atEnd then return $ Right [] else do - headMaybe <- statement + headMaybe <- declaration case headMaybe of Left err -> return $ Left err Right head -> do @@ -49,6 +58,27 @@ program = do case tailMaybe of Left err -> return $ Left err Right tail -> return $ Right $ head : tail +declaration :: State ParserState (Either ParserError Stmt) +declaration = do + varMaybe <- matchToken [VAR] + case varMaybe of + Just _ -> varDeclaration + _ -> statement + +varDeclaration :: State ParserState (Either ParserError Stmt) +varDeclaration = do + maybeName <- consume IDENTIFIER ExpectedVariableName + case maybeName of + Left err -> return $ Left err + Right name -> do + hasInit <- isJust <$> matchToken [EQUAL] + initMaybe <- if hasInit then expression else return $ Right $ LiteralExpr NullObject + semicolonMaybe <- consume SEMICOLON ExpectedSemicolonError + case (initMaybe, semicolonMaybe) of + (Left err, _) -> return $ Left err + (_, Left err) -> return $ Left err + (Right init, Right _) -> return $ Right $ VariableStmt name init + statement :: State ParserState (Either ParserError Stmt) statement = do @@ -64,7 +94,7 @@ printStatement = do case (valueMaybe, semicolonMaybe) of (Left err, _) -> return $ Left err (_, Left err) -> return $ Left err - (Right value, Right _) -> return $ Right $ Print value + (Right value, Right _) -> return $ Right $ PrintStmt value expressionStatement :: State ParserState (Either ParserError Stmt) expressionStatement = do @@ -73,11 +103,24 @@ expressionStatement = do case (valueMaybe, semicolonMaybe) of (Left err, _) -> return $ Left err (_, Left err) -> return $ Left err - (Right value, Right _) -> return $ Right $ Expression value + (Right value, Right _) -> return $ Right $ ExpressionStmt value expression :: State ParserState (Either ParserError Expr) -expression = equality +expression = assignment + +assignment :: State ParserState (Either ParserError Expr) +assignment = do + maybeExpr <- equality + matchedEqual <- isJust <$> matchToken [EQUAL] + if matchedEqual then do + maybeValue <- assignment + case (maybeExpr, maybeValue) of + (Left err, _) -> return $ Left err + (_, Left err) -> return $ Left err + (Right (VariableExpr name), Right value) -> return $ Right $ AssignmentExpr name value + _ -> return $ Left InvalidAssignmentTarget + else return maybeExpr equality :: State ParserState (Either ParserError Expr) equality = do @@ -116,24 +159,25 @@ unary = do exprMaybe <- unary case exprMaybe of Left err -> return $ Left err - Right expr -> return $ Right $ Unary op expr + Right expr -> return $ Right $ UnaryExpr op expr primary :: State ParserState (Either ParserError Expr) primary = do token <- advance case tokenType token of - FALSE -> return $ Right $ Literal $ BoolObject False - TRUE -> return $ Right $ Literal $ BoolObject True - NIL -> return $ Right $ Literal NullObject - NUMBER -> return $ Right $ Literal $ tokenObject token - STRING -> return $ Right $ Literal $ tokenObject token + FALSE -> return $ Right $ LiteralExpr $ BoolObject False + TRUE -> return $ Right $ LiteralExpr $ BoolObject True + NIL -> return $ Right $ LiteralExpr NullObject + NUMBER -> return $ Right $ LiteralExpr $ tokenObject token + STRING -> return $ Right $ LiteralExpr $ tokenObject token LEFT_PAREN -> do exprMaybe <- expression case exprMaybe of Left err -> return $ Left err Right expr -> do consume RIGHT_PAREN MismatchedParenthesesError - return $ Right $ Grouping expr + return $ Right $ GroupingExpr expr + IDENTIFIER -> return $ Right $ VariableExpr token _ -> return $ Left ExpectedExpressionError matchTail :: [TokenType] -> State ParserState (Either ParserError Expr) -> State ParserState (Either ParserError (Maybe (Token, Expr))) @@ -151,7 +195,7 @@ matchTail tokenTypes f = do mergeExpressionMaybe :: Expr -> Maybe (Token, Expr) -> Expr mergeExpressionMaybe expr Nothing = expr -mergeExpressionMaybe left (Just (op, right)) = Binary left op right +mergeExpressionMaybe left (Just (op, right)) = BinaryExpr left op right matchToken :: [TokenType] -> State ParserState (Maybe Token) matchToken [] = return Nothing From 44f39a7cf26c64abefce10e062594c80046521e0 Mon Sep 17 00:00:00 2001 From: vvsob Date: Tue, 30 Dec 2025 03:45:21 +0300 Subject: [PATCH 05/10] blocks --- src/Lox/Environment.hs | 21 +++++++++++++++------ src/Lox/Expr.hs | 1 + src/Lox/Interpreter.hs | 8 ++++++++ src/Lox/Parser.hs | 34 ++++++++++++++++++++++++++-------- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/Lox/Environment.hs b/src/Lox/Environment.hs index cd1c35e..60ccd45 100644 --- a/src/Lox/Environment.hs +++ b/src/Lox/Environment.hs @@ -1,5 +1,5 @@ module Lox.Environment ( - Environment, + Environment (..), emptyEnvironment, define, get, @@ -9,17 +9,26 @@ module Lox.Environment ( import Data.Map import Lox.Scanner -data Environment = Environment {variables :: Map String Object} +data Environment = Environment {enclosing :: Maybe Environment, variables :: Map String Object} emptyEnvironment :: Environment -emptyEnvironment = Environment {variables=empty} +emptyEnvironment = Environment {enclosing=Nothing, variables=empty} define :: String -> Object -> Environment -> Environment define key value env@Environment {variables=variables} = env {variables=insert key value variables} get :: String -> Environment -> Maybe Object -get key Environment {variables=variables} = variables !? key +get key Environment {enclosing=enclosing, variables=variables} = + case variables !? key of + Just val -> Just val + Nothing -> case enclosing of + Just e -> get key e + Nothing -> Nothing assign :: String -> Object -> Environment -> (Bool, Environment) -assign key value env@Environment {variables=variables} = - if member key variables then (True, env {variables=insert key value variables}) else (False, env) +assign key value env@Environment {enclosing=enclosing, variables=variables} = + if member key variables + then (True, env {variables=insert key value variables}) + else case enclosing of + Just e -> let (success, newEnclosing) = assign key value e in (success, env {enclosing = Just newEnclosing}) + Nothing -> (False, env) diff --git a/src/Lox/Expr.hs b/src/Lox/Expr.hs index 4e89fe1..b13148d 100644 --- a/src/Lox/Expr.hs +++ b/src/Lox/Expr.hs @@ -6,6 +6,7 @@ module Lox.Expr ( import Lox.Scanner data Stmt = + BlockStmt [Stmt] | ExpressionStmt Expr | PrintStmt Expr | VariableStmt Token Expr diff --git a/src/Lox/Interpreter.hs b/src/Lox/Interpreter.hs index 25ce85a..3e858ba 100644 --- a/src/Lox/Interpreter.hs +++ b/src/Lox/Interpreter.hs @@ -22,6 +22,7 @@ interpret :: [Stmt] -> State InterpreterState () interpret = foldr ((>>) . execute) (return ()) execute :: Stmt -> State InterpreterState () +execute (BlockStmt statements) = executeBlock statements execute (PrintStmt expr) = do value <- evalFrom expr modify (\s@(InterpreterState {io=io}) -> s {io=io >> print value}) @@ -30,6 +31,13 @@ execute (VariableStmt name expr) = do value <- evalFrom expr modify (\s@(InterpreterState {environment=env}) -> s {environment=define (tokenLexeme name) value env}) +executeBlock :: [Stmt] -> State InterpreterState () +executeBlock statements = do + oldEnv <- gets environment + modify (\s@InterpreterState {environment=_} -> s {environment=emptyEnvironment {enclosing=Just oldEnv}}) + interpret statements + modify (\s@InterpreterState {environment=_} -> s {environment=oldEnv}) + eval :: Expr -> IO Object eval expr = return $ evalState (evalFrom expr) emptyInterpreter diff --git a/src/Lox/Parser.hs b/src/Lox/Parser.hs index 4f91f8a..8d2f185 100644 --- a/src/Lox/Parser.hs +++ b/src/Lox/Parser.hs @@ -15,8 +15,9 @@ data ParserState = ParserState {tokens :: [Token]} data ParserError = MismatchedParenthesesError | ExpectedExpressionError | ExpectedSemicolonError - | ExpectedVariableName - | InvalidAssignmentTarget + | ExpectedVariableNameError + | ExpectedBraceAfterBlockError + | InvalidAssignmentTargetError deriving Show -- program → declaration* EOF ; @@ -25,11 +26,13 @@ data ParserError = MismatchedParenthesesError -- | statement ; -- -- statement → exprStmt --- | printStmt ; +-- | printStmt +-- | block ; -- -- exprStmt → expression ";" ; -- printStmt → "print" expression ";" ; -- varDecl → "var" IDENTIFIER ( "=" expression )? ";" ; +-- block → "{" declaration* "}" ; -- expression → assignment ; -- assignment → IDENTIFIER "=" assignment @@ -67,7 +70,7 @@ declaration = do varDeclaration :: State ParserState (Either ParserError Stmt) varDeclaration = do - maybeName <- consume IDENTIFIER ExpectedVariableName + maybeName <- consume IDENTIFIER ExpectedVariableNameError case maybeName of Left err -> return $ Left err Right name -> do @@ -82,11 +85,26 @@ varDeclaration = do statement :: State ParserState (Either ParserError Stmt) statement = do - printMaybe <- matchToken [PRINT] - case printMaybe of - Just _ -> printStatement + tokenMaybe <- matchToken [PRINT, LEFT_BRACE] + case tokenMaybe of + Just (Token {tokenType=PRINT}) -> printStatement + Just (Token {tokenType=LEFT_BRACE}) -> do + result <- fmap BlockStmt <$> block + braceMaybe <- consume RIGHT_BRACE ExpectedBraceAfterBlockError + return $ braceMaybe >> result _ -> expressionStatement +block :: State ParserState (Either ParserError [Stmt]) +block = do + isRightBrace <- check RIGHT_BRACE + if isRightBrace then return $ Right [] else do + declMaybe <- declaration + tailMaybe <- block + case (declMaybe, tailMaybe) of + (Left err, _) -> return $ Left err + (_, Left err) -> return $ Left err + (Right decl, Right tail) -> return $ Right $ decl : tail + printStatement :: State ParserState (Either ParserError Stmt) printStatement = do valueMaybe <- expression @@ -119,7 +137,7 @@ assignment = do (Left err, _) -> return $ Left err (_, Left err) -> return $ Left err (Right (VariableExpr name), Right value) -> return $ Right $ AssignmentExpr name value - _ -> return $ Left InvalidAssignmentTarget + _ -> return $ Left InvalidAssignmentTargetError else return maybeExpr equality :: State ParserState (Either ParserError Expr) From 7242b1b9d156287fee3236b90e555acca307499d Mon Sep 17 00:00:00 2001 From: vvsob Date: Tue, 30 Dec 2025 04:01:20 +0300 Subject: [PATCH 06/10] file support --- app/Main.hs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/Main.hs b/app/Main.hs index a4dff38..64d2c8e 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -2,6 +2,7 @@ import Lox.Scanner import Lox.Parser import Lox.Interpreter import System.IO +import System.Environment run :: String -> IO () run source = do @@ -16,5 +17,13 @@ run source = do Left ExpectedSemicolonError -> putStrLn "Expected semicolon" Right statements -> runStatements statements +repl :: IO () +repl = putStr ">> " >> hFlush stdout >> getLine >>= run + main :: IO () -main = putStr ">> " >> hFlush stdout >> getLine >>= run +main = getArgs >>= fs + +fs :: [String] -> IO () +fs [] = repl +fs [s] = readFile s >>= run +fs _ = putStrLn "Usage: lox [file]" From 01699716af5bcc8b5df18394b9386e7425fb1c52 Mon Sep 17 00:00:00 2001 From: vvsob Date: Tue, 30 Dec 2025 05:43:01 +0300 Subject: [PATCH 07/10] if statement --- app/Main.hs | 4 +- src/Lox/Expr.hs | 2 + src/Lox/Interpreter.hs | 21 ++++++-- src/Lox/Parser.hs | 116 +++++++++++++++++++++++++++-------------- 4 files changed, 96 insertions(+), 47 deletions(-) diff --git a/app/Main.hs b/app/Main.hs index 64d2c8e..007e933 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -12,9 +12,7 @@ run source = do Right tokens -> do let stmtMaybe = parse tokens case stmtMaybe of - Left ExpectedExpressionError -> putStrLn "Expected expression" - Left MismatchedParenthesesError -> putStrLn "Mismatched parentheses" - Left ExpectedSemicolonError -> putStrLn "Expected semicolon" + Left (SyntaxError s) -> putStrLn s Right statements -> runStatements statements repl :: IO () diff --git a/src/Lox/Expr.hs b/src/Lox/Expr.hs index b13148d..0c4b94a 100644 --- a/src/Lox/Expr.hs +++ b/src/Lox/Expr.hs @@ -8,12 +8,14 @@ import Lox.Scanner data Stmt = BlockStmt [Stmt] | ExpressionStmt Expr | + IfStmt Expr Stmt (Maybe Stmt) | PrintStmt Expr | VariableStmt Token Expr deriving Show data Expr = LiteralExpr Object | + LogicalExpr Expr Token Expr | UnaryExpr Token Expr | BinaryExpr Expr Token Expr | GroupingExpr Expr | diff --git a/src/Lox/Interpreter.hs b/src/Lox/Interpreter.hs index 3e858ba..11da404 100644 --- a/src/Lox/Interpreter.hs +++ b/src/Lox/Interpreter.hs @@ -23,10 +23,13 @@ interpret = foldr ((>>) . execute) (return ()) execute :: Stmt -> State InterpreterState () execute (BlockStmt statements) = executeBlock statements +execute (ExpressionStmt value) = void $ evalFrom value +execute (IfStmt condition thenBranch elseBranchMaybe) = do + condValue <- isTruthy <$> evalFrom condition + if condValue then execute thenBranch else forM_ elseBranchMaybe execute execute (PrintStmt expr) = do value <- evalFrom expr modify (\s@(InterpreterState {io=io}) -> s {io=io >> print value}) -execute (ExpressionStmt value) = void $ evalFrom value execute (VariableStmt name expr) = do value <- evalFrom expr modify (\s@(InterpreterState {environment=env}) -> s {environment=define (tokenLexeme name) value env}) @@ -59,10 +62,16 @@ evalFrom (UnaryExpr op expr) = do right <- evalFrom expr case (tokenType op, right) of (MINUS, NumberObject x) -> return $ NumberObject (-x) - (BANG, NullObject) -> return $ BoolObject False - (BANG, BoolObject x) -> return $ BoolObject (not x) - (BANG, _) -> return $ BoolObject True + (BANG, object) -> return $ BoolObject $ not $ isTruthy object _ -> error "Type error" +evalFrom (LogicalExpr leftExpr op rightExpr) = do + isLeftTruthy <- isTruthy <$> evalFrom leftExpr + case (tokenType op, isLeftTruthy) of + (OR, True) -> return $ BoolObject True + (OR, False) -> evalFrom rightExpr + (AND, True) -> evalFrom rightExpr + (AND, False) -> return $ BoolObject False + _ -> error "Unreachable" evalFrom (BinaryExpr leftExpr op rightExpr) = do left <- evalFrom leftExpr right <- evalFrom rightExpr @@ -83,3 +92,7 @@ evalFrom (BinaryExpr leftExpr op rightExpr) = do _ -> error "Type error" +isTruthy :: Object -> Bool +isTruthy NullObject = False +isTruthy (BoolObject False) = False +isTruthy _ = True diff --git a/src/Lox/Parser.hs b/src/Lox/Parser.hs index 8d2f185..37aaf23 100644 --- a/src/Lox/Parser.hs +++ b/src/Lox/Parser.hs @@ -1,5 +1,5 @@ module Lox.Parser ( - ParserError (..), + SyntaxError (..), parse ) where @@ -9,16 +9,11 @@ import Data.Either import Data.Maybe import Lox.Scanner import Lox.Expr +import Control.Monad.Extra (ifM) data ParserState = ParserState {tokens :: [Token]} -data ParserError = MismatchedParenthesesError - | ExpectedExpressionError - | ExpectedSemicolonError - | ExpectedVariableNameError - | ExpectedBraceAfterBlockError - | InvalidAssignmentTargetError - deriving Show +data SyntaxError = SyntaxError String deriving Show -- program → declaration* EOF ; -- @@ -26,17 +21,22 @@ data ParserError = MismatchedParenthesesError -- | statement ; -- -- statement → exprStmt +-- | ifStmt -- | printStmt -- | block ; -- -- exprStmt → expression ";" ; +-- ifStmt → "if" "(" expression ")" statement +-- ( "else" statement )? ; -- printStmt → "print" expression ";" ; -- varDecl → "var" IDENTIFIER ( "=" expression )? ";" ; -- block → "{" declaration* "}" ; - +-- -- expression → assignment ; -- assignment → IDENTIFIER "=" assignment --- | equality ; +-- | logic_or; +-- logic_or → logic_and ( "or" logic_and )* ; +-- logic_and → equality ( "and" equality )* ; -- equality → comparison ( ( "!=" | "==" ) comparison )* ; -- comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ; -- term → factor ( ( "-" | "+" ) factor )* ; @@ -46,10 +46,10 @@ data ParserError = MismatchedParenthesesError -- primary → NUMBER | STRING | "true" | "false" | "nil" -- | "(" expression ")" | IDENTIFIER; -parse :: [Token] -> Either ParserError [Stmt] +parse :: [Token] -> Either SyntaxError [Stmt] parse tokens = evalState program (ParserState {tokens=tokens}) -program :: State ParserState (Either ParserError [Stmt]) +program :: State ParserState (Either SyntaxError [Stmt]) program = do atEnd <- isAtEnd if atEnd then return $ Right [] else do @@ -61,40 +61,41 @@ program = do case tailMaybe of Left err -> return $ Left err Right tail -> return $ Right $ head : tail -declaration :: State ParserState (Either ParserError Stmt) +declaration :: State ParserState (Either SyntaxError Stmt) declaration = do varMaybe <- matchToken [VAR] case varMaybe of Just _ -> varDeclaration _ -> statement -varDeclaration :: State ParserState (Either ParserError Stmt) +varDeclaration :: State ParserState (Either SyntaxError Stmt) varDeclaration = do - maybeName <- consume IDENTIFIER ExpectedVariableNameError + maybeName <- consume IDENTIFIER $ SyntaxError "Expected variable name" case maybeName of Left err -> return $ Left err Right name -> do hasInit <- isJust <$> matchToken [EQUAL] initMaybe <- if hasInit then expression else return $ Right $ LiteralExpr NullObject - semicolonMaybe <- consume SEMICOLON ExpectedSemicolonError + semicolonMaybe <- consume SEMICOLON $ SyntaxError "Expected semicolon" case (initMaybe, semicolonMaybe) of (Left err, _) -> return $ Left err (_, Left err) -> return $ Left err (Right init, Right _) -> return $ Right $ VariableStmt name init -statement :: State ParserState (Either ParserError Stmt) +statement :: State ParserState (Either SyntaxError Stmt) statement = do - tokenMaybe <- matchToken [PRINT, LEFT_BRACE] + tokenMaybe <- matchToken [IF, PRINT, LEFT_BRACE] case tokenMaybe of + Just (Token {tokenType=IF}) -> ifStatement Just (Token {tokenType=PRINT}) -> printStatement Just (Token {tokenType=LEFT_BRACE}) -> do result <- fmap BlockStmt <$> block - braceMaybe <- consume RIGHT_BRACE ExpectedBraceAfterBlockError + braceMaybe <- consume RIGHT_BRACE $ SyntaxError "Expected '}' after block" return $ braceMaybe >> result _ -> expressionStatement -block :: State ParserState (Either ParserError [Stmt]) +block :: State ParserState (Either SyntaxError [Stmt]) block = do isRightBrace <- check RIGHT_BRACE if isRightBrace then return $ Right [] else do @@ -105,31 +106,41 @@ block = do (_, Left err) -> return $ Left err (Right decl, Right tail) -> return $ Right $ decl : tail -printStatement :: State ParserState (Either ParserError Stmt) +ifStatement :: State ParserState (Either SyntaxError Stmt) +ifStatement = do + leftParenMaybe <- consume LEFT_PAREN $ SyntaxError "Expected '(' after 'if'" + conditionMaybe <- expression + rightParenMaybe <- consume RIGHT_PAREN $ SyntaxError "Expected ')' after if condition" + thenBranchMaybe <- statement + isElse <- isJust <$> matchToken [ELSE] + elseBranchMaybe <- if isElse then fmap Just <$> statement else return $ Right Nothing + return $ IfStmt <$> (leftParenMaybe >> conditionMaybe <* rightParenMaybe) <*> thenBranchMaybe <*> elseBranchMaybe + +printStatement :: State ParserState (Either SyntaxError Stmt) printStatement = do valueMaybe <- expression - semicolonMaybe <- consume SEMICOLON ExpectedSemicolonError + semicolonMaybe <- consume SEMICOLON $ SyntaxError "Expected ';'" case (valueMaybe, semicolonMaybe) of (Left err, _) -> return $ Left err (_, Left err) -> return $ Left err (Right value, Right _) -> return $ Right $ PrintStmt value -expressionStatement :: State ParserState (Either ParserError Stmt) +expressionStatement :: State ParserState (Either SyntaxError Stmt) expressionStatement = do valueMaybe <- expression - semicolonMaybe <- consume SEMICOLON ExpectedSemicolonError + semicolonMaybe <- consume SEMICOLON $ SyntaxError "Expected ';'" case (valueMaybe, semicolonMaybe) of (Left err, _) -> return $ Left err (_, Left err) -> return $ Left err (Right value, Right _) -> return $ Right $ ExpressionStmt value -expression :: State ParserState (Either ParserError Expr) +expression :: State ParserState (Either SyntaxError Expr) expression = assignment -assignment :: State ParserState (Either ParserError Expr) +assignment :: State ParserState (Either SyntaxError Expr) assignment = do - maybeExpr <- equality + maybeExpr <- logicalOr matchedEqual <- isJust <$> matchToken [EQUAL] if matchedEqual then do maybeValue <- assignment @@ -137,38 +148,52 @@ assignment = do (Left err, _) -> return $ Left err (_, Left err) -> return $ Left err (Right (VariableExpr name), Right value) -> return $ Right $ AssignmentExpr name value - _ -> return $ Left InvalidAssignmentTargetError + _ -> return $ Left $ SyntaxError "Invalid assignment target" else return maybeExpr -equality :: State ParserState (Either ParserError Expr) +logicalOr :: State ParserState (Either SyntaxError Expr) +logicalOr = do + exprMaybe <- logicalAnd + case exprMaybe of + Left err -> return $ Left err + Right expr -> fmap (mergeExpressionLogicalMaybe expr) <$> matchTailLogical [OR] logicalAnd + +logicalAnd :: State ParserState (Either SyntaxError Expr) +logicalAnd = do + exprMaybe <- equality + case exprMaybe of + Left err -> return $ Left err + Right expr -> fmap (mergeExpressionLogicalMaybe expr) <$> matchTailLogical [AND] logicalAnd + +equality :: State ParserState (Either SyntaxError Expr) equality = do exprMaybe <- comparison case exprMaybe of Left err -> return $ Left err Right expr -> fmap (mergeExpressionMaybe expr) <$> matchTail [BANG_EQUAL, EQUAL_EQUAL] comparison -comparison :: State ParserState (Either ParserError Expr) +comparison :: State ParserState (Either SyntaxError Expr) comparison = do exprMaybe <- term case exprMaybe of Left err -> return $ Left err Right expr -> fmap (mergeExpressionMaybe expr) <$> matchTail [GREATER, GREATER_EQUAL, LESS, LESS_EQUAL] term -term :: State ParserState (Either ParserError Expr) +term :: State ParserState (Either SyntaxError Expr) term = do exprMaybe <- factor case exprMaybe of Left err -> return $ Left err Right expr -> fmap (mergeExpressionMaybe expr) <$> matchTail [MINUS, PLUS] factor -factor :: State ParserState (Either ParserError Expr) +factor :: State ParserState (Either SyntaxError Expr) factor = do exprMaybe <- unary case exprMaybe of Left err -> return $ Left err Right expr -> fmap (mergeExpressionMaybe expr) <$> matchTail [SLASH, STAR] unary -unary :: State ParserState (Either ParserError Expr) +unary :: State ParserState (Either SyntaxError Expr) unary = do maybeOperator <- matchToken [BANG, MINUS] case maybeOperator of @@ -179,7 +204,7 @@ unary = do Left err -> return $ Left err Right expr -> return $ Right $ UnaryExpr op expr -primary :: State ParserState (Either ParserError Expr) +primary :: State ParserState (Either SyntaxError Expr) primary = do token <- advance case tokenType token of @@ -193,13 +218,19 @@ primary = do case exprMaybe of Left err -> return $ Left err Right expr -> do - consume RIGHT_PAREN MismatchedParenthesesError + consume RIGHT_PAREN $ SyntaxError "Mismatched parentheses" return $ Right $ GroupingExpr expr IDENTIFIER -> return $ Right $ VariableExpr token - _ -> return $ Left ExpectedExpressionError + _ -> return $ Left $ SyntaxError "Expected expression" -matchTail :: [TokenType] -> State ParserState (Either ParserError Expr) -> State ParserState (Either ParserError (Maybe (Token, Expr))) -matchTail tokenTypes f = do +matchTail :: [TokenType] -> State ParserState (Either SyntaxError Expr) -> State ParserState (Either SyntaxError (Maybe (Token, Expr))) +matchTail tokenTypes = matchTailWith tokenTypes mergeExpressionMaybe + +matchTailLogical :: [TokenType] -> State ParserState (Either SyntaxError Expr) -> State ParserState (Either SyntaxError (Maybe (Token, Expr))) +matchTailLogical tokenTypes = matchTailWith tokenTypes mergeExpressionLogicalMaybe + +matchTailWith :: [TokenType] -> (Expr -> Maybe (Token, Expr) -> Expr) -> State ParserState (Either SyntaxError Expr) -> State ParserState (Either SyntaxError (Maybe (Token, Expr))) +matchTailWith tokenTypes m f = do maybeOperator <- matchToken tokenTypes case maybeOperator of Nothing -> return $ Right Nothing @@ -209,12 +240,17 @@ matchTail tokenTypes f = do case (exprMaybe, restMaybe) of (Left err, _) -> return $ Left err (_, Left err) -> return $ Left err - (Right expr, Right rest) -> return $ Right $ Just (op, mergeExpressionMaybe expr rest) + (Right expr, Right rest) -> return $ Right $ Just (op, m expr rest) + mergeExpressionMaybe :: Expr -> Maybe (Token, Expr) -> Expr mergeExpressionMaybe expr Nothing = expr mergeExpressionMaybe left (Just (op, right)) = BinaryExpr left op right +mergeExpressionLogicalMaybe :: Expr -> Maybe (Token, Expr) -> Expr +mergeExpressionLogicalMaybe expr Nothing = expr +mergeExpressionLogicalMaybe left (Just (op, right)) = LogicalExpr left op right + matchToken :: [TokenType] -> State ParserState (Maybe Token) matchToken [] = return Nothing matchToken (t:ts) = do @@ -226,7 +262,7 @@ check t = do atEnd <- isAtEnd if atEnd then return False else (== t) . tokenType <$> peek -consume :: TokenType -> ParserError -> State ParserState (Either ParserError Token) +consume :: TokenType -> SyntaxError -> State ParserState (Either SyntaxError Token) consume t err = do isOk <- check t if isOk then Right <$> advance else return $ Left err From 6f4a835e54681a1bdfc664b3fb9057dfa78dac0d Mon Sep 17 00:00:00 2001 From: vvsob Date: Tue, 30 Dec 2025 06:43:29 +0300 Subject: [PATCH 08/10] loops --- src/Lox/Expr.hs | 3 ++- src/Lox/Interpreter.hs | 8 ++++++- src/Lox/Parser.hs | 50 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/Lox/Expr.hs b/src/Lox/Expr.hs index 0c4b94a..a5007ea 100644 --- a/src/Lox/Expr.hs +++ b/src/Lox/Expr.hs @@ -10,7 +10,8 @@ data Stmt = ExpressionStmt Expr | IfStmt Expr Stmt (Maybe Stmt) | PrintStmt Expr | - VariableStmt Token Expr + VariableStmt Token Expr | + WhileStmt Expr Stmt deriving Show data Expr = diff --git a/src/Lox/Interpreter.hs b/src/Lox/Interpreter.hs index 11da404..00fc96a 100644 --- a/src/Lox/Interpreter.hs +++ b/src/Lox/Interpreter.hs @@ -33,13 +33,19 @@ execute (PrintStmt expr) = do execute (VariableStmt name expr) = do value <- evalFrom expr modify (\s@(InterpreterState {environment=env}) -> s {environment=define (tokenLexeme name) value env}) +execute (WhileStmt condition body) = executeWhile condition body executeBlock :: [Stmt] -> State InterpreterState () executeBlock statements = do oldEnv <- gets environment modify (\s@InterpreterState {environment=_} -> s {environment=emptyEnvironment {enclosing=Just oldEnv}}) interpret statements - modify (\s@InterpreterState {environment=_} -> s {environment=oldEnv}) + modify (\s@InterpreterState {environment=Environment {enclosing=Just enclosing}} -> s {environment=enclosing}) + +executeWhile :: Expr -> Stmt -> State InterpreterState () +executeWhile condition body = do + shouldContinue <- evalFrom condition + when (isTruthy shouldContinue) $ execute body >> executeWhile condition body eval :: Expr -> IO Object eval expr = return $ evalState (evalFrom expr) emptyInterpreter diff --git a/src/Lox/Parser.hs b/src/Lox/Parser.hs index 37aaf23..ff04174 100644 --- a/src/Lox/Parser.hs +++ b/src/Lox/Parser.hs @@ -21,14 +21,20 @@ data SyntaxError = SyntaxError String deriving Show -- | statement ; -- -- statement → exprStmt +-- | forStmt -- | ifStmt -- | printStmt +-- | whileStmt -- | block ; -- -- exprStmt → expression ";" ; +-- forStmt → "for" "(" ( varDecl | exprStmt | ";" ) +-- expression? ";" +-- expression? ") statement ; -- ifStmt → "if" "(" expression ")" statement -- ( "else" statement )? ; -- printStmt → "print" expression ";" ; +-- whileStmt → "while" "(" expression ")" statement ; -- varDecl → "var" IDENTIFIER ( "=" expression )? ";" ; -- block → "{" declaration* "}" ; -- @@ -85,11 +91,13 @@ varDeclaration = do statement :: State ParserState (Either SyntaxError Stmt) statement = do - tokenMaybe <- matchToken [IF, PRINT, LEFT_BRACE] - case tokenMaybe of - Just (Token {tokenType=IF}) -> ifStatement - Just (Token {tokenType=PRINT}) -> printStatement - Just (Token {tokenType=LEFT_BRACE}) -> do + tokenTypeMaybe <- fmap tokenType <$> matchToken [FOR, IF, PRINT, WHILE, LEFT_BRACE] + case tokenTypeMaybe of + Just FOR -> forStatement + Just IF -> ifStatement + Just PRINT -> printStatement + Just WHILE -> whileStatement + Just LEFT_BRACE -> do result <- fmap BlockStmt <$> block braceMaybe <- consume RIGHT_BRACE $ SyntaxError "Expected '}' after block" return $ braceMaybe >> result @@ -106,6 +114,31 @@ block = do (_, Left err) -> return $ Left err (Right decl, Right tail) -> return $ Right $ decl : tail +forStatement :: State ParserState (Either SyntaxError Stmt) +forStatement = do + leftParen <- consume LEFT_PAREN $ SyntaxError "Expected '(' after 'for'" + tokenTypeMaybe <- fmap tokenType <$> matchToken [SEMICOLON, VAR] + initializer <- case tokenTypeMaybe of + Just SEMICOLON -> return Nothing + Just VAR -> Just <$> varDeclaration + _ -> Just <$> expressionStatement + condition <- ifM (check SEMICOLON) (return Nothing) (Just <$> expression) + conditionSemicolon <- consume SEMICOLON $ SyntaxError "Expected ';' after loop condition" + increment <- ifM (check RIGHT_PAREN) (return Nothing) (Just <$> expression) + rightParen <- consume RIGHT_PAREN $ SyntaxError "Expected ')' after for clauses" + body <- statement + body1 <- case increment of + Just inc -> return $ BlockStmt <$> ((\x y -> [x, y]) <$> body <*> (ExpressionStmt <$> inc)) + Nothing -> return body + cond1 <- case condition of + Just cond -> return cond + Nothing -> return $ Right $ LiteralExpr $ BoolObject True + let body2 = WhileStmt <$> cond1 <*> body1 + body3 <- case initializer of + Just init -> return $ BlockStmt <$> ((\x y -> [x, y]) <$> init <*> body2) + Nothing -> return body2 + return $ leftParen >> conditionSemicolon >> rightParen >> body3 + ifStatement :: State ParserState (Either SyntaxError Stmt) ifStatement = do leftParenMaybe <- consume LEFT_PAREN $ SyntaxError "Expected '(' after 'if'" @@ -134,6 +167,13 @@ expressionStatement = do (_, Left err) -> return $ Left err (Right value, Right _) -> return $ Right $ ExpressionStmt value +whileStatement :: State ParserState (Either SyntaxError Stmt) +whileStatement = do + leftParenMaybe <- consume LEFT_PAREN $ SyntaxError "Expected '(' after 'if'" + conditionMaybe <- expression + rightParenMaybe <- consume RIGHT_PAREN $ SyntaxError "Expected ')' after if condition" + bodyMaybe <- statement + return $ WhileStmt <$> (leftParenMaybe >> conditionMaybe <* rightParenMaybe) <*> bodyMaybe expression :: State ParserState (Either SyntaxError Expr) expression = assignment From ef7dc3603dfa25d41f6e5f08ba876e4a386e0618 Mon Sep 17 00:00:00 2001 From: vvsob Date: Tue, 30 Dec 2025 06:53:05 +0300 Subject: [PATCH 09/10] tidy up --- README.md | 4 ++++ app/Main.hs | 14 +++++++++++++- examples/fibonacci.lox | 9 +++++++++ examples/scope.lox | 19 +++++++++++++++++++ src/Lox/Parser.hs | 6 +++++- 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 examples/fibonacci.lox create mode 100644 examples/scope.lox diff --git a/README.md b/README.md index 928141b..f83a52c 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ # crafting-interpreters-hs + +Usage: `lox [file]` + +Implemented up to control flow statements (chapter 9). diff --git a/app/Main.hs b/app/Main.hs index 007e933..c35aa2a 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -15,8 +15,20 @@ run source = do Left (SyntaxError s) -> putStrLn s Right statements -> runStatements statements +runEval :: String -> IO () +runEval source = do + let tokensMaybe = scanTokensFromSource source + object <- case tokensMaybe of + Left UnexpectedCharacterError -> putStrLn "Unexpected character" >> return NullObject + Right tokens -> do + let exprMaybe = parseExpression tokens + case exprMaybe of + Left (SyntaxError s) -> putStrLn s >> return NullObject + Right statements -> eval statements + print object + repl :: IO () -repl = putStr ">> " >> hFlush stdout >> getLine >>= run +repl = putStr ">> " >> hFlush stdout >> getLine >>= runEval main :: IO () main = getArgs >>= fs diff --git a/examples/fibonacci.lox b/examples/fibonacci.lox new file mode 100644 index 0000000..aa85ecf --- /dev/null +++ b/examples/fibonacci.lox @@ -0,0 +1,9 @@ +var a = 0; +var temp; + +for (var b = 1; a < 10000; b = temp + b) { + print a; + temp = a; + a = b; +} + diff --git a/examples/scope.lox b/examples/scope.lox new file mode 100644 index 0000000..d563807 --- /dev/null +++ b/examples/scope.lox @@ -0,0 +1,19 @@ +var a = "global a"; +var b = "global b"; +var c = "global c"; +{ + var a = "outer a"; + var b = "outer b"; + { + var a = "inner a"; + print a; + print b; + print c; + } + print a; + print b; + print c; +} +print a; +print b; +print c; diff --git a/src/Lox/Parser.hs b/src/Lox/Parser.hs index ff04174..cc44a96 100644 --- a/src/Lox/Parser.hs +++ b/src/Lox/Parser.hs @@ -1,6 +1,7 @@ module Lox.Parser ( SyntaxError (..), - parse + parse, + parseExpression ) where import Control.Monad @@ -55,6 +56,9 @@ data SyntaxError = SyntaxError String deriving Show parse :: [Token] -> Either SyntaxError [Stmt] parse tokens = evalState program (ParserState {tokens=tokens}) +parseExpression :: [Token] -> Either SyntaxError Expr +parseExpression tokens = evalState expression (ParserState {tokens=tokens}) + program :: State ParserState (Either SyntaxError [Stmt]) program = do atEnd <- isAtEnd From 8eeb671692095a6062fd8e14e1e93aaf79b4dbf5 Mon Sep 17 00:00:00 2001 From: vvsob Date: Tue, 30 Dec 2025 06:58:23 +0300 Subject: [PATCH 10/10] simplify scanner error type --- app/Main.hs | 4 ++-- src/Lox/Scanner.hs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Main.hs b/app/Main.hs index c35aa2a..832d0ba 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -8,7 +8,7 @@ run :: String -> IO () run source = do let tokensMaybe = scanTokensFromSource source case tokensMaybe of - Left UnexpectedCharacterError -> putStrLn "Unexpected character" + Left (LexicalError s) -> putStrLn s Right tokens -> do let stmtMaybe = parse tokens case stmtMaybe of @@ -19,7 +19,7 @@ runEval :: String -> IO () runEval source = do let tokensMaybe = scanTokensFromSource source object <- case tokensMaybe of - Left UnexpectedCharacterError -> putStrLn "Unexpected character" >> return NullObject + Left (LexicalError s) -> putStrLn s >> return NullObject Right tokens -> do let exprMaybe = parseExpression tokens case exprMaybe of diff --git a/src/Lox/Scanner.hs b/src/Lox/Scanner.hs index 44ace2f..120ac6d 100644 --- a/src/Lox/Scanner.hs +++ b/src/Lox/Scanner.hs @@ -2,7 +2,7 @@ module Lox.Scanner ( TokenType (..), Object (..), Token (..), - ScannerError (..), + LexicalError (..), scanTokensFromSource ) where @@ -45,16 +45,16 @@ data Token = Token { data ScannerState = ScannerState {source :: String, current :: String, lineNumber :: Int} -data ScannerError = UnexpectedCharacterError deriving Show +data LexicalError = LexicalError String emptyScannerState :: String -> ScannerState emptyScannerState source = ScannerState {source=source, current="", lineNumber=1} -scanTokensFromSource :: String -> Either ScannerError [Token] +scanTokensFromSource :: String -> Either LexicalError [Token] scanTokensFromSource source = evalState scanTokens (emptyScannerState source) -scanTokens :: State ScannerState (Either ScannerError [Token]) +scanTokens :: State ScannerState (Either LexicalError [Token]) scanTokens = do atEnd <- isAtEnd if atEnd then return . return <$> addToken EOF else do @@ -70,7 +70,7 @@ isAtEnd = gets scannerIsAtEnd scannerIsAtEnd :: ScannerState -> Bool scannerIsAtEnd ScannerState {source=source} = null source -scanToken :: State ScannerState (Either ScannerError (Maybe Token)) +scanToken :: State ScannerState (Either LexicalError (Maybe Token)) scanToken = do resetCurrent c <- advance @@ -97,7 +97,7 @@ scanToken = do '\r' -> return nothing '\t' -> return nothing '\n' -> modify (\s@(ScannerState {lineNumber=n}) -> s {lineNumber=n+1}) >> return nothing - c -> if isDigit c then ok <$> scanNumber else if isAlpha c then ok <$> scanIdentifier else return $ Left UnexpectedCharacterError + c -> if isDigit c then ok <$> scanNumber else if isAlpha c then ok <$> scanIdentifier else return $ Left $ LexicalError "Unexpected character" scanString :: State ScannerState Token scanString = do