Skip to content

Commit 3c83d69

Browse files
committed
Add pretty printing for unbound variable errors
1 parent b9fc07b commit 3c83d69

File tree

7 files changed

+106
-59
lines changed

7 files changed

+106
-59
lines changed

app/Main.hs

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,23 @@
11
module Main where
22

3-
import Data.Bifunctor
43
import System.Directory
54
import System.Exit
65
import System.IO
7-
import Text.Megaparsec
6+
87

98
import Type
109
import Syntax
1110

12-
import qualified Data.Map as Map
13-
14-
import qualified Parser as Parser
15-
import qualified Infer as Infer
1611
import qualified Gen as Gen
1712

1813

19-
renameMapKey :: Ord a => a -> a -> Map.Map a b -> Map.Map a b
20-
renameMapKey old new m =
21-
case Map.lookup old m of
22-
Nothing -> m
23-
Just v -> Map.insert new v $ Map.delete old m
24-
25-
renameMainType :: Infer.TypeEnv -> Infer.TypeEnv
26-
renameMainType (Infer.TypeEnv env) = Infer.TypeEnv $ renameMapKey "main" "userEntrypoint" env
27-
28-
renameMain :: [Decl] -> [Decl]
29-
renameMain (("main", expr) : xs) = ("userEntrypoint", expr) : renameMain xs
30-
renameMain (x : xs) = x : renameMain xs
31-
renameMain [] = []
32-
33-
compileProgram :: String -> Either String String
34-
compileProgram prog = do
35-
decls <- first errorBundlePretty $ Parser.parseModule "<stdin>" prog
36-
env <- first show $ Infer.inferTop Infer.glslStdLib decls
37-
newEnv <- case Infer.typeof env "main" of
38-
Just (Forall [] (TCon Vec2 `TArr` TCon Vec4)) -> Right $ renameMainType env
39-
Just scheme -> Left $ "Missing main function with correct type. Expected: Vec2 -> Vec4, Found: " ++ show scheme
40-
let newDecls = renameMain decls
41-
let code = newDecls >>= Gen.generateDecl newEnv
42-
pure $ code ++ "\n\nvoid main() { gl_FragColor = userEntrypoint(gl_FragCoord.xy); }"
43-
4414
main :: IO ()
4515
main = do
4616
program <- getContents
4717
path <- makeAbsolute "std.yin"
4818
stdLib <- readFile path
4919
let completeProg = stdLib ++ "\n\n" ++ program
50-
let result = compileProgram completeProg
20+
let result = Gen.compileProgram completeProg
5121
case result of
5222
Right code -> do
5323
putStrLn code

src/Gen.hs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
module Gen where
22

3+
import Text.Megaparsec
4+
5+
import qualified Data.Set as Set
6+
import qualified Data.Map as Map
7+
8+
import Data.List.NonEmpty as NonEmpty
9+
import Data.Bifunctor
310
import Debug.Trace
411
import Infer
512
import Syntax
613
import Type
14+
import qualified Parser as Parser
715

816

917
generateGlslType :: GlslTypes -> String
@@ -39,12 +47,12 @@ generateLet env ((var, expr):xs) inExpr state = let
3947
in generateLet newEnv xs inExpr newState
4048

4149
generateApp :: TypeEnv -> Expr -> Expr -> String
42-
generateApp env (Var fn) expr = fn ++ "(" ++ generateExpr env expr
50+
generateApp env (Var fn _ _) expr = fn ++ "(" ++ generateExpr env expr
4351
generateApp env (App a1 a2) expr = generateApp env a1 a2 ++ ", " ++ generateExpr env expr
4452

4553
generateExpr :: TypeEnv -> Expr -> String
4654
generateExpr env expr = case expr of
47-
Var x -> x
55+
Var x _ _ -> x
4856

4957
Let decls expr -> generateLet env decls expr ""
5058

@@ -91,3 +99,52 @@ generateDecl env (var, lam@(Lam _ _)) = case typeof env var of
9199
Just (Forall _ ty) -> generateGlslType (getLastType ty) ++ " " ++ var ++ "(" ++ generateLam env lam ty
92100
a -> show a
93101
generateDecl env (var, expr) = var ++ " = " ++ generateExpr env expr
102+
103+
104+
instance ShowErrorComponent Infer.TypeError where
105+
showErrorComponent (Infer.UnboundVariable var _ _) = "Variable " ++ var ++ " is unbound"
106+
showErrorComponent err = show err
107+
108+
errorComponentLen (Infer.UnboundVariable _ start end) = end - start
109+
errorComponentLen _ = 0
110+
111+
renameMapKey :: Ord a => a -> a -> Map.Map a b -> Map.Map a b
112+
renameMapKey old new m =
113+
case Map.lookup old m of
114+
Nothing -> m
115+
Just v -> Map.insert new v $ Map.delete old m
116+
117+
renameMainType :: Infer.TypeEnv -> Infer.TypeEnv
118+
renameMainType (Infer.TypeEnv env) = Infer.TypeEnv $ renameMapKey "main" "userEntrypoint" env
119+
120+
renameMain :: [Decl] -> [Decl]
121+
renameMain (("main", expr) : xs) = ("userEntrypoint", expr) : renameMain xs
122+
renameMain (x : xs) = x : renameMain xs
123+
renameMain [] = []
124+
125+
compileProgram :: String -> Either String String
126+
compileProgram prog = do
127+
decls <- first errorBundlePretty $ Parser.parseModule "<stdin>" prog
128+
env <- case Infer.inferTop Infer.glslStdLib decls of
129+
Left err@(Infer.UnboundVariable var start _) -> let
130+
initialState = PosState
131+
{ pstateInput = prog
132+
, pstateOffset = 0
133+
, pstateSourcePos = initialPos ""
134+
, pstateTabWidth = defaultTabWidth
135+
, pstateLinePrefix = ""
136+
}
137+
errorBundle = ParseErrorBundle
138+
{ bundleErrors = NonEmpty.fromList [FancyError start $ Set.fromList [ErrorCustom err]]
139+
-- ^ A collection of 'ParseError's that is sorted by parse error offsets
140+
, bundlePosState = initialState
141+
-- ^ State that is used for line\/column calculation
142+
}
143+
in Left $ errorBundlePretty errorBundle
144+
res -> first show $ res
145+
newEnv <- case Infer.typeof env "main" of
146+
Just (Forall [] (TCon Vec2 `TArr` TCon Vec4)) -> Right $ renameMainType env
147+
Just scheme -> Left $ "Missing main function with correct type. Expected: Vec2 -> Vec4, Found: " ++ show scheme
148+
let newDecls = renameMain decls
149+
let code = newDecls >>= Gen.generateDecl newEnv
150+
pure $ code ++ "\n\nvoid main() { gl_FragColor = userEntrypoint(gl_FragCoord.xy); }"

src/Infer.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ type Subst = Map.Map TVar Type
3030
data TypeError
3131
= UnificationFail Type Type
3232
| InfiniteType TVar Type
33-
| UnboundVariable String
34-
deriving (Show, Eq)
33+
| UnboundVariable String Offset Offset
34+
deriving (Show, Eq, Ord)
3535

3636

3737
glslStdLib :: TypeEnv
@@ -162,10 +162,10 @@ ops tv Sub = tv `TArr` tv `TArr` tv
162162
ops tv Div = tv `TArr` tv `TArr` tv
163163
ops tv Eql = tv `TArr` tv `TArr` typeBool
164164

165-
lookupEnv :: TypeEnv -> Var -> Infer (Subst, Type)
166-
lookupEnv (TypeEnv env) x =
165+
lookupEnv :: TypeEnv -> (Var, Offset, Offset) -> Infer (Subst, Type)
166+
lookupEnv (TypeEnv env) (x, start, end) =
167167
case Map.lookup x env of
168-
Nothing -> throwError $ UnboundVariable (show x)
168+
Nothing -> throwError $ UnboundVariable (show x) start end
169169
Just s -> do t <- instantiate s
170170
return (nullSubst, t)
171171

@@ -194,7 +194,7 @@ swizzleType sw = case length sw of
194194
infer :: TypeEnv -> Expr -> Infer (Subst, Type)
195195
infer env ex = case ex of
196196

197-
Var x -> lookupEnv env x
197+
Var x start end -> lookupEnv env (x, start, end)
198198

199199
Lam x e -> do
200200
tv <- fresh

src/Parser.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ type StringError = ParseErrorBundle String Void
2323

2424
variable :: Parser Expr
2525
variable = do
26+
start <- getOffset
2627
x <- L.identifier
27-
return (Var x)
28+
end <- getOffset
29+
return (Var x start end)
2830

2931
int :: Parser Expr
3032
int = do

src/Syntax.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import qualified Type as T
55
type Var = String
66
type Decl = (Var, Expr)
77

8+
type Offset = Int
9+
810
data Expr
9-
= Var Var
11+
= Var Var Offset Offset
1012
| App Expr Expr
1113
| Lam Var Expr
1214
| Let [Decl] Expr

std.yin

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ shape st_ sides radius smoothing = let
1515

1616
# Randomness
1717

18-
random x = fract (sin x * 10000.0)
18+
random x = fract (sin x * 43758.5453123)
1919

2020
noise_ p = let
2121
const_step = vec3 110.0 241.0 171.0
@@ -44,6 +44,17 @@ noise_ p = let
4444

4545
in mix mix_y_1 mix_y_2 u.z
4646

47+
#hash p = let
48+
#p1 = vec3 (dot p (vec3 127.1 311.7 74.7)) (dot p (vec3 269.5 183.3 246.1)) (dot p (vec3 113.5 271.9 124.6))
49+
#in (cast3 -1.0) + (cast3 2.0) * fract (sin p * 43758.5453123)
50+
51+
gnoise_ x = let
52+
p = floor x
53+
w = fract x
54+
55+
u = w * w * w * (w * (w * 6.0 - 15.0) + 10.0)
56+
in u
57+
4758
noise : Vec2 -> Float -> Float -> Float -> Vec4
4859
noise st time scale offset = let
4960
noised = noise_ (vec3 (st.x * scale) (st.y * scale) (offset * time))
@@ -205,9 +216,6 @@ add c0 c1 amount = (c0 + c1) * cast4 amount + c0 * cast4 (1.0 - amount)
205216
blend : Vec4 -> Vec4 -> Float -> Vec4
206217
blend c0 c1 amount = c0 * cast4 (1.0 - amount) + c1 * cast4 amount
207218

208-
mult : Vec4 -> Vec4 -> Float -> Vec4
209-
mult c0 c1 amount = c0 * cast4 (1.0 - amount) + c1 * cast4 amount
210-
211219
diff : Vec4 -> Vec4 -> Vec4
212220
diff c0 c1 = let
213221
difference = c0.rgb - c1.rgb

test/Spec.hs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,23 @@ main = hspec $ do
2828

2929
it "should infer let exprs" $ do
3030
let expr = (Let [ ("a", Lit $ LInt 1)
31-
, ("b", Var "a")] (Var "b"))
31+
, ("b", Var "a" 0 0)] (Var "b" 0 0))
3232
let res = inferExpr mempty expr
3333
res `shouldBe` Right (Forall [] typeInt)
3434

3535
it "should infer complex exprs" $ do
3636
let expr = Let [ ("b", Lit $ LInt 2)
3737
, ("c", Lit $ LInt 3)
3838
, ("a", If (Lit $ LBool True)
39-
(Op Sub (Var "b") (Var "b"))
40-
(Op Add (Var "c") (Var "c")))
39+
(Op Sub (Var "b" 0 0) (Var "b" 0 0))
40+
(Op Add (Var "c" 0 0) (Var "c" 0 0)))
4141
]
42-
(Op Mul (Var "a") (Var "c"))
42+
(Op Mul (Var "a" 0 0) (Var "c" 0 0))
4343
let res = inferExpr mempty expr
4444
res `shouldBe` Right (Forall [] typeInt)
4545

4646
it "Handle uniform declartions" $ do
47-
let decls = [("u_audio", ParameterDecl $ Uniform Vec4), ("test", (Var "u_audio"))]
47+
let decls = [("u_audio", ParameterDecl $ Uniform Vec4), ("test", (Var "u_audio" 0 0))]
4848
let Right env = inferTop emptyTyenv decls
4949
putStrLn $ show env
5050
let res = typeof env "test"
@@ -60,7 +60,7 @@ main = hspec $ do
6060
-- TODO this should just be an error
6161
it "should ignore type of function if ascribed (for now)" $ do
6262
let decls = [ ("test", TypeAscription (Forall [] (TArr (TCon Vec2) (TCon Vec2))))
63-
, ("test", Lam "n" (Var "n"))
63+
, ("test", Lam "n" (Var "n" 0 0))
6464
]
6565
let Right env = inferTop emptyTyenv decls
6666
putStrLn $ show env
@@ -96,23 +96,23 @@ main = hspec $ do
9696
it "should parse nested application" $ do
9797
let expr = "a 1 1"
9898
let result = P.parseExpr expr
99-
result `shouldBe` Right (App (App (Var "a") (Lit $ LInt 1)) (Lit $ LInt 1))
99+
result `shouldBe` Right (App (App (Var "a" 0 2) (Lit $ LInt 1)) (Lit $ LInt 1))
100100

101101
it "should parse simple let expr" $ do
102102
let expr = "let a = 1 in a"
103103
let result = P.parseExpr expr
104-
result `shouldBe` Right (Let [("a", Lit $ LInt 1)] (Var "a"))
104+
result `shouldBe` Right (Let [("a", Lit $ LInt 1)] (Var "a" 13 14))
105105

106106
it "should parse multi let expr" $ do
107107
let expr = "let\na = 1\n\nb = 2\n\nin \na"
108108
let result = P.parseExpr expr
109109
result `shouldBe` Right (Let [("a", Lit $ LInt 1),
110-
("b", Lit $ LInt 2)] (Var "a"))
110+
("b", Lit $ LInt 2)] (Var "a" 22 23))
111111

112112
it "should parse complex exprs" $ do
113113
let expr = "let a = if true then b - b else c + c in a * c"
114114
let result = P.parseExpr expr
115-
result `shouldBe` Right (Let [("a", If (Lit $ LBool True) (Op Sub (Var "b") (Var "b")) (Op Add (Var "c") (Var "c")))] (Op Mul (Var "a") (Var "c")))
115+
result `shouldBe` Right (Let [("a", If (Lit $ LBool True) (Op Sub (Var "b" 21 23) (Var "b" 25 27)) (Op Add (Var "c" 32 34) (Var "c" 36 38)))] (Op Mul (Var "a" 41 43) (Var "c" 45 46)))
116116

117117
it "should parse uniform declartions" $ do
118118
let expr = "uniform u_audio : Vec4"
@@ -135,17 +135,17 @@ main = hspec $ do
135135

136136
it "should generate simple let expr" $ do
137137
let expr = (Let [ ("a", Lit $ LInt 1)
138-
, ("b", Var "a")] (Var "b"))
138+
, ("b", Var "a" 0 0)] (Var "b" 0 0))
139139
let result = generateExpr glslStdLib expr
140140
result `shouldBe` "int a = 1;\nint b = a;\nreturn b;\n"
141141

142142
it "should generate single application" $ do
143-
let expr = App (Var "func") (Lit (LFloat 0.5))
143+
let expr = App (Var "func" 0 0) (Lit (LFloat 0.5))
144144
let result = generateExpr glslStdLib expr
145145
result `shouldBe` "func(0.5)"
146146

147147
it "should generate triple application" $ do
148-
let expr = App (App (App (Var "vec3") (Lit (LFloat 0.5))) (Lit (LFloat 0.5))) (Lit (LFloat 0.5))
148+
let expr = App (App (App (Var "vec3" 0 0) (Lit (LFloat 0.5))) (Lit (LFloat 0.5))) (Lit (LFloat 0.5))
149149
let result = generateExpr glslStdLib expr
150150
result `shouldBe` "vec3(0.5, 0.5, 0.5)"
151151

@@ -182,3 +182,11 @@ main = hspec $ do
182182
let result = decls >>= generateDecl env
183183
putStrLn result
184184
True `shouldBe` True
185+
186+
describe "main" $ do
187+
describe "compileProgram" $ do
188+
it "should pretty print unbound variable" $ do
189+
let program = "main coord = let a = b in a"
190+
let Left res = compileProgram program
191+
putStrLn res
192+
res `shouldBe` "1:22:\n |\n1 | main coord = let a = b in a\n | ^^\nVariable \"b\" is unbound\n"

0 commit comments

Comments
 (0)