Skip to content

Commit 498833c

Browse files
authored
Merge pull request #2 from 0xpantera/test/driver
Add comprehensive test suite
2 parents f568e46 + 9d493c7 commit 498833c

File tree

11 files changed

+283
-50
lines changed

11 files changed

+283
-50
lines changed

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,16 @@
3434
### Changed
3535
- Compiler pipeline now includes TACKY transformation stage
3636
- Assembly generation now works from TACKY rather than directly from AST
37-
- Parser improved to handle nested expressions correctly
37+
- Parser improved to handle nested expressions correctly
38+
39+
## 0.2.1.0 -- 2024-11-25
40+
41+
### Added
42+
- Comprehensive test suite with Hspec and Tasty
43+
- Unit tests for all compiler stages:
44+
- Lexer tests
45+
- Parser tests
46+
- TACKY generation tests
47+
- Assembly generation tests
48+
- Full pipeline integration tests
49+
- Test utilities and helper functions in Test.Common

README.md

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -65,40 +65,47 @@ Programs are represented internally using a series of increasingly lower-level d
6565

6666
## Project Structure
6767

68-
```
69-
.
70-
├── app/ # Application entry point
71-
│ └── Main.hs
72-
├── bin/ # Binary outputs
73-
├── lib/ # Main library code
74-
│ ├── Halcyon.hs # Library entry point
75-
│ └── Halcyon/ # Core modules
76-
│ ├── Backend/ # Code generation and emission
77-
│ │ ├── Codegen.hs # TACKY to Assembly conversion
78-
│ │ ├── Emit.hs # Assembly to text output
79-
│ │ └── ReplacePseudos.hs # Register/stack allocation
80-
│ ├── Core/ # Core data types and utilities
81-
│ │ ├── Assembly.hs # Assembly representation
82-
│ │ ├── Ast.hs # C language AST
83-
│ │ ├── Monad.hs # Compiler monad stack
84-
│ │ ├── Settings.hs # Compiler settings and types
85-
│ │ ├── Tacky.hs # TACKY IR definition
86-
│ │ └── TackyGen.hs # AST to TACKY transformation
87-
│ ├── Driver/ # Compiler driver
88-
│ │ ├── Cli.hs # Command line interface
89-
│ │ └── Pipeline.hs # Compilation pipeline
90-
│ └── Frontend/ # Parsing and analysis
91-
│ ├── Lexer.hs # Lexical analysis
92-
│ ├── Parse.hs # Parsing
93-
│ └── Tokens.hs # Token definitions
94-
├── test/ # Test suite
95-
│ └── Main.hs
96-
├── CHANGELOG.md # Version history
97-
├── LICENSE # Project license
98-
├── README.md # Project documentation
99-
├── flake.nix # Nix build configuration
100-
└── halcyon.cabal # Cabal build configuration
101-
```
68+
```
69+
.
70+
├── app/ # Application entry point
71+
│ └── Main.hs
72+
├── bin/ # Binary outputs
73+
├── lib/ # Main library code
74+
│ ├── Halcyon.hs # Library entry point
75+
│ └── Halcyon/ # Core modules
76+
│ ├── Backend/ # Code generation and emission
77+
│ │ ├── Codegen.hs # TACKY to Assembly conversion
78+
│ │ ├── Emit.hs # Assembly to text output
79+
│ │ └── ReplacePseudos.hs # Register/stack allocation
80+
│ ├── Core/ # Core data types and utilities
81+
│ │ ├── Assembly.hs # Assembly representation
82+
│ │ ├── Ast.hs # C language AST
83+
│ │ ├── Monad.hs # Compiler monad stack
84+
│ │ ├── Settings.hs # Compiler settings and types
85+
│ │ ├── Tacky.hs # TACKY IR definition
86+
│ │ └── TackyGen.hs # AST to TACKY transformation
87+
│ ├── Driver/ # Compiler driver
88+
│ │ ├── Cli.hs # Command line interface
89+
│ │ └── Pipeline.hs # Compilation pipeline
90+
│ └── Frontend/ # Parsing and analysis
91+
│ ├── Lexer.hs # Lexical analysis
92+
│ ├── Parse.hs # Parsing
93+
│ └── Tokens.hs # Token definitions
94+
├── test/ # Test suite
95+
│ ├── Main.hs
96+
│ └── Test/
97+
│ ├── Lexer.hs
98+
│ ├── Parser.hs
99+
│ ├── Tacky.hs
100+
│ ├── Assembly.hs
101+
│ ├── Pipeline.hs
102+
│ └── Common.hs
103+
├── CHANGELOG.md # Version history
104+
├── LICENSE # Project license
105+
├── README.md # Project documentation
106+
├── flake.nix # Nix build configuration
107+
└── halcyon.cabal # Cabal build configuration
108+
```
102109
103110
### Architecture
104111
@@ -147,6 +154,37 @@ cabal run halcyon -- input.c
147154
cabal run halcyon -- --lex input.c
148155
```
149156

157+
## Testing
158+
159+
Halcyon uses Hspec and Tasty for its test suite. The tests cover all stages of compilation:
160+
161+
```bash
162+
# Run all tests
163+
cabal test
164+
165+
# Run tests with output
166+
cabal test --test-show-details=direct
167+
168+
# Run a specific test module
169+
cabal test --test-pattern "Lexer"
170+
```
171+
172+
The test suite includes:
173+
174+
- Unit tests for each compiler stage
175+
- Integration tests for the full pipeline
176+
- Helper utilities for building test cases
177+
178+
Tests are organized by compiler stage in `test/Test/`:
179+
180+
- `Lexer.hs`: Token generation
181+
- `Parser.hs`: AST construction
182+
- `Tacky.hs`: TACKY IR generation
183+
- `Assembly.hs`: Assembly generation
184+
- `Pipeline.hs`: Full compilation pipeline
185+
- `Common.hs`: Shared test utilities
186+
187+
150188
## External Dependencies
151189

152190
Halcyon relies on the following system tools:
@@ -168,7 +206,7 @@ The compiler provides detailed error reporting for:
168206

169207
### The Basics
170208
- [x] A minimal compiler
171-
- [ ] Unary operators
209+
- [x] Unary operators
172210
- [ ] Binary operators
173211
- [ ] Logical and relational operators
174212
- [ ] Local variables

halcyon.cabal

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 3.0
22
name: halcyon
3-
version: 0.2.0.0
3+
version: 0.2.1.0
44
-- synopsis:
55
-- description:
66
license: BSD-3-Clause
@@ -19,13 +19,12 @@ common warnings
1919
library
2020
import: warnings
2121
exposed-modules:
22-
Halcyon
23-
other-modules:
24-
Halcyon.Core.Settings
22+
Halcyon
23+
, Halcyon.Core.Settings
2524
, Halcyon.Core.Ast
2625
, Halcyon.Core.Assembly
2726
, Halcyon.Core.Monad
28-
, Halcyon.Core.Tacky
27+
, Halcyon.Core.Tacky
2928
, Halcyon.Core.TackyGen
3029
, Halcyon.Frontend.Lexer
3130
, Halcyon.Frontend.Parse
@@ -35,6 +34,7 @@ library
3534
, Halcyon.Backend.ReplacePseudos
3635
, Halcyon.Driver.Cli
3736
, Halcyon.Driver.Pipeline
37+
other-modules:
3838
-- other-extensions:
3939
build-depends: base ^>=4.20.0.0
4040
, bytestring
@@ -65,11 +65,26 @@ executable halcyon
6565
test-suite halcyon-test
6666
import: warnings
6767
default-language: GHC2024
68-
-- other-modules:
68+
other-modules:
69+
Test.Lexer
70+
, Test.Parser
71+
, Test.Tacky
72+
, Test.Assembly
73+
, Test.Pipeline
6974
-- other-extensions:
7075
type: exitcode-stdio-1.0
7176
hs-source-dirs: test
7277
main-is: Main.hs
7378
build-depends:
74-
base ^>=4.20.0.0,
75-
halcyon
79+
base ^>=4.20.0.0
80+
, halcyon
81+
, tasty ^>=1.4
82+
, tasty-hspec ^>=1.2
83+
, tasty-hedgehog ^>=1.4
84+
, tasty-golden ^>=2.3
85+
, hedgehog ^>=1.4
86+
, hspec ^>=2.11
87+
, text
88+
, megaparsec
89+
, directory
90+
, filepath

lib/Halcyon/Core/Ast.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@ module Halcyon.Core.Ast where
33
import Data.Text ( Text )
44

55
data Program = Program FunctionDef
6-
deriving Show
6+
deriving (Eq, Show)
77

88
data FunctionDef = Function
99
{ name :: Text
1010
, body :: Statement
11-
} deriving Show
11+
} deriving (Eq, Show)
1212

1313
data Statement = Return Expr
14-
deriving Show
14+
deriving (Eq, Show)
1515

1616
data Expr = Constant Int | Unary UnaryOp Expr
17-
deriving Show
17+
deriving (Eq, Show)
1818

1919
data UnaryOp = Complement | Negate
20-
deriving Show
20+
deriving (Eq, Show)
2121

2222
-- Formal Grammar in EBNF
2323
-- <program> ::= <function>

test/Main.hs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
1+
{-# LANGUAGE OverloadedStrings #-}
12
module Main (main) where
23

4+
import Test.Tasty ( defaultMain, testGroup )
5+
import Test.Tasty.Hspec ( testSpecs )
6+
7+
import qualified Test.Lexer as Lex
8+
import qualified Test.Parser as Parse
9+
import qualified Test.Tacky as Tacky
10+
import qualified Test.Assembly as Asm
11+
import qualified Test.Pipeline as Pipeline
12+
313
main :: IO ()
4-
main = putStrLn "Test suite not yet implemented."
14+
main = do
15+
specs <- concat <$> mapM testSpecs
16+
[ Lex.lexerSpecs
17+
, Parse.parserSpecs
18+
, Tacky.tackySpecs
19+
, Asm.assemblySpecs
20+
, Pipeline.pipelineSpecs
21+
]
22+
23+
defaultMain (testGroup "All Tests" [
24+
testGroup "Specs" specs
25+
-- Add property/golden tests later:
26+
-- , testGroup "Properties" props
27+
-- , testGroup "Golden Tests" goldens
28+
])

test/Test/Assembly.hs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
module Test.Assembly (assemblySpecs) where
3+
4+
import Test.Hspec
5+
import Halcyon.Backend.Codegen (gen)
6+
import Halcyon.Core.Assembly qualified as Asm
7+
import Halcyon.Core.Tacky qualified as Tacky
8+
9+
assemblySpecs :: Spec
10+
assemblySpecs = describe "Assembly Generation" $ do
11+
it "generates assembly for simple return" $
12+
let tacky = Tacky.Program $ Tacky.Function "main" [Tacky.Return (Tacky.Constant 42)]
13+
in gen tacky `shouldBe` Asm.Program (Asm.Function "main"
14+
[ Asm.Mov (Asm.Imm 42) (Asm.Register Asm.Ax)
15+
, Asm.Ret])
16+
17+
it "generates assembly for unary operation" $
18+
let tacky = Tacky.Program $ Tacky.Function "main"
19+
[ Tacky.Unary Tacky.Negate (Tacky.Constant 42) (Tacky.Var "tmp.0")
20+
, Tacky.Return (Tacky.Var "tmp.0")]
21+
in gen tacky `shouldBe` Asm.Program (Asm.Function "main"
22+
[ Asm.Mov (Asm.Imm 42) (Asm.Pseudo "tmp.0")
23+
, Asm.Unary Asm.Neg (Asm.Pseudo "tmp.0")
24+
, Asm.Mov (Asm.Pseudo "tmp.0") (Asm.Register Asm.Ax)
25+
, Asm.Ret])

test/Test/Common.hs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module Test.Common where
2+
3+
import Halcyon.Core.Ast as Ast
4+
5+
-- Smart constructors for building test cases
6+
makeMainFunction :: Statement -> Program
7+
makeMainFunction stmt = Program (Function "main" stmt)
8+
9+
makeReturn :: Expr -> Statement
10+
makeReturn = Return
11+
12+
makeConstant :: Int -> Expr
13+
makeConstant = Constant
14+
15+
makeUnary :: UnaryOp -> Int -> Expr
16+
makeUnary op n = Unary op (Constant n)
17+
18+
-- Combining them for common cases
19+
simpleProgram :: Int -> Program
20+
simpleProgram n = makeMainFunction (makeReturn (makeConstant n))
21+
22+
unaryProgram :: UnaryOp -> Int -> Program
23+
unaryProgram op n = makeMainFunction (makeReturn (makeUnary op n))

test/Test/Lexer.hs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
module Test.Lexer (lexerSpecs) where
3+
4+
import Test.Hspec
5+
import Text.Megaparsec (parse)
6+
import Halcyon.Frontend.Lexer (lexer)
7+
import Halcyon.Frontend.Tokens
8+
9+
lexerSpecs :: Spec
10+
lexerSpecs = describe "Lexer" $ do
11+
describe "basic tokens" $ do
12+
it "lexes empty input" $
13+
parse lexer "" "" `shouldParse` []
14+
15+
it "lexes simple return" $
16+
parse lexer "" "return 42;" `shouldParse`
17+
[TokReturn, TokNumber 42, TokSemicolon]
18+
19+
describe "unary operators" $ do
20+
it "lexes negation" $
21+
parse lexer "" "-42" `shouldParse`
22+
[TokHyphen, TokNumber 42]
23+
24+
it "lexes complement" $
25+
parse lexer "" "~42" `shouldParse`
26+
[TokTilde, TokNumber 42]
27+
28+
29+
-- Helper for cleaner parse result assertions
30+
shouldParse :: (Eq a, Show a, Show e) => Either e a -> a -> Expectation
31+
shouldParse (Right got) expected = got `shouldBe` expected
32+
shouldParse (Left err) _ = expectationFailure $ show err

test/Test/Parser.hs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
module Test.Parser (parserSpecs) where
3+
4+
import Test.Hspec
5+
import Halcyon.Frontend.Parse (parseTokens)
6+
import Halcyon.Frontend.Tokens
7+
import Halcyon.Core.Ast
8+
9+
parserSpecs :: Spec
10+
parserSpecs = describe "Parser" $ do
11+
it "parses minimal program" $
12+
parseTokens [TokInt, TokIdent "main", TokLParen, TokVoid, TokRParen,
13+
TokLBrace, TokReturn, TokNumber 42, TokSemicolon, TokRBrace]
14+
`shouldBe` Right (Program (Function "main" (Return (Constant 42))))
15+
16+
it "parses unary negation" $
17+
parseTokens [TokInt, TokIdent "main", TokLParen, TokVoid, TokRParen,
18+
TokLBrace, TokReturn, TokHyphen, TokNumber 42, TokSemicolon, TokRBrace]
19+
`shouldBe` Right (Program (Function "main" (Return (Unary Negate (Constant 42)))))

0 commit comments

Comments
 (0)