Skip to content

Commit a641b4b

Browse files
committed
[init]
0 parents  commit a641b4b

File tree

15 files changed

+1602
-0
lines changed

15 files changed

+1602
-0
lines changed

.github/workflows/haskell.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: "Bob The Builder"
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
container:
11+
image: haskell:9
12+
steps:
13+
- uses: actions/checkout@v2
14+
- run: cabal update
15+
- run: sed -i 's/4\.14\.2\.0/4\.15\.0\.0/g' $GITHUB_WORKSPACE/axolotl.cabal
16+
- run: WORKSPACE="$GITHUB_WORKSPACE" bash $GITHUB_WORKSPACE/build.sh
17+
- uses: actions/upload-artifact@v2
18+
with:
19+
name: axl
20+
path: ${{ github.workspace }}/axl

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
/dist-newstyle
3+
/.DS_Store

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

axolotl.cabal

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
cabal-version: 2.4
2+
3+
name: axolotl
4+
version: 0.1.0.0
5+
synopsis: a statically typed lisp-like programming language
6+
author: Udit Karode <[email protected]>
7+
category: Language
8+
bug-reports: https://github.com/uditkarode/axolotl/issues
9+
maintainer: Udit Karode <[email protected]>
10+
11+
license: GPL-3.0-or-later
12+
license-file: LICENSE
13+
14+
executable axl
15+
main-is: Main.hs
16+
17+
other-modules: Parser.Ast
18+
, Parser.Combinators
19+
, Parser.Parser
20+
, Analyser.Analyser
21+
, Analyser.Util
22+
, Evaluator.Evaluator
23+
, Evaluator.NativeFns
24+
25+
build-depends: base ^>=4.14.2.0
26+
, unordered-containers
27+
, string-conversions
28+
, pretty-terminal
29+
, pretty-simple
30+
, megaparsec
31+
, either
32+
, text
33+
hs-source-dirs: src
34+
default-extensions: OverloadedStrings
35+
, LambdaCase
36+
, BlockArguments
37+
default-language: Haskell2010

build.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/bash
2+
3+
4+
RED='\033[0;31m'
5+
GREEN='\033[0;32m'
6+
YELLOW='\033[0;33m'
7+
NC='\033[0m'
8+
9+
function check_installed() {
10+
if [ -z "$(which ${1})" ] && [ ! -f "/bin/${1}" ]; then
11+
echo "false"
12+
else
13+
echo "true"
14+
fi
15+
}
16+
17+
echo '-------------- building release binary --------------'
18+
19+
cd $(if [ ! -z "$WORKSPACE" ]; then echo "$WORKSPACE"; else echo "."; fi)
20+
21+
if [ "$(check_installed cabal)" = "true" ]; then
22+
cabal new-build -O2 --disable-debug-info --enable-executable-stripping --enable-library-stripping --disable-debug-info --disable-library-for-ghci --enable-split-sections
23+
else
24+
echo -e "${RED}[fatal]${NC}: cabal executable not found, cannot proceed"; exit
25+
fi
26+
27+
if [ "$(check_installed strip)" == "true" ]; then
28+
strip dist-newstyle/build/x86_64-linux/ghc-*/axolotl-*/x/axl/opt/build/axl/axl
29+
else
30+
echo -e "${YELLOW}[warn]${NC}: 'strip' not found in PATH, not stripping executable"
31+
fi
32+
33+
cp dist-newstyle/build/x86_64-linux/ghc-*/axolotl-*/x/axl/opt/build/axl/axl axl
34+
echo "${GREEN}[success]${NC}: release binary saved to 'axl'!"
35+

example.axl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
(def name (str "hi, " "i am" " udit!"))
2+
(print (str name " nice to meet you"))
3+
4+
(print "this is my programming language, axolotl")
5+
6+
(defun get-given-count [(argument-for-no-reason: int)] {
7+
(+i argument-for-no-reason 1)
8+
})
9+
10+
(def text1 "If I have ")
11+
(def text2 " apples to everyone, i'll be left with ")
12+
13+
(defun arbitrary-function [(initial: int)] {
14+
(print "expressions are written in braces")
15+
(print "and the last value is returned!")
16+
(print (str text1 (+i initial 2) " apples and I give " (get-given-count 2) text2 (/f (+i initial 2) (get-given-count 4)) " apples!"))
17+
420
18+
})
19+
20+
(def initial 37)
21+
22+
(print (arbitrary-function initial))
23+

hie.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
cradle:
2+
cabal:
3+
- path: "src/Main.hs"
4+
component: "axolotl:exe:axl"
5+
6+
- path: "src/Parser/Ast.hs"
7+
component: "axolotl:exe:axl"
8+
9+
- path: "src/Parser/Combinators.hs"
10+
component: "axolotl:exe:axl"
11+
12+
- path: "src/Parser/Parser.hs"
13+
component: "axolotl:exe:axl"
14+
15+
- path: "src/Analyser/Analyser.hs"
16+
component: "axolotl:exe:axl"
17+
18+
- path: "src/Analyser/Util.hs"
19+
component: "axolotl:exe:axl"
20+
21+
- path: "src/Evaluator/Evaluator.hs"
22+
component: "axolotl:exe:axl"
23+
24+
- path: "src/Evaluator/NativeFns.hs"
25+
component: "axolotl:exe:axl"

src/Analyser/Analyser.hs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
module Analyser.Analyser where
2+
3+
import Analyser.Util
4+
( Def (Argument, Function, Variable),
5+
GDefs,
6+
LDefs,
7+
getTypeFromArr,
8+
getTypeFromExpr,
9+
rFoldl,
10+
tfst,
11+
tsnd,
12+
tthd,
13+
)
14+
import Data.Bifunctor (second)
15+
import Data.Either.Combinators (fromLeft', fromRight, fromRight', isLeft, maybeToRight)
16+
import Data.HashMap.Strict as H (HashMap, empty, findWithDefault, fromList, insert, lookup, union)
17+
import Data.Maybe (fromJust, fromMaybe, isJust)
18+
import Data.Text as T (Text, empty, pack, toLower, unpack)
19+
import Debug.Trace (trace)
20+
import Parser.Ast
21+
( Expr (Array, FunctionCall, FunctionDef, Nil, Root, Variable, VariableDef),
22+
VDataType (Function, Inferred, NilType),
23+
)
24+
25+
{-
26+
semCheckExprs should be used to fold over a list of expressions,
27+
with the final result being (globalDefs, localDefs, inferredTree)
28+
29+
globalDefs here is a hashmap - (DefName, Expr), simple enough
30+
localDefs is a hashmap - (scopeName, hashmap (DefName, Expr))
31+
32+
so if you have two functions called a and b, and you defined a
33+
variable age=20 in a and name="udit" in b, you'll have localDefs as
34+
[
35+
[ "a", [ ("age", IntLiteral 20) ] ]
36+
[ "b", [ ("name", StringLiteral "udit") ] ]
37+
]
38+
39+
inferredTree is the expr array you passed it with all Inferred
40+
in it's tree replaced with actual types inferred from context
41+
42+
semCheckExprs calls inferType for every expr in the expr array you give it
43+
initially, which in turn in most cases calls getTypeFromExpr
44+
45+
I use semCheckExprs for AST Root (just array of all expr in program)
46+
and function bodies here, but it can be used anywhere you want to
47+
infer types and analyse a set of expressions
48+
-}
49+
type Accumulator = (GDefs, LDefs, [Either Text Expr])
50+
51+
{-
52+
Cases where a type-check is necessary:
53+
* variable definition when the type is explicitly defined
54+
* function definition when the return type is explicitly defined
55+
* function call (whether all arguments confirm to needed types)
56+
* array generation (whether all arguments confirm to needed type)
57+
58+
note that if the type of an array is explicitly defined, every
59+
element in the array must have the same type, and in case the
60+
type is _not_ explicitly defined, every element in the array
61+
must have the same type as the first element in the array
62+
-}
63+
makeDtArr :: Accumulator -> [Expr] -> Either Text [VDataType]
64+
makeDtArr acc = mapM (`getTypeFromExpr` tfst acc)
65+
66+
checkArgs :: [VDataType] -> Either Text [VDataType] -> Text -> Maybe Text
67+
checkArgs expArgs vdtArgs fnName = do
68+
case vdtArgs of
69+
Left txt -> Just txt
70+
Right vdts -> snd $
71+
rFoldl (zip expArgs vdts) (0, Nothing) $ \acc curr ->
72+
( fst acc + 1,
73+
if uncurry (==) curr
74+
then Nothing
75+
else
76+
Just $
77+
"Expected argument of type '"
78+
<> (toLower . T.pack . show) (fst curr)
79+
<> "' but got '"
80+
<> (toLower . T.pack . show) (snd curr)
81+
<> "' in argument "
82+
<> T.pack (show (fst acc + 1))
83+
<> T.pack " of call to function "
84+
<> T.pack (show fnName)
85+
)
86+
87+
semCheckExprs :: (Accumulator -> Expr -> Accumulator)
88+
semCheckExprs acc curr = do
89+
let makeLeft r = (H.empty, H.empty, [Left r])
90+
if not (null (tthd acc)) && isLeft (last (tthd acc))
91+
then (H.empty, H.empty, [last (tthd acc)])
92+
else case inferType curr (tfst acc) of
93+
Left err -> (tfst acc, tsnd acc, tthd acc <> [Left err])
94+
-- if it's a def, add to a1 or a2, else just add expr to a3
95+
Right infExpr -> case infExpr of
96+
Array exprs -> do
97+
-- since inferType evaluated to Right, this exists
98+
let at = getTypeFromArr $ fromRight' $ getTypeFromExpr infExpr (tfst acc)
99+
let mapped = mapM (`getTypeFromExpr` tfst acc) exprs
100+
case mapped of
101+
Left txt -> makeLeft txt
102+
Right mvdts -> do
103+
let res = rFoldl exprs (0, Nothing) $ \acc' curr -> do
104+
let et = getTypeFromExpr curr (tfst acc)
105+
let ni = fst acc' + 1
106+
if isJust (snd acc')
107+
then (ni, snd acc')
108+
else case et of
109+
Left txt -> (ni, Just txt)
110+
Right avdt ->
111+
if avdt == at
112+
then (ni, Nothing)
113+
else
114+
( ni,
115+
Just $
116+
"Expected type '"
117+
<> (toLower . T.pack . show) at
118+
<> "' but got '"
119+
<> (toLower . T.pack . show) avdt
120+
<> "' in index "
121+
<> T.pack (show (fst acc'))
122+
<> " of array literal"
123+
)
124+
maybe (tfst acc, tsnd acc, tthd acc <> [Right infExpr]) makeLeft (snd res)
125+
FunctionCall name args -> do
126+
-- since inferType evaluated to Right, this exists
127+
let def = fromJust $ H.lookup name (tfst acc)
128+
-- (def arg-1 arg-2 ...)
129+
case def of
130+
Analyser.Util.Variable v _ -> case v of
131+
Parser.Ast.Function expArgs _ ->
132+
maybe
133+
(tfst acc, tsnd acc, tthd acc <> [Right infExpr])
134+
makeLeft
135+
(checkArgs expArgs (makeDtArr acc args) name)
136+
x -> makeLeft $ "Variable of type '" <> pack (show x) <> "' is not callable"
137+
Analyser.Util.Function vdt expArgs _ frgn ->
138+
maybe
139+
(tfst acc, tsnd acc, tthd acc <> [Right infExpr])
140+
makeLeft
141+
(checkArgs (map snd expArgs) (makeDtArr acc args) name)
142+
Analyser.Util.Argument vdt -> undefined -- TODO
143+
VariableDef name vtype expr -> case H.lookup name (tfst acc) of
144+
Nothing -> do
145+
let res =
146+
( insert name (Analyser.Util.Variable vtype expr) (tfst acc),
147+
tsnd acc,
148+
tthd acc <> [Right infExpr]
149+
)
150+
if vtype /= Inferred
151+
then do
152+
let atype = getTypeFromExpr expr (tfst acc)
153+
case atype of
154+
Left txt -> makeLeft txt
155+
Right vdt ->
156+
if vdt == vtype
157+
then res
158+
else
159+
makeLeft $
160+
"Cannot assign value of type "
161+
<> T.pack (show vdt)
162+
<> " to variable of type "
163+
<> T.pack (show vtype)
164+
else res
165+
Just _ -> (H.empty, H.empty, [Left $ "Redefinition of variable " <> name])
166+
FunctionDef name vtype args body frgn -> case H.lookup name (tfst acc) of
167+
Just _ -> (H.empty, H.empty, [Left $ "Redefinition of function " <> name])
168+
Nothing -> do
169+
let result =
170+
foldl
171+
semCheckExprs
172+
(tfst acc `union` H.fromList (map (second Argument) args), H.empty, [])
173+
body
174+
let r =
175+
getTypeFromExpr
176+
(if null body then Nil else last body)
177+
(tfst result `union` tfst acc)
178+
let inferred = vtype == Inferred
179+
case r of
180+
Left txt -> makeLeft txt
181+
Right dvdt -> do
182+
let res =
183+
( insert name (Analyser.Util.Function (if inferred then fromRight' r else vtype) args body frgn) (tfst acc),
184+
insert name (tfst result) (tsnd acc),
185+
tthd acc
186+
<> [ sequence (tthd result) >>= \v ->
187+
r
188+
>>= \ct -> Right $ FunctionDef name ct args v frgn
189+
]
190+
)
191+
if not inferred
192+
then
193+
if vtype == dvdt
194+
then res
195+
else
196+
makeLeft $
197+
"Expected function '"
198+
<> name
199+
<> "' to return "
200+
<> (toLower . T.pack . show) vtype
201+
<> ", instead got "
202+
<> (toLower . T.pack . show) dvdt
203+
else res
204+
_ -> (tfst acc, tsnd acc, tthd acc <> [Right infExpr])
205+
206+
inferType :: Expr -> GDefs -> Either Text Expr
207+
inferType (Root x) gd = error "fold with semCheckExprs for this"
208+
-- handle variable definition inside variable definition
209+
inferType (VariableDef name x VariableDef {}) _ =
210+
Left "Cannot define a variable inside a variable"
211+
-- infer types for proper variable definitions
212+
inferType (VariableDef name Inferred y) gd =
213+
getTypeFromExpr y gd >>= \t -> Right $ VariableDef name t y
214+
-- infer function call types
215+
inferType (FunctionCall name args) gd =
216+
getTypeFromExpr (FunctionCall name args) gd >>= \t ->
217+
Right $ FunctionCall name args
218+
-- send back nodes that don't need type inference
219+
inferType x _ = Right x
220+
221+
analyseAst :: Expr -> GDefs -> (Either Text Expr, GDefs, LDefs)
222+
analyseAst (Root x) gd = do
223+
let t = foldl semCheckExprs (gd, H.empty, []) x
224+
(sequence (tthd t) >>= \v -> Right (Root v), tfst t, tsnd t)
225+
analyseAst _ _ = undefined
226+
227+
analyseAst' :: Expr -> Either Text Expr
228+
analyseAst' (Root x) = do
229+
let t = foldl semCheckExprs (H.empty, H.empty, []) x
230+
sequence (tthd t) >>= \v -> Right (Root v)
231+
analyseAst' _ = undefined

0 commit comments

Comments
 (0)