├── .envrc ├── .gitattributes ├── .github └── workflows │ └── web.yaml ├── .gitignore ├── README.md ├── cabal.project ├── examples ├── fibonacci.glam ├── primes.glam └── y.glam ├── flake.lock ├── flake.nix ├── glam ├── Glam │ ├── Rules │ │ ├── Term.hs │ │ └── Type.hs │ ├── Run.hs │ ├── Term.hs │ ├── Type.hs │ └── Utils.hs ├── LICENSE ├── Main.hs ├── MainJS.hs └── glam.cabal └── web ├── favicon.ico ├── glam_syntax.js └── index.html /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.glam linguist-language=Idris 2 | -------------------------------------------------------------------------------- /.github/workflows/web.yaml: -------------------------------------------------------------------------------- 1 | name: web 2 | on: [push, pull_request, workflow_dispatch] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - uses: cachix/install-nix-action@v20 9 | with: 10 | extra_nix_config: | 11 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 12 | extra-substituters = https://nix.monade.li 13 | extra-trusted-public-keys = nix.monade.li:2Zgy59ai/edDBizXByHMqiGgaHlE04G6Nzuhx1RPFgo= 14 | - name: Build 15 | run: | 16 | web=$(nix -L build .#web --print-out-paths) 17 | cp -rL --no-preserve=mode,ownership,timestamps "$web" pages 18 | - uses: actions/upload-pages-artifact@v1 19 | with: 20 | path: pages 21 | retention-days: 1 22 | deploy: 23 | if: github.ref_name == 'main' 24 | needs: build 25 | permissions: 26 | pages: write 27 | id-token: write 28 | environment: 29 | name: github-pages 30 | url: ${{ steps.deployment.outputs.page_url }} 31 | runs-on: ubuntu-latest 32 | steps: 33 | - id: deployment 34 | uses: actions/deploy-pages@v2 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist* 2 | .glam_history 3 | result* 4 | .direnv 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glam 2 | 3 | **glam** is an implementation of the guarded λ-calculus, as described in the paper [The Guarded Lambda-Calculus: Programming and Reasoning with Guarded Recursion for Coinductive Types](https://arxiv.org/abs/1606.09455) by Ranald Clouston, Aleš Bizjak, Hans Bugge Grathwohl and Lars Birkedal, and extended with polymorphism and automatic boxing as described in my [internship report](https://monade.li/glam.pdf). 4 | 5 | Please refer to those papers for basic motivation and introduction to the language, as well as a description of its type system, operational and denotational semantics. This README only covers the specifics of my implementation. 6 | 7 | An [online demo](https://glam.monade.li) is available, as well as [rendered documentation](https://glam.monade.li/doc). 8 | 9 | - [Usage](#usage) 10 | - [Syntax](#syntax) 11 | - [Types](#types) 12 | - [Terms](#terms) 13 | - [Programs](#programs) 14 | - [Evaluation](#evaluation) 15 | - [Type system](#type-system) 16 | - [Polymorphism](#polymorphism) 17 | - [Automatic boxing](#automatic-boxing) 18 | - [To do](#to-do) 19 | 20 | ## Usage 21 | 22 | This project is built using Cabal (`cabal build`). 23 | 24 | ``` 25 | usage: glam [options...] files... 26 | -i --interactive run in interactive mode (default if no files are provided) 27 | ``` 28 | 29 | The interactive mode gives you a REPL that will execute statements and display their results. It also provides a `:type` command that displays the type of a given term. 30 | 31 | ## Syntax 32 | 33 | **glam**'s syntax is intended to be similar to Haskell's. See the `examples` directory for example programs. 34 | 35 | ### Types 36 | 37 | The syntax for types is as follows: 38 | 39 | ``` 40 | TVar = [a-zA-Z_] [a-zA-Z_0-9']* ; (excluding keywords) 41 | 42 | Type = "(" Type ")" 43 | | TVar ; type variables 44 | | TVar Type* ; application of type synonyms 45 | | "Int" ; integer type 46 | | "0" ; zero/void/initial type 47 | | "1" ; unit/terminal type 48 | | Type "*" Type ; product types 49 | | Type "+" Type ; coproduct/sum types 50 | | Type "->" Type ; function types 51 | | ">" Type ; Later types 52 | | "#" Type ; Constant types 53 | | "Fix" TVar "." Type ; fixed point types 54 | 55 | TypeDef = "type" TVar TVar* "=" Type ; type synonyms 56 | 57 | Polytype = ("forall" ("#"? TVar)+ ".")? Type 58 | ``` 59 | 60 | `*`, `+` and `->` are right-associative. Unicode syntax is supported (`μ`, `∀`, `→`, `×`, `⊤`, `⊥`, `ℤ`, `▸`, `■`). 61 | 62 | Some syntactic sugar is provided: 63 | 64 | | Construct | Desugars to | 65 | | --- | --- | 66 | | `type T x y z = ... (T x y z) ...` | `type T x y z = Fix T. ... T ...` | 67 | 68 | Due to the absence of type-level lambdas, type synonyms must be applied to exactly as many arguments as they expect. When using a type synonym recursively inside its own definition, it must be applied to its exact formal arguments. 69 | 70 | ### Terms 71 | 72 | The syntax for terms is as follows: 73 | 74 | ``` 75 | Var = [a-zA-Z_] [a-zA-Z_0-9']* ; (excluding keywords) 76 | Integer = [0-9]+ 77 | Binary = "+" | "-" | "*" | "/" | "<$>" | "<*>" 78 | Unary = "fst" | "snd" 79 | | "abort" | "left" | "right" 80 | | "fold" | "unfold" 81 | | "next" | "prev" 82 | | "box" | "unbox" 83 | 84 | Term = "(" Term ")" 85 | | Var ; variables 86 | | Integer ; integers 87 | | "intrec" ; integer recursion operator (forall a. (a -> a) -> a -> (a -> a) -> Int -> a) 88 | | "(" ","-separated(Term+) ")" ; tuples 89 | | "(" ")" ; unit 90 | | Term Term ; applications 91 | | Term Binary Term ; binary operators 92 | | Unary Term ; unary operators 93 | | "\" Var+ "." Term ; λ-abstractions 94 | | "fix" Var+ "." Term ; fixed points 95 | | "let" "{" ";"-separated(TermDef) "}" ; let expressions 96 | "in" Term 97 | | "case" Term "of" "{" ; case expressions 98 | "left" Var "." Term ";" 99 | "right" Var "." Term "}" 100 | 101 | TermDef = Var Var* "=" Term 102 | ``` 103 | 104 | Unicode syntax is supported (`λ`, `⊛`). 105 | 106 | Some syntactic sugar is provided: 107 | 108 | | Construct | Desugars to | 109 | | --- | --- | 110 | | `f x y z = t` | `f = \x y z. t` | 111 | | `f = ... f ...` | `f = fix f. ... f ...` | 112 | | `(a, b, c, ...)` | `(a, (b, (c, ...)))` | 113 | | `\x y z. t` | `\x. \y. \z. t` | 114 | | `fix x y z. t` | `fix x. \y z. t` | 115 | | `f <$> x` | `next f <*> x` | 116 | 117 | ### Programs 118 | 119 | **glam** programs are structured as follows: 120 | 121 | ``` 122 | Signature = Var ":" Polytype 123 | 124 | Statement = TypeDef 125 | | Signature 126 | | TermDef 127 | | Term 128 | 129 | Program = newline-separated(Statement) 130 | ``` 131 | 132 | Statements can span over multiple lines, provided that subsequent lines are indented further than the first line. 133 | 134 | Type signatures and term definitions don't have to appear consecutively, but signatures have to appear *before* definitions. 135 | 136 | Type signatures can be omitted; a most general type will then be inferred. In practice, most signatures can be omitted, except for terms involving `fold` and `unfold`. 137 | 138 | The interpreter currently prints the (inferred or checked) type for each top-level term definition, as well as the value and inferred type of top-level terms. 139 | 140 | ## Evaluation 141 | 142 | Terms are evaluated using a call-by-need strategy (piggy-backing on Haskell's), based on the operational semantics given in the paper. 143 | This is a form of normalisation by evaluation (NbE), except we don't reify values back into terms. 144 | 145 | ## Type system 146 | 147 | ### Polymorphism 148 | 149 | **glam** extends the guarded λ-calculus with predicative, rank-1 polymorphism à la Hindley-Milner. 150 | 151 | Polymorphic types (or *polytypes*) are of the form `forall a #b. ...`, where `a` refers to any type, while `b` refers to any *constant* type. 152 | 153 | To allow for interesting polymorphic types involving the constant (`#`) modality, the definition of **valid types** has been modified to allow polymorphic type variables to appear under `#`. 154 | 155 | The definition of **constant types** has also been modified as follows: 156 | 157 | - `x` is a constant type if and only if `x` is bound by `forall #x` 158 | - `0`, `1`, `#T` are constant types 159 | - `>T` is not a constant type 160 | - `T1 * T2`, `T1 + T2` are constant if and only if `T1` and `T2` are both constant 161 | - `T1 -> T2` is constant if and only if `T2` is constant¹ 162 | - `Fix x. T` is constant if and only if `T` is constant 163 | 164 | ¹ This is also more permissive than the paper's definition; this modification is based on semantic considerations. 165 | 166 | Just like in standard Hindley-Milner, polymorphic generalisation only occurs for `let`-bound terms (but not inside recursive definitions). 167 | 168 | ### Automatic boxing 169 | 170 | Consider the following motivating examples: 171 | 172 | - The term `let { z = consG 0 z } in box z` (where `consG` is the constructor for guarded recursive streams) should type-check, because `box (fix z. consG 0 z)` does; but the typing rules given by the paper prohibit this because `z` has the non-constant type `Fix s. Int * >s`. 173 | - The **box⁺** term former introduced in the paper is inelegant. We would like to be able to define such a construct internally: 174 | ``` 175 | box' : forall a b. #(a + b) -> #a + #b 176 | box' ab = case unbox ab of { 177 | left a. left box a; 178 | right b. right box b } 179 | ``` 180 | Again, this would not be allowed, because `a` and `b` have the potentially non-constant types `a` and `b`. 181 | 182 | We solve these two problems simultaneously by introducing (context-dependent) notions of **boxable terms** and **boxed variables**, defined as follows: 183 | - A term is boxable if each of its free variables is either boxed or has a constant type. 184 | - A variable that is `let`-bound to a boxable term is automatically boxed (this includes top-level bindings; as a consequence, all top-level bound variables are boxed). Similarly, a variable bound in a `case` expression from matching on a boxable term is boxed. 185 | 186 | The `box t` and `prev t` constructs require `t` to be boxable. This makes the examples above type-check as expected. 187 | 188 | ## To do 189 | 190 | - Infer types for `fold` and `unfold` via higher-orderish unification. This should type-check: 191 | ``` 192 | type CoNat = 1 + >CoNat 193 | rec : forall a. a -> (>a -> a) -> CoNat -> a 194 | rec z s = let { go n = case unfold n of { left _. z; right m. s (go <*> m) } } in go 195 | ``` 196 | (Note that it does if you use `fix go.`) 197 | - Better type error reporting. 198 | - Make semicolons and braces optional using something like Haskell's layout rules. 199 | - Add infix operators. 200 | - Make `left`, `right`, `abort`, `fst`, `snd`, `(,)` (the pair former), `fix`, `next`, `(<$>)`, `(<*>)` and `unbox` first-class functions instead of keywords. 201 | - Proper type constructors and pattern matching. 202 | - Add basic types and operations (booleans, characters, strings...), and some sort of standard library for dealing with streams, colists, ... 203 | - Add syntactic sugar to make recursive definitions less painful to write and read: idiom brackets, Idris-style `!(notation)`, something. 204 | - More generally turn **glam** into a usable and useful programming language. 205 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: glam 2 | -------------------------------------------------------------------------------- /examples/fibonacci.glam: -------------------------------------------------------------------------------- 1 | -- Guarded recursive streams 2 | type StreamG a = a * >StreamG a 3 | 4 | consG : forall a. a -> >StreamG a -> StreamG a 5 | consG x s = fold (x, s) 6 | 7 | headG : forall a. StreamG a -> a 8 | headG s = fst unfold s 9 | 10 | tailG : forall a. StreamG a -> >StreamG a 11 | tailG s = snd unfold s 12 | 13 | zipWithG f = fix go s1 s2. consG (f (headG s1) (headG s2)) (go <*> tailG s1 <*> tailG s2) 14 | 15 | -- Coinductive streams 16 | type Stream a = #StreamG a 17 | 18 | cons x s = box (consG x (next unbox s)) 19 | head s = headG unbox s 20 | tail s = box prev (tailG unbox s) 21 | nth n s = head (intrec (\_. s) s tail n) 22 | 23 | -- The Fibonacci sequence 24 | fibG = consG 0 ((\f. consG 1 (zipWithG (\x y. x + y) f <$> tailG f)) <$> fibG) 25 | fib = box fibG 26 | 27 | -- Print the first five Fibonacci numbers 28 | head fib 29 | head (tail fib) 30 | head (tail (tail fib)) 31 | head (tail (tail (tail fib))) 32 | head (tail (tail (tail (tail fib)))) 33 | 34 | -- Print the 100th Fibonacci number 35 | nth 100 fib 36 | -------------------------------------------------------------------------------- /examples/primes.glam: -------------------------------------------------------------------------------- 1 | -- Guarded recursive streams 2 | type StreamG a = a * >StreamG a 3 | 4 | consG : forall a. a -> >StreamG a -> StreamG a 5 | consG x s = fold (x, s) 6 | 7 | headG : forall a. StreamG a -> a 8 | headG s = fst unfold s 9 | 10 | tailG : forall a. StreamG a -> >StreamG a 11 | tailG s = snd unfold s 12 | 13 | mapG f = fix go s. consG (f (headG s)) (go <*> tailG s) 14 | 15 | scanl1G f = fix go s. consG (headG s) (mapG (f (headG s)) <$> (go <*> tailG s)) 16 | 17 | -- Coinductive streams 18 | type Stream a = #StreamG a 19 | 20 | cons x s = box (consG x (next unbox s)) 21 | head s = headG unbox s 22 | tail s = box prev (tailG unbox s) 23 | 24 | -- An infinite stream of prime numbers, as used in Euclid's proof of infinitude 25 | primesG = consG 2 (mapG (\x. x + 1) <$> (scanl1G (\x y. x * y) <$> primesG)) 26 | primes = box primesG 27 | 28 | -- Print the first five primes in the sequence 29 | head primes 30 | head (tail primes) 31 | head (tail (tail primes)) 32 | head (tail (tail (tail primes))) 33 | head (tail (tail (tail (tail primes)))) 34 | -------------------------------------------------------------------------------- /examples/y.glam: -------------------------------------------------------------------------------- 1 | -- A guarded variant of Curry's Y combinator. 2 | -- Alternatively, a proof of Löb's theorem for the "later" modality. 3 | 4 | type Mu a = >Mu a -> a 5 | 6 | Mu : forall a. (>Mu a -> a) -> Mu a 7 | Mu x = fold x 8 | 9 | uM : forall a. Mu a -> >Mu a -> a 10 | uM x = unfold x 11 | 12 | Y : forall a. (>a -> a) -> a 13 | Y f = let { x x' = f (uM <$> x' <*> next x') } 14 | in x (next (Mu x)) 15 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1683777345, 6 | "narHash": "sha256-V2p/A4RpEGqEZussOnHYMU6XglxBJGCODdzoyvcwig8=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "635a306fc8ede2e34cb3dd0d6d0a5d49362150ed", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "ref": "nixpkgs-unstable", 15 | "type": "indirect" 16 | } 17 | }, 18 | "root": { 19 | "inputs": { 20 | "nixpkgs": "nixpkgs" 21 | } 22 | } 23 | }, 24 | "root": "root", 25 | "version": 7 26 | } 27 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "nixpkgs/nixpkgs-unstable"; 3 | 4 | outputs = { self, nixpkgs }: let 5 | examples = [ 6 | { name = "fibonacci"; description = "Fibonacci sequence"; } 7 | { name = "primes"; description = "Prime numbers"; } 8 | { name = "y"; description = "Y combinator"; } 9 | ]; 10 | 11 | inherit (nixpkgs) lib; 12 | system = "x86_64-linux"; 13 | haskellOverlay = self: super: { 14 | haskell = super.haskell // { 15 | packageOverrides = hself: hsuper: { 16 | glam = self.haskell.lib.overrideCabal (hself.callCabal2nix "glam" ./glam {}) { 17 | doHaddock = !(hself.ghc.isGhcjs or false); 18 | isLibrary = true; # otherwise doHaddock does nothing 19 | haddockFlags = [ "--all" "--html-location='https://hackage.haskell.org/package/$pkg-$version/docs'" ]; 20 | }; 21 | }; 22 | }; 23 | }; 24 | pkgs = import nixpkgs { 25 | inherit system; 26 | overlays = [ haskellOverlay ]; 27 | }; 28 | hpkgs = pkgs.haskell.packages.ghc94; 29 | in { 30 | packages.${system} = rec { 31 | default = glam; 32 | glam = hpkgs.glam; 33 | glam-js = pkgs.haskell.packages.ghcjs.glam; 34 | 35 | glam-min-js = pkgs.runCommand "glam.min.js" { 36 | nativeBuildInputs = with pkgs; [ closurecompiler ]; 37 | glam = "${glam-js}/bin/glam.jsexe"; 38 | } '' 39 | closure-compiler -O advanced -W quiet --jscomp_off undefinedVars \ 40 | --externs "$glam/all.js.externs" --js "$glam/all.js" --js_output_file "$out" 41 | ''; 42 | 43 | web = pkgs.runCommandLocal "glam-web" { 44 | examples = lib.concatMapStrings ({ name, description }: '' 45 | 48 | '') examples; 49 | scripts = '' 50 | 51 | 52 | ''; 53 | } '' 54 | mkdir -p "$out" 55 | cp -rT ${./web} "$out" 56 | ln -s ${glam.haddockDir glam}/glam "$out/doc" 57 | ln -s ${glam-min-js} "$out/glam.min.js" 58 | substituteAllInPlace "$out/index.html" 59 | ''; 60 | }; 61 | 62 | devShells.${system}.default = hpkgs.shellFor { 63 | packages = ps: with ps; [ glam self.packages.${system}.glam-js ]; 64 | nativeBuildInputs = with pkgs; [ 65 | haskell.compiler.ghcjs 66 | cabal-install 67 | haskell-language-server 68 | ]; 69 | }; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /glam/Glam/Rules/Term.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | -- | The rules for type checking and type inference. 3 | module Glam.Rules.Term where 4 | 5 | import Data.Traversable 6 | import Data.Map (Map) 7 | import Data.Map qualified as Map 8 | import Data.Set qualified as Set 9 | import Control.Monad 10 | import Control.Monad.Except 11 | import Control.Monad.Reader 12 | import Control.Monad.State 13 | import Control.Monad.Trans.Maybe 14 | import Control.Lens hiding (Fold) 15 | import Text.Megaparsec 16 | 17 | import Glam.Utils 18 | import Glam.Term 19 | import Glam.Type 20 | 21 | data Environment = Environment 22 | { _termCtx :: Map Var (Polytype, Constancy) -- ^ The term context 23 | , _typeCtx :: [(TVar, Constancy)] -- ^ The type context 24 | } 25 | 26 | makeLenses ''Environment 27 | 28 | -- | A metavariable: a placeholder for a type to be determined later (or generalised over). 29 | data Meta = Meta 30 | { _solution :: Maybe Type -- ^ The metavariable's solution 31 | , _constant :: Constancy 32 | , _level :: Int -- ^ The de Bruijn level at which this metavariable was created. 33 | -- Solutions may not contain variables introduced later, as they would escape their scope. 34 | -- An invariant is that (currently) fixed point variables are never in scope of a metavariable, so we 35 | -- don't have to worry about substitution. 36 | } 37 | 38 | makeLenses ''Meta 39 | 40 | data UnificationState = UnificationState 41 | { _metas :: Map TVar Meta -- ^ Metavariables 42 | , _tvars :: [TVar] -- ^ A stream of fresh type variables 43 | } 44 | 45 | makeLenses ''UnificationState 46 | 47 | type MonadCheckTerm m = (MonadState UnificationState m, MonadReader Environment m, MonadError String m) 48 | 49 | runUnification a xs = evalStateT a $ UnificationState mempty (freshTVarsFor xs) 50 | 51 | -- | Case split on whether a type variable is bound or is a metavariable. 52 | -- Note that since we use the same constructor for both, conflicts are possible. 53 | -- In that case, we prefer bound type variables. This is fragile and probably buggy. 54 | viewTVar :: MonadCheckTerm m => TVar -> m (Either Meta Constancy) 55 | viewTVar x = maybe (throwError $ "unbound type variable " ++ x) pure =<< runMaybeT ( 56 | Right <$> MaybeT (lookup x <$> view typeCtx) <|> Left <$> MaybeT (use (metas.at x))) 57 | 58 | ifMeta :: MonadCheckTerm m => Type -> ((TVar, Meta) -> m ()) -> m () -> m () 59 | ifMeta (TVar x) y n = maybe n (curry y x) =<< use (metas.at x) 60 | ifMeta _ _ n = n 61 | 62 | freshTVar :: MonadCheckTerm m => m TVar 63 | freshTVar = head <$> (tvars <<%= tail) 64 | 65 | -- | Create a new metavariable. 66 | newMeta' :: MonadCheckTerm m => Constancy -> m Type 67 | newMeta' c = do 68 | x <- freshTVar 69 | l <- length <$> view typeCtx 70 | metas.at x ?= Meta Nothing c l 71 | pure (TVar x) 72 | -- | Create a new not-necessarily-constant metavariable. 73 | newMeta = newMeta' False 74 | newMetas n = replicateM n newMeta 75 | 76 | class Zonk t where 77 | -- | Recursively replace solved metavariables in a type with their solutions. 78 | zonk :: MonadCheckTerm m => t -> m t 79 | 80 | instance Zonk Type where 81 | zonk ty@(TVar x) = viewTVar x >>= \case 82 | Left m | Just sol <- m ^. solution -> zonk sol 83 | _ -> pure ty 84 | zonk (a :*: b) = (:*:) <$> zonk a <*> zonk b 85 | zonk (a :+: b) = (:+:) <$> zonk a <*> zonk b 86 | zonk (a :->: b) = (:->:) <$> zonk a <*> zonk b 87 | zonk (Later t) = Later <$> zonk t 88 | zonk (Constant t) = Constant <$> zonk t 89 | zonk (TFix x t) = typeCtx %~ ((x, False):) |- TFix x <$> zonk t 90 | zonk ty = pure ty 91 | 92 | instance Zonk Polytype where 93 | zonk (Forall xs ty) = typeCtx %~ (xs ++) |- Forall xs <$> zonk ty 94 | 95 | -- | Instantiate a polytype to a type by replacing polymorphic type variables with fresh metavariables. 96 | instantiate :: MonadCheckTerm m => Polytype -> m Type 97 | instantiate (Monotype ty) = pure ty 98 | instantiate (Forall xs ty) = do 99 | s <- for xs \(x, c) -> (x,) <$> newMeta' c 100 | pure $ substituteType (Map.fromList s) ty 101 | 102 | -- | Generalise a type to a polytype by abstracting over its metavariables that aren't free in the context. 103 | generalise :: MonadCheckTerm m => Type -> m Polytype 104 | generalise ty = do 105 | ty <- zonk ty 106 | freeInCtx <- fmap (foldMap freeTVars) . traverse (zonk . fst) =<< view termCtx 107 | metas <- use metas 108 | let generalisable = metas `Map.restrictKeys` freeTVars ty `Map.withoutKeys` freeInCtx 109 | pure $ Forall [(x, m ^. constant) | (x, m) <- Map.toList generalisable] ty 110 | 111 | class Constant t where 112 | -- | Check whether a type or term is constant, optionally forcing it to be by marking its metavariables as constant. 113 | isConstant :: MonadCheckTerm m => Bool -> t -> m Constancy 114 | 115 | -- | A type is constant if all uses of @▸@ occur under @■@. 116 | instance Constant Type where 117 | isConstant force (TVar x) = viewTVar x >>= \case 118 | Left m | Just sol <- m ^. solution -> 119 | if m ^. constant then pure True else isConstant force sol 120 | | force -> metas.ix x.constant <.= True 121 | | otherwise -> pure (m ^. constant) 122 | Right c -> c <$ when (force && not c) (throwError $ "non-constant type variable " ++ x) 123 | isConstant force (t1 :*: t2) = (&&) <$> isConstant force t1 <*> isConstant force t2 124 | isConstant force (t1 :+: t2) = (&&) <$> isConstant force t1 <*> isConstant force t2 125 | isConstant force (_ :->: t2) = isConstant force t2 126 | isConstant force ty@Later{} 127 | | force = throwError $ "non-constant type " ++ show ty 128 | | otherwise = pure False 129 | isConstant force (TFix x t) = typeCtx %~ ((x, False):) |- isConstant force t -- if the type is well-formed, we'll never encounter x 130 | isConstant _ _ = pure True 131 | 132 | instance Constant Polytype where 133 | isConstant force (Forall xs ty) = typeCtx %~ (xs ++) |- isConstant force ty 134 | 135 | -- | A term is constant if it only refers to constant terms or terms with a constant type. 136 | instance Constant Term where 137 | isConstant force t = do 138 | ctx <- view termCtx 139 | and <$> traverse constantBinding (ctx `Map.restrictKeys` freeVars t) 140 | where 141 | constantBinding (_, True) = pure True 142 | constantBinding (ty, False) = isConstant force ty 143 | 144 | -- * Unification 145 | 146 | infix 5 !:= 147 | 148 | -- | Attempt to assign a type to a metavariable. 149 | -- Performs occurs check, scope check and constancy check. 150 | (!:=) :: MonadCheckTerm m => (TVar, Meta) -> Type -> m () 151 | (x, meta) !:= ty 152 | | Just sol <- meta ^. solution = sol !~ ty 153 | | otherwise = assign =<< zonk ty where 154 | assign ty 155 | | ty == TVar x = pure () 156 | | x `freeInType` ty = throwError $ "cannot construct infinite type " ++ x ++ " ~ " ++ show ty 157 | | otherwise = do 158 | ctx <- view typeCtx 159 | let escaped = [x | x <- Set.toList (freeTVars ty) 160 | , Just (_, l) <- [lookupLevel x ctx] 161 | , l >= meta ^. level] 162 | unless (null escaped) $ throwError $ 163 | "cannot unify " ++ x ++ " with " ++ show ty ++ ": type variables " ++ show (map TVar escaped) ++ " would escape their scope" 164 | when (meta ^. constant) $ () <$ isConstant True ty 165 | metas.ix x.solution ?= ty 166 | 167 | infix 4 !~ 168 | 169 | -- | Unify two types. 170 | (!~) :: MonadCheckTerm m => Type -> Type -> m () 171 | ta1 :*: tb1 !~ ta2 :*: tb2 = ta1 !~ ta2 >> tb1 !~ tb2 172 | ta1 :+: tb1 !~ ta2 :+: tb2 = ta1 !~ ta2 >> tb1 !~ tb2 173 | ta1 :->: tb1 !~ ta2 :->: tb2 = ta1 !~ ta2 >> tb1 !~ tb2 174 | Later ty1 !~ Later ty2 = ty1 !~ ty2 175 | Constant ty1 !~ Constant ty2 = ty1 !~ ty2 176 | TFix x1 tf1 !~ TFix x2 tf2 = typeCtx %~ ((x1, False):) |- tf1 !~ substituteType1 x2 (TVar x1) tf2 177 | ty1 !~ ty2 = ifMeta ty1 (!:= ty2) $ ifMeta ty2 (!:= ty1) $ unless (ty1 == ty2) do 178 | ty1 <- zonk ty1 179 | ty2 <- zonk ty2 180 | throwError $ "cannot match type " ++ show ty1 ++ " with " ++ show ty2 181 | 182 | -- * Type checking and inference 183 | 184 | -- | The type of the integer recursion operator. 185 | intrecType :: Polytype 186 | intrecType = Forall [("a", False)] (("a" :->: "a") :->: "a" :->: ("a" :->: "a") :->: TInt :->: "a") 187 | 188 | class Types t where 189 | infix 4 !: 190 | 191 | -- | Check that a term has the given type. 192 | (!:) :: MonadCheckTerm m => Term -> t -> m () 193 | 194 | -- | Infer a type for the given term. 195 | (?:) :: MonadCheckTerm m => Term -> m t 196 | 197 | instance Types Type where 198 | Var x !: ty = view (termCtx.at x) >>= \case 199 | Just (tyx, _) -> (ty !~) =<< instantiate tyx 200 | Nothing -> throwError $ "unbound variable " ++ x 201 | Int{} !: ty = ty !~ TInt 202 | Plus a b !: ty = do 203 | a !: TInt 204 | b !: TInt 205 | ty !~ TInt 206 | Minus a b !: ty = do 207 | a !: TInt 208 | b !: TInt 209 | ty !~ TInt 210 | Times a b !: ty = do 211 | a !: TInt 212 | b !: TInt 213 | ty !~ TInt 214 | Divide a b !: ty = do 215 | a !: TInt 216 | b !: TInt 217 | ty !~ TInt 218 | IntRec !: ty = (ty !~) =<< instantiate intrecType 219 | Unit !: ty = ty !~ One 220 | Pair a b !: ty = do 221 | ~[ta, tb] <- newMetas 2 222 | ty !~ ta :*: tb 223 | a !: ta 224 | b !: tb 225 | Fst t !: ty = do 226 | tb <- newMeta 227 | t !: ty :*: tb 228 | Snd t !: ty = do 229 | ta <- newMeta 230 | t !: ta :*: ty 231 | Abort t !: _ = t !: Zero 232 | InL t !: ty = do 233 | ~[ta, tb] <- newMetas 2 234 | ty !~ ta :+: tb 235 | t !: ta 236 | InR t !: ty = do 237 | ~[ta, tb] <- newMetas 2 238 | ty !~ ta :+: tb 239 | t !: tb 240 | Case t ~(Abs x1 t1) ~(Abs x2 t2) !: ty = do 241 | ~[ta, tb] <- newMetas 2 242 | t !: ta :+: tb 243 | constant <- isConstant False t 244 | termCtx.at x1 ?~ (Monotype ta, constant) |- t1 !: ty 245 | termCtx.at x2 ?~ (Monotype tb, constant) |- t2 !: ty 246 | Abs x t !: ty = do 247 | ~[ta, tb] <- newMetas 2 248 | ty !~ ta :->: tb 249 | termCtx.at x ?~ (Monotype ta, False) |- t !: tb 250 | s :$: t !: ty = do 251 | ta <- newMeta 252 | s !: ta :->: ty 253 | t !: ta 254 | Let s t !: ty = do 255 | e <- for s \t' -> do 256 | ty <- (t' ?:) 257 | constant <- isConstant False t' 258 | pure (ty, constant) 259 | termCtx %~ Map.union e |- t !: ty 260 | Fold t !: ty = do 261 | ty <- zonk ty 262 | case ty of 263 | TFix x tf -> t !: substituteType1 x ty tf 264 | _ -> throwError $ "bad type for fold: " ++ show ty 265 | Unfold t !: ty = do 266 | ty' <- zonk =<< (t ?:) 267 | case ty' of 268 | TFix x tf -> ty !~ substituteType1 x ty' tf 269 | _ -> throwError $ "bad type for unfold: " ++ show ty' 270 | Fix ~(Abs x t) !: ty = termCtx.at x ?~ (Monotype (Later ty), False) |- t !: ty 271 | Next t !: ty = do 272 | ta <- newMeta 273 | ty !~ Later ta 274 | t !: ta 275 | Prev t !: ty = do 276 | t !: Later ty 277 | () <$ isConstant True t 278 | s :<*>: t !: ty = do 279 | ~[ta, tb] <- newMetas 2 280 | ty !~ Later ta 281 | t !: Later tb 282 | s !: Later (tb :->: ta) 283 | Box t !: ty = do 284 | ta <- newMeta 285 | ty !~ Constant ta 286 | t !: ta 287 | () <$ isConstant True t 288 | Unbox t !: ty = t !: Constant ty 289 | 290 | (?:) t = do 291 | ty <- newMeta 292 | t !: ty 293 | pure ty 294 | 295 | instance Types Polytype where 296 | t !: Forall xs ty = typeCtx %~ (xs ++) |- t !: ty 297 | 298 | (?:) = (?:) >=> generalise 299 | 300 | checkTerm t ty = runUnification (t !: ty) (allTVars ty) 301 | 302 | inferTerm t = alphaNormalise <$> runUnification (t ?:) mempty 303 | -------------------------------------------------------------------------------- /glam/Glam/Rules/Type.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-# LANGUAGE TypeFamilies #-} 3 | -- | The rules for checking types. 4 | module Glam.Rules.Type where 5 | 6 | import Data.Map (Map) 7 | import Data.Map qualified as Map 8 | import Data.Monoid 9 | import Control.Monad.Except hiding (guard) 10 | import Control.Monad.Reader hiding (guard) 11 | import Control.Monad.Writer hiding (guard) 12 | import Control.Lens 13 | 14 | import Glam.Utils 15 | import Glam.Type 16 | 17 | -- | Things a type variable can refer to 18 | data TVarBinding = 19 | -- | A type synonym (with a list of arguments) 20 | Syn [TVar] Type 21 | -- | A fixed point 22 | | Self { _guardedness :: Guardedness 23 | , arguments :: Maybe [TVar] -- ^ If this is the type synonym we're defining, contains its arguments 24 | } 25 | -- | Anything else 26 | | Bound 27 | 28 | makeLenses ''TVarBinding 29 | 30 | type TEnvironment = Map TVar TVarBinding 31 | 32 | type MonadCheckType m = (MonadReader TEnvironment m, MonadError String m) 33 | 34 | -- | Going under a @▸@ 35 | guard :: Guardedness -> Guardedness 36 | guard Unguarded = Guarded 37 | guard x = x 38 | 39 | -- | Check that a type is well-formed and expand type synonyms. 40 | -- This handles desugaring recursive uses of type synonyms to fixed points: 41 | -- if the current type synonym is used (and applied to the same arguments), 42 | -- the second return value contains the fixed point variable to abstract over. 43 | checkType :: MonadCheckType m => Type -> m (Type, First TVar) 44 | checkType = runWriterT . go where 45 | go ty@TVar{} = apply ty [] 46 | go ty@TApp{} = apply ty [] 47 | go (ta :*: tb) = (:*:) <$> go ta <*> go tb 48 | go (ta :+: tb) = (:+:) <$> go ta <*> go tb 49 | go (ta :->: tb) = (:->:) <$> go ta <*> go tb 50 | go (Later ty) = mapped.guardedness %~ guard |- Later <$> go ty 51 | go (Constant ty) = mapped.guardedness .~ Forbidden |- Constant <$> go ty 52 | go (TFix x tf) = at x ?~ Self Unguarded Nothing |- TFix x <$> go tf 53 | go ty = pure ty 54 | apply (TApp t1 t2) tys = do 55 | t2' <- go t2 56 | apply t1 (t2':tys) 57 | apply (TVar x) tys = view (at x) >>= maybe (throwError $ "unbound type variable " ++ x) \case 58 | Syn ys ty 59 | | length ys == length tys -> pure $ substituteType (Map.fromList (zip ys tys)) ty 60 | | otherwise -> throwError $ 61 | "wrong number of arguments for type constructor " ++ x ++ ": got " ++ 62 | show (length tys) ++ ", expecting " ++ show (length ys) 63 | Self _ (Just ys) | tys /= map TVar ys -> throwError $ 64 | "recursive type constructor " ++ x ++ " must be applied to the same arguments" 65 | Self Unguarded _ -> throwError $ "unguarded fixed point variable " ++ x 66 | Self Forbidden _ -> throwError $ "fixed point variable " ++ x ++ " cannot appear under #" 67 | Self Guarded (Just _) -> TVar x <$ tell (pure x) 68 | Self Guarded Nothing -> pure (TVar x) 69 | Bound | null tys -> pure (TVar x) 70 | | otherwise -> throwError $ "not a type constructor: " ++ x 71 | apply ty _ = throwError $ "not a type constructor: " ++ show ty 72 | 73 | -- | Check a polytype 74 | checkPolytype :: MonadCheckType m => Polytype -> m Polytype 75 | checkPolytype (Forall as ty) = do 76 | let scope = Map.fromList [(a, Bound) | (a, _) <- as] 77 | (ty', _) <- Map.union scope |- checkType ty 78 | pure $ Forall as ty' 79 | 80 | -- | Check a type synonym definition 81 | checkTypeSynonym :: MonadCheckType m => TVar -> [TVar] -> Type -> m Type 82 | checkTypeSynonym x ys ty = do 83 | let scope = Map.fromList $ (x, Self Unguarded (Just ys)) : [(y, Bound) | y <- ys] 84 | (ty', First autofix) <- Map.union scope |- checkType ty 85 | pure $ maybe id TFix autofix ty' 86 | -------------------------------------------------------------------------------- /glam/Glam/Run.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | -- | The glam interpreter 3 | module Glam.Run where 4 | 5 | import Data.Functor.Identity 6 | import Data.Map (Map) 7 | import Data.Map qualified as Map 8 | import Control.Applicative hiding (many, some) 9 | import Control.Monad 10 | import Control.Monad.Except 11 | import Control.Monad.Reader 12 | import Control.Monad.State 13 | import Control.Monad.Writer 14 | import Control.Lens 15 | import Text.Megaparsec hiding (parse) 16 | 17 | import Glam.Utils 18 | import Glam.Term 19 | import Glam.Type 20 | import Glam.Rules.Term 21 | import Glam.Rules.Type 22 | 23 | -- | Program statements 24 | data Statement = TypeDef TVar [TVar] Type -- ^ Type synonym definitions 25 | | Signature Var Polytype -- ^ Type signatures 26 | | Def Var Term -- ^ Definitions 27 | | Eval Term -- ^ Evaluate and print a term 28 | deriving (Eq, Show) 29 | 30 | data GlamState = GlamState { _termBindings :: Map Var (Maybe Value, Polytype) 31 | , _typeBindings :: Map TVar ([TVar], Type) } 32 | 33 | makeLenses ''GlamState 34 | 35 | type MonadGlam = MonadState GlamState 36 | 37 | runGlamT :: Monad m => StateT GlamState m a -> m a 38 | runGlamT a = evalStateT a (GlamState mempty mempty) 39 | 40 | runGlam = runIdentity . runGlamT 41 | 42 | evalTerm :: MonadGlam m => Term -> m Value 43 | evalTerm t = do 44 | s <- Map.mapMaybe fst <$> use termBindings 45 | pure (eval s t) 46 | 47 | withTypes a = do 48 | tys <- use typeBindings 49 | let tenv = Map.fromList [(x, Syn ys ty) | (x, (ys, ty)) <- Map.assocs tys] 50 | runReaderT a tenv 51 | 52 | withTerms a = do 53 | ts <- use termBindings 54 | let env = Environment (Map.mapMaybe (\(t, ty) -> (ty, True) <$ t) ts) mempty 55 | runReaderT a env 56 | 57 | getWords :: MonadGlam m => m [Var] 58 | getWords = liftA2 (<>) (Map.keys <$> use termBindings) 59 | (Map.keys <$> use typeBindings) 60 | 61 | getType :: MonadGlam m => String -> m (Either String Polytype) 62 | getType s = runExceptT do 63 | t <- liftEither $ parse term "" s 64 | withTerms $ inferTerm t 65 | 66 | statement :: Parser Statement 67 | statement = typeDef <|> signature <|> uncurry Def <$> binding <|> Eval <$> term 68 | where 69 | typeDef = TypeDef <$ "type" <*> tVar <*> many tVar <* equal <*> type_ 70 | signature = try (Signature <$> variable <* colon) <*> polytype 71 | 72 | file :: Parser [Statement] 73 | file = whitespace *> many (lineFolded statement) <* eof 74 | 75 | runFile :: MonadGlam m => String -> String -> m (Either String [String]) 76 | runFile name contents = runExceptT $ execWriterT do 77 | cs <- liftEither $ parse file name contents 78 | forM cs runStatement 79 | 80 | runStatement :: (MonadGlam m, MonadWriter [String] m, MonadError String m) => Statement -> m () 81 | runStatement (TypeDef x ys ty) = do 82 | ty' <- withTypes $ checkTypeSynonym x ys ty 83 | typeBindings.at x ?= (ys, ty') 84 | runStatement (Signature x ty) = do 85 | ty' <- withTypes $ checkPolytype ty 86 | termBindings.at x ?= (Nothing, ty') 87 | runStatement (Def x t) = do 88 | ty <- use (termBindings.at x) >>= \case 89 | Just (_, ty) -> ty <$ withTerms (checkTerm t ty) 90 | Nothing -> withTerms $ inferTerm t 91 | t' <- evalTerm t 92 | termBindings.at x ?= (Just t', ty) 93 | tell [x ++ " : " ++ show ty] 94 | runStatement (Eval t) = do 95 | ty <- withTerms $ inferTerm t 96 | t' <- evalTerm t 97 | tell [show t' ++ " : " ++ show (ty :: Polytype)] 98 | -------------------------------------------------------------------------------- /glam/Glam/Term.hs: -------------------------------------------------------------------------------- 1 | -- | Syntax and evaluation of terms. 2 | module Glam.Term where 3 | 4 | import Data.Function 5 | import Data.List 6 | import Data.Map.Lazy (Map) 7 | import Data.Map.Lazy qualified as Map 8 | import Data.Set (Set) 9 | import Data.Set qualified as Set 10 | import Text.Megaparsec 11 | import Control.Monad.Combinators.Expr 12 | 13 | import Glam.Utils 14 | 15 | -- | Variables 16 | type Var = String 17 | 18 | -- | Substitutions 19 | type Subst = Map Var Term 20 | 21 | infixl :<*>: 22 | infixl 9 :$: 23 | 24 | -- | Terms of the guarded λ-calculus 25 | data Term = Var Var -- ^ Variables 26 | | Int Integer | Plus Term Term | Minus Term Term | Times Term Term | Divide Term Term | IntRec -- ^ Integers 27 | | Unit | Pair Term Term | Fst Term | Snd Term -- ^ Products 28 | | Abort Term | InL Term | InR Term | Case Term Term Term -- ^ Sums 29 | | Abs Var Term | Term :$: Term -- ^ Functions 30 | | Let Subst Term -- ^ Let-bindings 31 | | Fold Term | Unfold Term | Fix Term -- ^ Guarded recursion 32 | | Next Term | Prev Term | Term :<*>: Term -- ^ @▸@ operations 33 | | Box Term | Unbox Term -- ^ @■@ operations 34 | deriving Eq 35 | 36 | -- * Free variables 37 | 38 | freeVars :: Term -> Set Var 39 | freeVars (Var x) = Set.singleton x 40 | freeVars (Int _) = mempty 41 | freeVars (Plus t1 t2) = freeVars t1 <> freeVars t2 42 | freeVars (Minus t1 t2) = freeVars t1 <> freeVars t2 43 | freeVars (Times t1 t2) = freeVars t1 <> freeVars t2 44 | freeVars (Divide t1 t2) = freeVars t1 <> freeVars t2 45 | freeVars IntRec = mempty 46 | freeVars Unit = mempty 47 | freeVars (Pair t1 t2) = freeVars t1 <> freeVars t2 48 | freeVars (Fst t) = freeVars t 49 | freeVars (Snd t) = freeVars t 50 | freeVars (Abort t) = freeVars t 51 | freeVars (InL t) = freeVars t 52 | freeVars (InR t) = freeVars t 53 | freeVars (Case t t1 t2) = freeVars t <> freeVars t1 <> freeVars t2 54 | freeVars (Abs x t) = Set.delete x (freeVars t) 55 | freeVars (t1 :$: t2) = freeVars t1 <> freeVars t2 56 | freeVars (Let s t) = foldMap freeVars s <> (freeVars t Set.\\ Map.keysSet s) 57 | freeVars (Fold t) = freeVars t 58 | freeVars (Unfold t) = freeVars t 59 | freeVars (Fix t) = freeVars t 60 | freeVars (Next t) = freeVars t 61 | freeVars (Prev t) = freeVars t 62 | freeVars (t1 :<*>: t2) = freeVars t1 <> freeVars t2 63 | freeVars (Box t) = freeVars t 64 | freeVars (Unbox t) = freeVars t 65 | 66 | freeIn :: Var -> Term -> Bool 67 | x `freeIn` t = x `Set.member` freeVars t 68 | 69 | -- * Evaluation 70 | 71 | -- | Values are basically destructor-free terms, with abstractions represented 72 | -- in a higher-order way. 73 | data Value = VInt !Integer 74 | | VUnit | VPair Value Value 75 | | VInL Value | VInR Value 76 | | VAbs (Value -> Value) 77 | | VFold Value 78 | | VNext Value 79 | | VBox Value 80 | 81 | intrec :: (a -> a) -> a -> (a -> a) -> Integer -> a 82 | intrec p z s = go where 83 | go n = case compare n 0 of 84 | LT -> p (go (succ n)) 85 | EQ -> z 86 | GT -> s (go (pred n)) 87 | 88 | -- | Evaluate a term in the given environment. 89 | eval :: Map Var Value -> Term -> Value 90 | eval s (Var x) = s Map.! x 91 | eval _ (Int i) = VInt i 92 | eval s (Plus t1 t2) = case (eval s t1, eval s t2) of ~(VInt i1, VInt i2) -> VInt (i1 + i2) 93 | eval s (Minus t1 t2) = case (eval s t1, eval s t2) of ~(VInt i1, VInt i2) -> VInt (i1 - i2) 94 | eval s (Times t1 t2) = case (eval s t1, eval s t2) of ~(VInt i1, VInt i2) -> VInt (i1 * i2) 95 | eval s (Divide t1 t2) = case (eval s t1, eval s t2) of ~(VInt i1, VInt i2) -> VInt (i1 `div` i2) 96 | eval _ IntRec = VAbs \(VAbs p) -> VAbs \z -> VAbs \(VAbs s) -> VAbs \(VInt n) -> intrec p z s n 97 | eval _ Unit = VUnit 98 | eval s (Pair t1 t2) = VPair (eval s t1) (eval s t2) 99 | eval s (Fst t) = case eval s t of ~(VPair t1 _) -> t1 100 | eval s (Snd t) = case eval s t of ~(VPair _ t2) -> t2 101 | eval _ (Abort _) = undefined 102 | eval s (InL t) = VInL (eval s t) 103 | eval s (InR t) = VInR (eval s t) 104 | eval s (Case t ~(Abs x1 t1) ~(Abs x2 t2)) = case eval s t of 105 | VInL l -> eval (Map.insert x1 l s) t1 106 | VInR r -> eval (Map.insert x2 r s) t2 107 | _ -> undefined 108 | eval s (Abs x t) = VAbs (\ v -> eval (Map.insert x v s) t) 109 | eval s (t1 :$: t2) = case eval s t1 of ~(VAbs f) -> f (eval s t2) 110 | eval s (Let s' t) = eval (Map.union (eval s <$> s') s) t 111 | eval s (Fold t) = VFold (eval s t) 112 | eval s (Unfold t) = case eval s t of ~(VFold t) -> t 113 | eval s (Fix ~(Abs x t)) = fix \ self -> eval (Map.insert x (VNext self) s) t 114 | eval s (Next t) = VNext (eval s t) 115 | eval s (Prev t) = case eval s t of ~(VNext t) -> t 116 | eval s (t1 :<*>: t2) = case (eval s t1, eval s t2) of ~(VNext (VAbs f), VNext t2) -> VNext (f t2) 117 | eval s (Box t) = VBox (eval s t) 118 | eval s (Unbox t) = case eval s t of ~(VBox t) -> t 119 | 120 | -- * Printing 121 | 122 | showSubst s = intercalate "; " [v ++ " = " ++ show t | (v, t) <- Map.assocs s] 123 | 124 | pad "" = "" 125 | pad s = " " ++ s ++ " " 126 | 127 | appPrec = 10 128 | plusPrec = 6 129 | 130 | instance Show Term where 131 | showsPrec _ (Var x) = showString x 132 | showsPrec _ (Int i) = shows i 133 | showsPrec d (t1 `Plus` t2) = showParen (d > plusPrec) $ 134 | showsPrec plusPrec t1 . showString " + " . showsPrec (plusPrec + 1) t2 135 | showsPrec d (t1 `Minus` t2) = showParen (d > plusPrec) $ 136 | showsPrec plusPrec t1 . showString " - " . showsPrec (plusPrec + 1) t2 137 | showsPrec d (t1 `Times` t2) = showParen (d > plusPrec) $ 138 | showsPrec plusPrec t1 . showString " * " . showsPrec (plusPrec + 1) t2 139 | showsPrec d (t1 `Divide` t2) = showParen (d > plusPrec) $ 140 | showsPrec plusPrec t1 . showString " / " . showsPrec (plusPrec + 1) t2 141 | showsPrec _ IntRec = showString "intrec" 142 | showsPrec _ Unit = showString "()" 143 | showsPrec d (Pair t1 t2) = showParen (d >= 0) $ 144 | shows t1 . showString ", " . showsPrec (-1) t2 145 | showsPrec d (Fst t) = showParen (d > appPrec) $ 146 | showString "fst " . showsPrec (appPrec + 1) t 147 | showsPrec d (Snd t) = showParen (d > appPrec) $ 148 | showString "snd " . showsPrec (appPrec + 1) t 149 | showsPrec d (Abort t) = showParen (d > appPrec) $ 150 | showString "abort " . showsPrec (appPrec + 1) t 151 | showsPrec d (InL t) = showParen (d > appPrec) $ 152 | showString "left " . showsPrec (appPrec + 1) t 153 | showsPrec d (InR t) = showParen (d > appPrec) $ 154 | showString "right " . showsPrec (appPrec + 1) t 155 | showsPrec d (Case t ~(Abs x1 t1) ~(Abs x2 t2)) = showParen (d > 0) $ 156 | showString "case " . shows t . showString " of { left " 157 | . showString x1 . showString ". " . shows t1 158 | . showString "; right " 159 | . showString x2 . showString ". " . shows t2 160 | . showString " }" 161 | showsPrec d (Abs x t) = showParen (d > 0) $ 162 | showChar '\\' . showString x . showString ". " . shows t 163 | showsPrec d (t1 :$: t2) = showParen (d > appPrec) $ 164 | showsPrec appPrec t1 . showChar ' ' . showsPrec (appPrec + 1) t2 165 | showsPrec d (Let s t) = showParen (d > 0) $ 166 | showString "let {" . showString (pad (showSubst s)) . showString "} in " . shows t 167 | showsPrec d (Fold t) = showParen (d > appPrec) $ 168 | showString "fold " . showsPrec (appPrec + 1) t 169 | showsPrec d (Unfold t) = showParen (d > appPrec) $ 170 | showString "unfold " . showsPrec (appPrec + 1) t 171 | showsPrec d (Fix ~(Abs x t)) = showParen (d > 0) $ 172 | showString "fix " . showString x . showString ". " . shows t 173 | showsPrec d (Next t) = showParen (d > appPrec) $ 174 | showString "next " . showsPrec (appPrec + 1) t 175 | showsPrec d (Prev t) = showParen (d > appPrec) $ 176 | showString "prev " . showsPrec (appPrec + 1) t 177 | showsPrec d (t1 :<*>: t2) = showParen (d > prec) $ 178 | showsPrec prec t1 . showString " <*> " . showsPrec (prec + 1) t2 179 | where prec = 4 180 | showsPrec d (Box t) = showParen (d > appPrec) $ 181 | showString "box " . showsPrec (appPrec + 1) t 182 | showsPrec d (Unbox t) = showParen (d > appPrec) $ 183 | showString "unbox " . showsPrec (appPrec + 1) t 184 | 185 | instance Show Value where 186 | showsPrec _ (VInt i) = shows i 187 | showsPrec _ VUnit = showString "()" 188 | showsPrec d (VPair t1 t2) = showParen (d >= 0) $ 189 | shows t1 . showString ", " . showsPrec (-1) t2 190 | showsPrec d (VInL t) = showParen (d > appPrec) $ 191 | showString "left " . showsPrec (appPrec + 1) t 192 | showsPrec d (VInR t) = showParen (d > appPrec) $ 193 | showString "right " . showsPrec (appPrec + 1) t 194 | showsPrec _ (VAbs _) = showString "" 195 | showsPrec d (VFold t) = showParen (d > appPrec) $ 196 | showString "fold " . showsPrec (appPrec + 1) t 197 | showsPrec d (VNext t) = showParen (d > appPrec) $ 198 | showString "next " . showsPrec (appPrec + 1) t 199 | showsPrec d (VBox t) = showParen (d > appPrec) $ 200 | showString "box " . showsPrec (appPrec + 1) t 201 | 202 | -- * Parsing 203 | 204 | variable :: Parser Var 205 | variable = mkIdentifier 206 | ["intrec", "fst", "snd", "left", "right", "case", "of", "let", "fold", "unfold", 207 | "fix", "next", "prev", "box", "unbox", "in", "type"] 208 | 209 | term :: Parser Term 210 | term = choice [abs_, fix_, case_, letIn, makeExprParser base ops] "term" 211 | where 212 | abs_ = flip (foldr Abs) <$ lambda <*> some variable <* dot <*> term 213 | fix_ = Fix <$> (flip (foldr Abs) <$ "fix" <*> some variable <* dot <*> term) 214 | case_ = do 215 | "case"; t <- term; "of" 216 | braces do 217 | "left"; x1 <- variable; dot; t1 <- term 218 | semicolon 219 | "right"; x2 <- variable; dot; t2 <- term 220 | pure $ Case t (Abs x1 t1) (Abs x2 t2) 221 | letIn = Let <$ "let" <*> braces subst <* "in" <*> term 222 | base = Var <$> variable 223 | <|> Int <$> number 224 | <|> IntRec <$ "intrec" 225 | <|> parens (tuple <$> term `sepBy` comma) 226 | tuple [] = Unit 227 | tuple [t] = t 228 | tuple (t : ts) = Pair t (tuple ts) 229 | unaries = [("fst", Fst), ("snd", Snd), ("abort", Abort), ("left", InL), ("right", InR), 230 | ("fold", Fold), ("unfold", Unfold), ("next", Next), ("prev", Prev), 231 | ("box", Box), ("unbox", Unbox)] 232 | unary = choice [f <$ hidden (keyword w) | (w, f) <- unaries] 233 | ops = [ [ InfixL (pure (:$:)) 234 | , Prefix (foldr1 (.) <$> some unary) ] 235 | , [ InfixL (Plus <$ symbol "+"), InfixL (Minus <$ symbol "-") 236 | , InfixL (Times <$ symbol "*"), InfixL (Divide <$ symbol "/") ] 237 | , [ InfixL ((:<*>:) <$ (symbol "<*>" <|> symbol "⊛")) 238 | , InfixL ((:<*>:) . Next <$ symbol "<$>") ] ] 239 | 240 | binding :: Parser (Var, Term) 241 | binding = try (mkBinding <$> variable <*> many variable <* equal) <*> term 242 | where 243 | mkBinding x ys t = (x, autoFix x (foldr Abs t ys)) 244 | autoFix x t | x `freeIn` t = Fix (Abs x t) 245 | | otherwise = t 246 | 247 | subst :: Parser Subst 248 | subst = Map.fromList <$> binding `sepBy` semicolon 249 | -------------------------------------------------------------------------------- /glam/Glam/Type.hs: -------------------------------------------------------------------------------- 1 | -- | The basic syntax and operations on types. 2 | module Glam.Type where 3 | 4 | import Control.Monad 5 | import Data.List 6 | import Data.String 7 | import Data.Set (Set) 8 | import Data.Set qualified as Set 9 | import Data.Map (Map) 10 | import Data.Map qualified as Map 11 | import Text.Megaparsec 12 | import Control.Monad.Combinators.Expr 13 | 14 | import Glam.Utils 15 | 16 | -- | Type variables 17 | type TVar = String 18 | 19 | -- | Type substitutions 20 | type TSubst = Map TVar Type 21 | 22 | -- | Whether a type, or term, is constant. Note that 'False' means "we don't know". 23 | type Constancy = Bool 24 | 25 | -- | A fixed point variable can only be used when it is /guarded/ by a @▸@ modality. 26 | data Guardedness = Unguarded -- ^ Can't use it yet 27 | | Guarded -- ^ OK, under @▸@ 28 | | Forbidden -- ^ No way, we're under @■@ 29 | deriving Eq 30 | 31 | infixr 7 :*: 32 | infixr 6 :+: 33 | infixr 5 :->: 34 | 35 | -- | Monomorphic types of the guarded λ-calculus 36 | data Type = TVar TVar -- ^ Variables 37 | | TInt -- ^ Integers 38 | | TApp Type Type -- ^ Applications 39 | | One | Type :*: Type -- ^ Products 40 | | Zero | Type :+: Type -- ^ Sums 41 | | Type :->: Type -- ^ Functions 42 | | Later Type -- ^ @▸@ modality 43 | | Constant Type -- ^ @■@ modality 44 | | TFix TVar Type -- ^ Fixed points 45 | deriving Eq 46 | 47 | -- | Polymorphic type schemes 48 | data Polytype = Forall [(TVar, Constancy)] Type 49 | deriving Eq 50 | 51 | pattern Monotype ty = Forall [] ty 52 | 53 | instance IsString Type where 54 | fromString = TVar 55 | 56 | -- * Variables and substitution 57 | 58 | class HasTVars t where 59 | freeTVars :: t -> Set TVar 60 | allTVars :: t -> Set TVar 61 | 62 | instance HasTVars Type where 63 | freeTVars (TVar x) = Set.singleton x 64 | freeTVars (t1 :*: t2) = freeTVars t1 <> freeTVars t2 65 | freeTVars (t1 :+: t2) = freeTVars t1 <> freeTVars t2 66 | freeTVars (t1 :->: t2) = freeTVars t1 <> freeTVars t2 67 | freeTVars (Later t) = freeTVars t 68 | freeTVars (Constant t) = freeTVars t 69 | freeTVars (TFix x t) = Set.delete x (freeTVars t) 70 | freeTVars _ = mempty 71 | allTVars (TVar x) = Set.singleton x 72 | allTVars (t1 :*: t2) = allTVars t1 <> allTVars t2 73 | allTVars (t1 :+: t2) = allTVars t1 <> allTVars t2 74 | allTVars (t1 :->: t2) = allTVars t1 <> allTVars t2 75 | allTVars (Later t) = allTVars t 76 | allTVars (Constant t) = allTVars t 77 | allTVars (TFix x t) = Set.insert x (allTVars t) 78 | allTVars _ = mempty 79 | 80 | instance HasTVars Polytype where 81 | freeTVars (Forall (map fst -> xs) ty) = freeTVars ty Set.\\ Set.fromList xs 82 | allTVars (Forall (map fst -> xs) ty) = allTVars ty <> Set.fromList xs 83 | 84 | x `freeInType` t = x `Set.member` freeTVars t 85 | 86 | freshTVarsFor :: Set TVar -> [TVar] 87 | freshTVarsFor xs = [x | n <- [1..] 88 | , x <- replicateM n ['a'..'z'] 89 | , x `Set.notMember` xs] 90 | 91 | avoidCaptureType vs (x, ty) 92 | | x `Set.member` vs = (y, substituteType1 x (TVar y) ty) 93 | | otherwise = (x, ty) 94 | where y:_ = freshTVarsFor (vs <> Set.delete x (freeTVars ty)) 95 | 96 | substituteType :: TSubst -> Type -> Type 97 | substituteType s (TVar x) = Map.findWithDefault (TVar x) x s 98 | substituteType s (t1 :*: t2) = substituteType s t1 :*: substituteType s t2 99 | substituteType s (t1 :+: t2) = substituteType s t1 :+: substituteType s t2 100 | substituteType s (t1 :->: t2) = substituteType s t1 :->: substituteType s t2 101 | substituteType s (Later t1) = Later (substituteType s t1) 102 | substituteType s (Constant t1) = Constant (substituteType s t1) 103 | substituteType s (TFix x tf) = TFix x' (substituteType (Map.delete x' s) tf') 104 | where (x', tf') = avoidCaptureType (foldMap freeTVars s) (x, tf) 105 | substituteType _ ty = ty 106 | 107 | substituteType1 x s = substituteType (Map.singleton x s) 108 | 109 | alphaNormalise :: Polytype -> Polytype 110 | alphaNormalise pty@(Forall as ty) = Forall [(b, c) | ((_, c), b) <- s] ty' where 111 | s = zip as (freshTVarsFor (freeTVars pty)) 112 | ty' = substituteType (Map.fromList [(a, TVar b) | ((a, _), b) <- s]) ty 113 | 114 | -- * Printing 115 | 116 | prodPrec = 6 117 | sumPrec = 4 118 | funPrec = 0 119 | modPrec = 8 120 | appPrec = 10 121 | 122 | instance Show Type where 123 | showsPrec _ (TVar x) = showString x 124 | showsPrec _ TInt = showString "Int" 125 | showsPrec d (TApp t1 t2) = showParen (d > appPrec) $ 126 | showsPrec appPrec t1 . showChar ' ' . showsPrec (appPrec + 1) t2 127 | showsPrec _ One = showString "1" 128 | showsPrec d (t1 :*: t2) = showParen (d > prodPrec) $ 129 | showsPrec (prodPrec + 1) t1 . showString " * " . showsPrec prodPrec t2 130 | showsPrec _ Zero = showString "0" 131 | showsPrec d (t1 :+: t2) = showParen (d > sumPrec) $ 132 | showsPrec (sumPrec + 1) t1 . showString " + " . showsPrec sumPrec t2 133 | showsPrec d (t1 :->: t2) = showParen (d > funPrec) $ 134 | showsPrec (funPrec + 1) t1 . showString " -> " . showsPrec funPrec t2 135 | showsPrec d (Later ty) = showParen (d > modPrec) $ 136 | showString ">" . showsPrec modPrec ty 137 | showsPrec d (Constant ty) = showParen (d > modPrec) $ 138 | showString "#" . showsPrec modPrec ty 139 | showsPrec _ (TFix x tf) = showParen True $ 140 | showString "Fix " . showString x . showString ". " . shows tf 141 | 142 | instance Show Polytype where 143 | showsPrec _ (Forall [] ty) = shows ty 144 | showsPrec _ (Forall xs ty) = showString "forall " . showString (intercalate " " [(if c then "#" else "") ++ x | (x, c) <- xs]) . showString ". " . shows ty 145 | 146 | -- * Parsing 147 | 148 | tVar :: Parser TVar 149 | tVar = mkIdentifier ["type", "Fix", "μ", "Int", "ℤ", "forall"] 150 | 151 | tConstant = symbol "#" <|> symbol "■" 152 | 153 | type_ :: Parser Type 154 | type_ = tfix <|> makeExprParser base ops "type" 155 | where 156 | tfix = TFix <$ ("Fix" <|> "μ") <*> tVar <* dot <*> type_ 157 | base = TInt <$ ("Int" <|> "ℤ") 158 | <|> TVar <$> tVar 159 | <|> One <$ (symbol "1" <|> symbol "⊤") 160 | <|> Zero <$ (symbol "0" <|> symbol "⊥") 161 | <|> parens type_ 162 | modality = Later <$ (symbol ">" <|> symbol "▸") 163 | <|> Constant <$ tConstant 164 | ops = [ [InfixL (pure TApp)] 165 | , [Prefix (foldr1 (.) <$> some modality)] 166 | , [binary ["*", "×"] (:*:)] 167 | , [binary ["+"] (:+:)] 168 | , [binary ["->", "→"] (:->:)] ] 169 | binary s f = InfixR (f <$ choice (map symbol s)) 170 | 171 | quantifiedTVar :: Parser (TVar, Constancy) 172 | quantifiedTVar = flip (,) <$> option False (True <$ tConstant) <*> tVar 173 | 174 | polytype :: Parser Polytype 175 | polytype = Forall <$> option [] (("forall" <|> symbol "∀") *> some quantifiedTVar <* dot) <*> type_ 176 | -------------------------------------------------------------------------------- /glam/Glam/Utils.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TypeOperators #-} 2 | {-# LANGUAGE TypeFamilies #-} 3 | {-# LANGUAGE FlexibleInstances #-} 4 | module Glam.Utils where 5 | 6 | import Control.Monad.Reader 7 | import Data.Bifunctor (first) 8 | import Data.Char 9 | import Data.String 10 | import Data.Void 11 | import Text.Megaparsec hiding (State, parse) 12 | import Text.Megaparsec.Char 13 | import Text.Megaparsec.Char.Lexer qualified as L 14 | 15 | -- * Parsing 16 | 17 | type IndentRef = Maybe SourcePos 18 | 19 | type Parser = ReaderT IndentRef (Parsec Void String) 20 | 21 | parse :: Parser a -> String -> String -> Either String a 22 | parse p f s = first (init . errorBundlePretty) $ runParser (runReaderT p Nothing) f s 23 | 24 | whitespace :: Parser () 25 | whitespace = L.space space1 (L.skipLineComment "--") (L.skipBlockComment "{-" "-}") 26 | 27 | alpha :: Parser Char 28 | alpha = letterChar <|> char '_' 29 | 30 | isRest :: Char -> Bool 31 | isRest c = c == '\'' || c == '_' || isDigit c || isAlpha c 32 | 33 | lexeme :: Parser a -> Parser a 34 | lexeme p = do 35 | SourcePos { sourceLine = curLine, sourceColumn = curColumn } <- getSourcePos 36 | ref <- ask 37 | case ref of 38 | Just SourcePos { sourceLine = refLine, sourceColumn = refColumn } 39 | | curLine > refLine, curColumn <= refColumn -> 40 | L.incorrectIndent GT refColumn curColumn 41 | _ -> pure () 42 | p <* whitespace 43 | 44 | symbol, keyword :: String -> Parser String 45 | symbol s = lexeme (string s) 46 | keyword s = label (show s) $ try $ lexeme $ string s <* notFollowedBy (satisfy isRest) 47 | 48 | instance {-# OVERLAPPING #-} a ~ String => IsString (Parser a) where 49 | fromString = keyword 50 | 51 | colon, semicolon, comma, equal, dot, lambda :: Parser String 52 | colon = symbol ":" 53 | semicolon = symbol ";" 54 | comma = symbol "," 55 | equal = symbol "=" 56 | dot = symbol "." 57 | lambda = symbol "λ" <|> symbol "\\" 58 | 59 | parens, braces, lineFolded :: Parser a -> Parser a 60 | parens = between (symbol "(") (symbol ")") 61 | braces = between (symbol "{") (symbol "}") 62 | lineFolded p = do 63 | pos <- getSourcePos 64 | local (\_ -> Just pos) p 65 | 66 | word :: Parser String 67 | word = (:) <$> alpha <*> takeWhileP Nothing isRest "word" 68 | 69 | number :: Parser Integer 70 | number = lexeme L.decimal 71 | 72 | mkIdentifier :: [String] -> Parser String 73 | mkIdentifier reserved = label "identifier" $ try $ lexeme do 74 | w <- word 75 | if w `elem` reserved 76 | then fail $ "unexpected keyword " ++ w 77 | else pure w 78 | 79 | -- * Type checking 80 | 81 | infix 1 |- 82 | (|-) = local 83 | 84 | -- | Like 'lookup', but also returns the de Bruijn /level/ of the variable. 85 | lookupLevel :: Eq a => a -> [(a, b)] -> Maybe (b, Int) 86 | lookupLevel _ [] = Nothing 87 | lookupLevel x ((y, c):ys) 88 | | x == y = Just (c, length ys) 89 | | otherwise = lookupLevel x ys 90 | -------------------------------------------------------------------------------- /glam/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Naïm Favier 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /glam/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE MultiParamTypeClasses #-} 3 | {-# LANGUAGE UndecidableInstances #-} 4 | import Data.Char 5 | import Data.List 6 | import Control.Monad 7 | import Control.Monad.Loops 8 | import Control.Monad.State 9 | import System.Console.GetOpt 10 | import System.Console.Haskeline 11 | import System.Environment 12 | import System.Exit 13 | import System.IO 14 | 15 | import Glam.Run 16 | 17 | instance MonadState s m => MonadState s (InputT m) where 18 | get = lift get 19 | put = lift . put 20 | 21 | err = liftIO . hPutStrLn stderr 22 | 23 | usage = "usage: glam [options...] files..." 24 | 25 | options = [Option ['i'] ["interactive"] (NoArg ()) "run in interactive mode (default if no files are provided)"] 26 | 27 | parseArgs = do 28 | args <- getArgs 29 | (i, fs) <- case getOpt Permute options args of 30 | (o, fs, []) -> pure (not (null o), fs) 31 | (_, _, errs) -> die $ concat errs ++ usageInfo usage options 32 | let interactive = i || null fs 33 | pure (interactive, fs) 34 | 35 | comp = completeWord Nothing " \t" \p -> do 36 | defined <- getWords 37 | let words = defined ++ ["fst", "snd", "abort", "left", "right", "fold", "unfold", "box", "unbox", "next", "prev"] 38 | pure [simpleCompletion w | w <- words, p `isPrefixOf` w] 39 | 40 | settings = Settings { complete = comp 41 | , historyFile = Just ".glam_history" 42 | , autoAddHistory = True } 43 | 44 | prompt = "> " 45 | 46 | main = runGlamT do 47 | (interactive, fs) <- liftIO parseArgs 48 | liftIO $ hSetBuffering stdout NoBuffering 49 | forM_ fs \f -> do 50 | let (name, contents) | f == "-" = ("", getContents) 51 | | otherwise = (f, readFile f) 52 | contents <- liftIO contents 53 | liftIO . either die (mapM_ putStrLn) =<< runFile name contents 54 | when interactive do 55 | runInputT settings repl 56 | 57 | commands = 58 | [ "type" ==> \s -> do 59 | ty <- getType s 60 | liftIO case ty of 61 | Right ty -> putStrLn $ s ++ " : " ++ show ty 62 | Left e -> err e 63 | , "quit" ==> \_ -> liftIO exitSuccess 64 | ] where (==>) = (,) 65 | 66 | repl = handleInterrupt repl $ withInterrupt $ 67 | whileJust_ (getInputLine prompt) \(dropWhile isSpace -> line) -> case line of 68 | ':':(break isSpace -> (cmd, dropWhile isSpace -> args)) -> 69 | case [c | c@(name, _) <- commands, cmd `isPrefixOf` name] of 70 | [(_, action)] -> action args 71 | [] -> err $ "unknown command :" ++ cmd 72 | cs -> err $ "ambiguous command :" ++ cmd ++ " could refer to: " ++ intercalate " " (map fst cs) 73 | _ -> liftIO . either err (mapM_ putStrLn) =<< runFile "" line 74 | -------------------------------------------------------------------------------- /glam/MainJS.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ViewPatterns #-} 2 | import GHCJS.Marshal 3 | import GHCJS.Foreign.Callback 4 | 5 | import Glam.Run 6 | 7 | foreign import javascript unsafe "glam = $1" 8 | setGlam :: Callback a -> IO () 9 | 10 | main = do 11 | setGlam =<< syncCallback1' \v -> do 12 | Just input <- fromJSVal v 13 | toJSVal $ either id unlines 14 | $ runGlam 15 | $ runFile "" input 16 | -------------------------------------------------------------------------------- /glam/glam.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | 3 | name: glam 4 | version: 0.0 5 | synopsis: Polymorphic guarded λ-calculus 6 | description: 7 | An implementation of Clouston, Bizjak, Bugge and Birkedal's guarded λ-calculus with rank-1 polymorphism and automatic boxing. 8 | homepage: https://github.com/ncfavier/glam 9 | license: ISC 10 | license-file: LICENSE 11 | author: Naïm Favier 12 | maintainer: n@monade.li 13 | category: Compilers/Interpreters 14 | 15 | source-repository head 16 | type: git 17 | location: https://github.com/ncfavier/glam 18 | 19 | executable glam 20 | build-depends: 21 | base >= 4.12.0.0 && < 5, 22 | containers, 23 | transformers, 24 | mtl, 25 | megaparsec, 26 | parser-combinators, 27 | lens 28 | other-modules: 29 | Glam.Utils 30 | Glam.Term 31 | Glam.Type 32 | Glam.Rules.Term 33 | Glam.Rules.Type 34 | Glam.Run 35 | default-language: Haskell2010 36 | default-extensions: 37 | NamedFieldPuns 38 | BlockArguments 39 | LambdaCase 40 | MultiWayIf 41 | ViewPatterns 42 | PatternSynonyms 43 | ApplicativeDo 44 | OverloadedStrings 45 | PostfixOperators 46 | FlexibleContexts 47 | ConstraintKinds 48 | NoMonomorphismRestriction 49 | ImportQualifiedPost 50 | TupleSections 51 | ghc-options: -W 52 | ghcjs-options: -dedupe 53 | 54 | if impl(ghcjs) 55 | main-is: MainJS.hs 56 | cpp-options: -DGHCJS_BROWSER 57 | build-depends: 58 | ghcjs-base 59 | else 60 | main-is: Main.hs 61 | build-depends: 62 | haskeline, 63 | monad-loops 64 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ncfavier/glam/4dc41ca2c77e4cc0d0178cf5b455f19ca4e62e41/web/favicon.ico -------------------------------------------------------------------------------- /web/glam_syntax.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | CodeMirror.defineMode("glam", function(_config, modeConfig) { 15 | 16 | function switchState(source, setState, f) { 17 | setState(f); 18 | return f(source, setState); 19 | } 20 | 21 | // These should all be Unicode extended, as per the Haskell 2010 report 22 | var smallRE = /[a-z_]/; 23 | var largeRE = /[A-Z]/; 24 | var digitRE = /\d/; 25 | var hexitRE = /[0-9A-Fa-f]/; 26 | var octitRE = /[0-7]/; 27 | var idRE = /[a-z_A-Z0-9'\xa1-\uffff]/; 28 | var symbolRE = /[-!#$%&*+.\/<=>?@\\^|~:]/; 29 | var specialRE = /[(),;[\]`{}]/; 30 | var whiteCharRE = /[ \t\v\f]/; // newlines are handled in tokenizer 31 | 32 | function normal(source, setState) { 33 | if (source.eatWhile(whiteCharRE)) { 34 | return null; 35 | } 36 | 37 | var ch = source.next(); 38 | if (specialRE.test(ch)) { 39 | if (ch == '{' && source.eat('-')) { 40 | var t = "comment"; 41 | return switchState(source, setState, ncomment(t, 1)); 42 | } 43 | return null; 44 | } 45 | 46 | if (ch == '\'') { 47 | if (source.eat('\\')) { 48 | source.next(); // should handle other escapes here 49 | } 50 | else { 51 | source.next(); 52 | } 53 | if (source.eat('\'')) { 54 | return "string"; 55 | } 56 | return "string error"; 57 | } 58 | 59 | if (ch == '"') { 60 | return switchState(source, setState, stringLiteral); 61 | } 62 | 63 | if (largeRE.test(ch)) { 64 | source.eatWhile(idRE); 65 | return "variable-2"; 66 | } 67 | 68 | if (smallRE.test(ch)) { 69 | source.eatWhile(idRE); 70 | return "variable"; 71 | } 72 | 73 | if (digitRE.test(ch)) { 74 | if (ch == '0') { 75 | if (source.eat(/[xX]/)) { 76 | source.eatWhile(hexitRE); // should require at least 1 77 | return "integer"; 78 | } 79 | if (source.eat(/[oO]/)) { 80 | source.eatWhile(octitRE); // should require at least 1 81 | return "number"; 82 | } 83 | } 84 | source.eatWhile(digitRE); 85 | var t = "number"; 86 | if (source.match(/^\.\d+/)) { 87 | t = "number"; 88 | } 89 | if (source.eat(/[eE]/)) { 90 | t = "number"; 91 | source.eat(/[-+]/); 92 | source.eatWhile(digitRE); // should require at least 1 93 | } 94 | return t; 95 | } 96 | 97 | if (ch == "." && source.eat(".")) 98 | return "keyword"; 99 | 100 | if (symbolRE.test(ch)) { 101 | if (ch == '-' && source.eat(/-/)) { 102 | source.eatWhile(/-/); 103 | if (!source.eat(symbolRE)) { 104 | source.skipToEnd(); 105 | return "comment"; 106 | } 107 | } 108 | var t = "variable"; 109 | if (ch == ':') { 110 | t = "variable-2"; 111 | } 112 | source.eatWhile(symbolRE); 113 | return t; 114 | } 115 | 116 | return "error"; 117 | } 118 | 119 | function ncomment(type, nest) { 120 | if (nest == 0) { 121 | return normal; 122 | } 123 | return function(source, setState) { 124 | var currNest = nest; 125 | while (!source.eol()) { 126 | var ch = source.next(); 127 | if (ch == '{' && source.eat('-')) { 128 | ++currNest; 129 | } 130 | else if (ch == '-' && source.eat('}')) { 131 | --currNest; 132 | if (currNest == 0) { 133 | setState(normal); 134 | return type; 135 | } 136 | } 137 | } 138 | setState(ncomment(type, currNest)); 139 | return type; 140 | }; 141 | } 142 | 143 | function stringLiteral(source, setState) { 144 | while (!source.eol()) { 145 | var ch = source.next(); 146 | if (ch == '"') { 147 | setState(normal); 148 | return "string"; 149 | } 150 | if (ch == '\\') { 151 | if (source.eol() || source.eat(whiteCharRE)) { 152 | setState(stringGap); 153 | return "string"; 154 | } 155 | if (source.eat('&')) { 156 | } 157 | else { 158 | source.next(); // should handle other escapes here 159 | } 160 | } 161 | } 162 | setState(normal); 163 | return "string error"; 164 | } 165 | 166 | function stringGap(source, setState) { 167 | if (source.eat('\\')) { 168 | return switchState(source, setState, stringLiteral); 169 | } 170 | source.next(); 171 | setState(normal); 172 | return "error"; 173 | } 174 | 175 | 176 | var wellKnownWords = (function() { 177 | var wkw = {}; 178 | function setType(t) { 179 | return function () { 180 | for (var i = 0; i < arguments.length; i++) 181 | wkw[arguments[i]] = t; 182 | }; 183 | } 184 | 185 | setType("keyword")( 186 | "case", "of", "let", "in", "fix", "Fix", "type", "forall"); 187 | 188 | setType("keyword")( 189 | "=", "\\", "λ", ".", ":", "μ", "∀", "->", "→", ">", "▸", "#", "■", "+", "*", "×"); 190 | 191 | setType("builtin")( 192 | "intrec", "next", "prev", "box", "unbox", "fold", "unfold", "fst", "snd", "abort", "left", "right", "Int"); 193 | 194 | setType("builtin")( 195 | "<*>", "⊛", "<$>", "+", "-", "⊤", "⊥", "ℤ"); 196 | 197 | var override = modeConfig.overrideKeywords; 198 | if (override) for (var word in override) if (override.hasOwnProperty(word)) 199 | wkw[word] = override[word]; 200 | 201 | return wkw; 202 | })(); 203 | 204 | 205 | 206 | return { 207 | startState: function () { return { f: normal }; }, 208 | copyState: function (s) { return { f: s.f }; }, 209 | 210 | token: function(stream, state) { 211 | var t = state.f(stream, function(s) { state.f = s; }); 212 | var w = stream.current(); 213 | return wellKnownWords.hasOwnProperty(w) ? wellKnownWords[w] : t; 214 | }, 215 | 216 | blockCommentStart: "{-", 217 | blockCommentEnd: "-}", 218 | lineComment: "--" 219 | }; 220 | 221 | }); 222 | 223 | }); 224 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | glam — Polymorphic guarded λ-calculus 8 | 9 | 10 | 11 | 12 | 50 | 51 | 52 | @scripts@ 53 | 54 | 55 |

glam. Polymorphic guarded λ-calculus

56 |

57 | Based on Ranald Clouston, Aleš Bizjak, Hans Bugge Grathwohl and Lars Birkedal's paper. 58 |

59 |

60 | Also see my internship report and the source code (or generated documentation) for details. If you find a bug, please report it! 61 |

62 |

63 | Examples: 64 | @examples@ 65 |

66 | 67 |

68 |
69 | 98 | 99 | 100 | --------------------------------------------------------------------------------