1-- SPDX-FileCopyrightText: 2025 Sören Tempel <soeren+git@soeren-tempel.net>
2--
3-- SPDX-License-Identifier: GPL-3.0-only
4
5module Parser where
6
7import qualified Data.Map as Map
8import Language.QBE.Parser (dataDef, funcDef, typeDef)
9import Language.QBE.Types
10import Test.Tasty
11import Test.Tasty.HUnit
12import qualified Text.ParserCombinators.Parsec as P
13
14typeTests :: TestTree
15typeTests =
16 testGroup
17 "Aggregate Type Definition"
18 [ testCase "Opaque type with alignment" $
19 let v = TypeDef (UserIdent "opaque") (Just 16) (AOpaque 32)
20 in parse "type :opaque = align 16 { 32 }" @?= Right v,
21 testCase "Regular empty type" $
22 let v = TypeDef (UserIdent "empty") Nothing (ARegular [])
23 in parse "type :empty = {}" @?= Right v,
24 testCase "Regular type with multiple fields" $
25 let f = [(SExtType (Base Single), Nothing), (SExtType (Base Single), Nothing)]
26 v = TypeDef (UserIdent "twofloats") Nothing (ARegular f)
27 in parse "type :twofloats = { s, s }" @?= Right v,
28 testCase "Regular type with trailing whitespaces" $
29 let f = [(SExtType Byte, Nothing), (SExtType (Base Word), Just 100)]
30 v = TypeDef (UserIdent "abyteandmanywords") Nothing (ARegular f)
31 in parse "type :abyteandmanywords = { b, w 100 }" @?= Right v,
32 testCase "Union type with multiple fields" $
33 let f = [[(SExtType Byte, Nothing)], [(SExtType (Base Single), Nothing)]]
34 v = TypeDef (UserIdent "un9") Nothing (AUnion f)
35 in parse "type :un9 = { { b } { s } }" @?= Right v,
36 testCase "Union type with multiple nested fields" $
37 let f =
38 [ [(SExtType (Base Long), Nothing), (SExtType (Base Single), Nothing)],
39 [(SExtType (Base Word), Nothing), (SExtType (Base Long), Nothing)]
40 ]
41 v = TypeDef (UserIdent "un9") Nothing (AUnion f)
42 in parse "type :un9 = { { l, s } { w, l } }" @?= Right v,
43 testCase "Type definition with trailing comma" $
44 let f = [(SExtType (Base Single), Nothing), (SExtType (Base Single), Nothing)]
45 v = TypeDef (UserIdent "twofloats") Nothing (ARegular f)
46 in parse "type :twofloats = { s, s, }" @?= Right v
47 ]
48 where
49 parse :: String -> Either P.ParseError TypeDef
50 parse = P.parse typeDef ""
51
52dataTests :: TestTree
53dataTests =
54 testGroup
55 "Data Definition"
56 [ testCase "Data definition with zero fill" $
57 let v = DataDef [] (GlobalIdent "foo") Nothing [OZeroFill 42]
58 in parse "data $foo = { z 42 }" @?= Right v,
59 testCase "Data definition with empty value" $
60 let v = DataDef [] (GlobalIdent "foo") Nothing []
61 in parse "data $foo = {}" @?= Right v,
62 testCase "Data definition without optional spaces" $
63 let v = DataDef [] (GlobalIdent "foo") Nothing [OZeroFill 42]
64 in parse "data $foo={z 42}" @?= Right v,
65 testCase "Data definition with newlines as spaces" $
66 let v = DataDef [] (GlobalIdent "foo") Nothing [OZeroFill 42]
67 in parse "data\n$foo={z\n42}" @?= Right v,
68 testCase "Data definition with comments" $
69 let v = DataDef [] (GlobalIdent "foo") Nothing [OZeroFill 42]
70 in parse "data\n#test\n$foo={z\n#foo\n42}" @?= Right v,
71 testCase "Data definition with comments and whitespaces" $
72 let v = DataDef [] (GlobalIdent "foo") Nothing [OZeroFill 42]
73 in parse "data\n#test1 \n #test2\n$foo={z\n#foo\n42}" @?= Right v,
74 testCase "Data definition with linkage" $
75 let v = DataDef [LExport] (GlobalIdent "foo") Nothing [OZeroFill 42]
76 in parse "export data $foo = { z 42 }" @?= Right v,
77 testCase "Data definition with linkage, newlines, and comments" $
78 let v = DataDef [LExport, LThread] (GlobalIdent "foo") Nothing [OZeroFill 42]
79 in parse "export\nthread\n#foo\ndata $foo = { z 42 }" @?= Right v,
80 testCase "Data definition with types" $
81 let w = [DConst (Number 23), DConst (Number 42)]
82 v = DataDef [] (GlobalIdent "bar") Nothing [OItem (Base Word) w]
83 in parse "data $bar = { w 23 42 }" @?= Right v,
84 testCase "An object containing two 64-bit fields" $
85 let o =
86 [ OItem (Base Long) [DConst (Number 0xffffffffffffffff)],
87 OItem (Base Long) [DConst (Number 23)]
88 ]
89 v = DataDef [] (GlobalIdent "c") Nothing o
90 in parse "data $c = { l -1, l 23 }" @?= Right v,
91 testCase "Data definition with specified alignment and linkage" $
92 let v = DataDef [LExport] (GlobalIdent "b") (Just 8) [OZeroFill 1000]
93 in parse "export data $b = align 8 { z 1000 }" @?= Right v,
94 testCase "Data definition with linkage section and string escape sequences" $
95 let v = DataDef [LSection "f\\oo\\\"bar" Nothing] (GlobalIdent "b") (Just 8) [OZeroFill 1]
96 in parse "section \"f\\oo\\\"bar\" data $b =align 8 {z 1}" @?= Right v,
97 testCase "Data definition with symbol offset" $
98 let v = DataDef {linkage = [], name = GlobalIdent "b", align = Just 8, objs = [OItem (Base Long) [DSymOff (GlobalIdent "s") 1]]}
99 in parse "data $b = align 8 { l $s + 1 }" @?= Right v,
100 testCase "Data definition with symbol offset and without whitespaces" $
101 let v = DataDef {linkage = [], name = GlobalIdent "b", align = Just 8, objs = [OItem (Base Long) [DSymOff (GlobalIdent "s") 1]]}
102 in parse "data $b = align 8 {l $s+1}" @?= Right v,
103 testCase "Data definition with symbol but without offset" $
104 let v = DataDef {linkage = [], name = GlobalIdent "b", align = Just 8, objs = [OItem (Base Long) [DConst (Global (GlobalIdent "s"))]]}
105 in parse "data $b = align 8 {l $s}" @?= Right v,
106 testCase "Data definition with octal character sequence" $
107 let v = DataDef {linkage = [], name = GlobalIdent "b", align = Just 1, objs = [OItem Byte [DString "f\too\NUL"]]}
108 in parse "data $b = align 1 { b \"f\\011oo\\000\" }" @?= Right v,
109 testCase "Data definition with trailing comma" $
110 let v = DataDef {linkage = [], name = GlobalIdent "b", align = Just 1, objs = [OItem Byte [DConst (Number 1)], OItem Byte [DConst (Number 2)]]}
111 in parse "data $b = align 1 { b 1, b 2,}" @?= Right v
112 ]
113 where
114 parse :: String -> Either P.ParseError DataDef
115 parse = P.parse dataDef ""
116
117funcTests :: TestTree
118funcTests =
119 testGroup
120 "Function Definition"
121 [ testCase "Minimal function definition" $
122 let p = [Regular (ABase Word) (LocalIdent "argc")]
123 b = [Block {label = BlockIdent "start", phi = [], stmt = [], term = Return Nothing}]
124 f = FuncDef [] (GlobalIdent "main") Nothing p b
125 in parse "function $main(w %argc) {\n@start\nret\n}" @?= Right f,
126 testCase "Function definition with load instruction" $
127 let s = [Assign (LocalIdent "v") Word (Load (LBase Word) (VLocal $ LocalIdent "addr"))]
128 b = [Block {label = BlockIdent "begin", phi = [], stmt = s, term = Return Nothing}]
129 f = FuncDef [] (GlobalIdent "main") Nothing [] b
130 in parse "function $main() {\n@begin\n%v =w loadw %addr\nret\n}" @?= Right f,
131 testCase "Function definition with linkage and return type" $
132 let p = [Regular (ABase Long) (LocalIdent "v")]
133 b = [Block {label = BlockIdent "start", phi = [], stmt = [], term = Return Nothing}]
134 f = FuncDef [LExport, LThread] (GlobalIdent "example") (Just (ABase Word)) p b
135 in parse "export\nthread function w $example(l %v) {\n@start\nret\n}" @?= Right f,
136 testCase "Function definition with section linkage" $
137 let p = [Regular (ABase Long) (LocalIdent "v")]
138 b = [Block {label = BlockIdent "start", phi = [], stmt = [], term = Return Nothing}]
139 f = FuncDef [LSection "foo" Nothing] (GlobalIdent "bla") (Just (ABase Word)) p b
140 in parse "section \"foo\"\nfunction w $bla(l %v) {\n@start\nret\n}" @?= Right f,
141 testCase "Function definition with subword return type" $
142 let b = [Block {label = BlockIdent "here", phi = [], stmt = [], term = Halt}]
143 f = FuncDef [] (GlobalIdent "f") (Just (ASubWordType SignedHalf)) [] b
144 in parse "function sh $f() {\n@here\nhlt\n}" @?= Right f,
145 testCase "Function definition with comments" $
146 let p = [Regular (ABase Long) (LocalIdent "v")]
147 b = [Block {label = BlockIdent "start", phi = [], stmt = [], term = Return Nothing}]
148 f = FuncDef [LSection "foo" (Just "bar")] (GlobalIdent "bla") (Just (ABase Word)) p b
149 in parse "section \"foo\" \"bar\"\n#test\nfunction w $bla(l %v) {\n#foo\n@start\n# bar \nret\n#bllubbb\n#bllaaa\n}" @?= Right f,
150 testCase "Function definition with comparison instruction" $
151 let c = Compare Word CSlt (VConst (Const (Number 23))) (VConst (Const (Number 42)))
152 b = [Block {label = BlockIdent "start", phi = [], stmt = [Assign (LocalIdent "res") Word c], term = Return Nothing}]
153 f = FuncDef [] (GlobalIdent "f") Nothing [] b
154 in parse "function $f() {\n@start\n%res =w csltw 23, 42\nret\n}" @?= Right f,
155 testCase "Function definition with extend instruction" $
156 let c = Ext SLSignedWord (VConst (Const (Number 42)))
157 b = [Block {label = BlockIdent "start", phi = [], stmt = [Assign (LocalIdent "res") Word c], term = Return Nothing}]
158 f = FuncDef [] (GlobalIdent "f") Nothing [] b
159 in parse "function $f() {\n@start\n%res =w extsw 42\nret\n}" @?= Right f,
160 testCase "Function definition with fallthrough block" $
161 let b1 = Block {label = BlockIdent "b1", phi = [], stmt = [], term = Jump (BlockIdent "b2")}
162 b2 = Block {label = BlockIdent "b2", phi = [], stmt = [], term = Return Nothing}
163 f = FuncDef [] (GlobalIdent "f") Nothing [] [b1, b2]
164 in parse "function $f() {\n@b1\n@b2\nret\n}" @?= Right f,
165 testCase "Block with phi instrunction" $
166 let v1 = VConst (Const (Number 1))
167 v2 = VConst (Const (Number 2))
168 p1 = Phi (LocalIdent "v") Word $ Map.fromList [(BlockIdent "b1", v1), (BlockIdent "b2", v2)]
169 b1 = Block {label = BlockIdent "b1", phi = [], stmt = [], term = Jump (BlockIdent "b2")}
170 b2 = Block {label = BlockIdent "b2", phi = [], stmt = [], term = Jump (BlockIdent "b3")}
171 b3 = Block {label = BlockIdent "b3", phi = [p1], stmt = [], term = Return Nothing}
172 fn = FuncDef [] (GlobalIdent "f") Nothing [] [b1, b2, b3]
173 in parse "function $f() {\n@b1\njmp @b2\n@b2\njmp @b3\n@b3\n%v =w phi @b1 1, @b2 2\nret\n}" @?= Right fn,
174 testCase "Call instruction with integer literal value" $
175 let c = Call Nothing (VConst (Const $ Global (GlobalIdent "foo"))) [ArgReg (ABase Word) (VConst (Const (Number 42)))]
176 b = [Block {label = BlockIdent "s", phi = [], stmt = [c], term = Return Nothing}]
177 f = FuncDef [] (GlobalIdent "f") Nothing [] b
178 in parse "function $f() {\n@s\ncall $foo(w 42)\nret\n}" @?= Right f,
179 testCase "Unary neg instruction" $
180 let i1 = Assign (LocalIdent "r") Word $ Neg (VConst (Const (Number 1)))
181 i2 = Assign (LocalIdent "r") Word $ Neg (VLocal $ LocalIdent "r")
182 b = Block {label = BlockIdent "s", phi = [], stmt = [i1, i2], term = Halt}
183 f = FuncDef [] (GlobalIdent "f") Nothing [] [b]
184 in parse "function $f() {\n@s\n%r =w neg 1\n%r =w neg %r\nhlt\n}" @?= Right f,
185 testCase "cast instruction" $
186 let c = Assign (LocalIdent "r") Word $ Cast (VLocal $ LocalIdent "f")
187 b = Block {label = BlockIdent "s", phi = [], stmt = [c], term = Halt}
188 f = FuncDef [] (GlobalIdent "f") Nothing [Regular (ABase Single) (LocalIdent "f")] [b]
189 in parse "function $f(s %f) {\n@s\n%r =w cast %f\nhlt\n}" @?= Right f
190 ]
191 where
192 parse :: String -> Either P.ParseError FuncDef
193 parse = P.parse funcDef ""
194
195mkParser :: TestTree
196mkParser =
197 testGroup
198 "Tests for the QBE parser"
199 [typeTests, dataTests, funcTests]