Compare commits
18 Commits
antiquated
...
another-at
Author | SHA1 | Date | |
---|---|---|---|
|
57a0d0a603 | ||
|
b44fda3283 | ||
|
1be26eb453 | ||
|
7067edc86f | ||
|
dc771fc7ad | ||
|
45c4d08fb9 | ||
|
77257d0eb7 | ||
|
f33195ab28 | ||
|
8cde20641b | ||
|
ba4ccfe6bf | ||
|
7bc92aef97 | ||
|
95e22567e7 | ||
|
dc09d804ef | ||
|
49e6e3a71d | ||
|
cf7a2ff9ba | ||
|
aff809e4ce | ||
|
18b4ac0d4b | ||
|
ab53cfdb7d |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
target
|
||||
.schala_repl
|
||||
.schala_history
|
||||
rusty-tags.vi
|
||||
node_modules/
|
||||
experiments/tree-sitter-test/src
|
||||
|
1249
Cargo.lock
generated
1249
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
25
Cargo.toml
25
Cargo.toml
@ -1,20 +1,7 @@
|
||||
[package]
|
||||
name = "schala"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
edition = "2018"
|
||||
resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.21"
|
||||
|
||||
schala-repl = { path = "schala-repl" }
|
||||
schala-lang = { path = "schala-lang" }
|
||||
# maaru-lang = { path = "maaru" }
|
||||
# rukka-lang = { path = "rukka" }
|
||||
# robo-lang = { path = "robo" }
|
||||
|
||||
[build-dependencies]
|
||||
includedir_codegen = "0.2.0"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"schala-main",
|
||||
"schala-parser",
|
||||
"experiments",
|
||||
]
|
||||
resolver = "2"
|
||||
|
920
HindleyMilner.hs
920
HindleyMilner.hs
@ -1,920 +0,0 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedLists #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
|
||||
|
||||
-- | This module is an extensively documented walkthrough for typechecking a
|
||||
-- basic functional language using the Hindley-Damas-Milner algorithm.
|
||||
--
|
||||
-- In the end, we'll be able to infer the type of expressions like
|
||||
--
|
||||
-- @
|
||||
-- find (λx. (>) x 0)
|
||||
-- :: [Integer] -> Either () Integer
|
||||
-- @
|
||||
--
|
||||
-- It can be used in multiple different forms:
|
||||
--
|
||||
-- * The source is written in literate programming style, so you can almost
|
||||
-- read it from top to bottom, minus some few references to later topics.
|
||||
-- * /Loads/ of doctests (runnable and verified code examples) are included
|
||||
-- * The code is runnable in GHCi, all definitions are exposed.
|
||||
-- * A small main module that gives many examples of what you might try out in
|
||||
-- GHCi is also included.
|
||||
-- * The Haddock output yields a nice overview over the definitions given, with
|
||||
-- a nice rendering of a truckload of Haddock comments.
|
||||
|
||||
module HindleyMilner where
|
||||
|
||||
import Control.Monad.Trans
|
||||
import Control.Monad.Trans.Except
|
||||
import Control.Monad.Trans.State
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as M
|
||||
import Data.Monoid
|
||||
import Data.Set (Set)
|
||||
import qualified Data.Set as S
|
||||
import Data.String
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
|
||||
|
||||
|
||||
-- $setup
|
||||
--
|
||||
-- For running doctests:
|
||||
--
|
||||
-- >>> :set -XOverloadedStrings
|
||||
-- >>> :set -XOverloadedLists
|
||||
-- >>> :set -XLambdaCase
|
||||
-- >>> import qualified Data.Text.IO as T
|
||||
-- >>> let putPprLn = T.putStrLn . ppr
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
-- * Preliminaries
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Prettyprinting
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | A prettyprinter class. Similar to 'Show', but with a focus on having
|
||||
-- human-readable output as opposed to being valid Haskell.
|
||||
class Pretty a where
|
||||
ppr :: a -> Text
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Names
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | A 'name' is an identifier in the language we're going to typecheck.
|
||||
-- Variables on both the term and type level have 'Name's, for example.
|
||||
newtype Name = Name Text
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
-- | >>> "lorem" :: Name
|
||||
-- Name "lorem"
|
||||
instance IsString Name where
|
||||
fromString = Name . T.pack
|
||||
|
||||
-- | >>> putPprLn (Name "var")
|
||||
-- var
|
||||
instance Pretty Name where
|
||||
ppr (Name n) = n
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Monotypes
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | A monotype is an unquantified/unparametric type, in other words it contains
|
||||
-- no @forall@s. Monotypes are the inner building blocks of all types. Examples
|
||||
-- of monotypes are @Int@, @a@, @a -> b@.
|
||||
--
|
||||
-- In formal notation, 'MType's are often called τ (tau) types.
|
||||
data MType = TVar Name -- ^ @a@
|
||||
| TFun MType MType -- ^ @a -> b@
|
||||
| TConst Name -- ^ @Int@, @()@, …
|
||||
|
||||
-- Since we can't declare our own types in our simple type system
|
||||
-- here, we'll hard-code certain basic ones so we can typecheck some
|
||||
-- familar functions that use them later.
|
||||
| TList MType -- ^ @[a]@
|
||||
| TEither MType MType -- ^ @Either a b@
|
||||
| TTuple MType MType -- ^ @(a,b)@
|
||||
deriving Show
|
||||
|
||||
-- | >>> putPprLn (TFun (TEither (TVar "a") (TVar "b")) (TFun (TVar "c") (TVar "d")))
|
||||
-- Either a b → c → d
|
||||
--
|
||||
-- Using the 'IsString' instance:
|
||||
--
|
||||
-- >>> putPprLn (TFun (TEither "a" "b") (TFun "c" "d"))
|
||||
-- Either a b → c → d
|
||||
instance Pretty MType where
|
||||
ppr = go False
|
||||
where
|
||||
go _ (TVar name) = ppr name
|
||||
go _ (TList a) = "[" <> ppr a <> "]"
|
||||
go _ (TEither l r) = "Either " <> ppr l <> " " <> ppr r
|
||||
go _ (TTuple a b) = "(" <> ppr a <> ", " <> ppr b <> ")"
|
||||
go _ (TConst name) = ppr name
|
||||
go parenthesize (TFun a b)
|
||||
| parenthesize = "(" <> lhs <> " → " <> rhs <> ")"
|
||||
| otherwise = lhs <> " → " <> rhs
|
||||
where lhs = go True a
|
||||
rhs = go False b
|
||||
|
||||
-- | >>> "var" :: MType
|
||||
-- TVar (Name "var")
|
||||
instance IsString MType where
|
||||
fromString = TVar . fromString
|
||||
|
||||
|
||||
|
||||
-- | The free variables of an 'MType'. This is simply the collection of all the
|
||||
-- individual type variables occurring inside of it.
|
||||
--
|
||||
-- __Example:__ The free variables of @a -> b@ are @a@ and @b@.
|
||||
freeMType :: MType -> Set Name
|
||||
freeMType = \case
|
||||
TVar a -> [a]
|
||||
TFun a b -> freeMType a <> freeMType b
|
||||
TList a -> freeMType a
|
||||
TEither l r -> freeMType l <> freeMType r
|
||||
TTuple a b -> freeMType a <> freeMType b
|
||||
TConst _ -> []
|
||||
|
||||
|
||||
|
||||
-- | Substitute all the contained type variables mentioned in the substitution,
|
||||
-- and leave everything else alone.
|
||||
instance Substitutable MType where
|
||||
applySubst s = \case
|
||||
TVar a -> let Subst s' = s
|
||||
in M.findWithDefault (TVar a) a s'
|
||||
TFun f x -> TFun (applySubst s f) (applySubst s x)
|
||||
TList a -> TList (applySubst s a)
|
||||
TEither l r -> TEither (applySubst s l) (applySubst s r)
|
||||
TTuple a b -> TTuple (applySubst s a) (applySubst s b)
|
||||
c@TConst {} -> c
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Polytypes
|
||||
-- #############################################################################
|
||||
|
||||
-- | A polytype is a monotype universally quantified over a number of type
|
||||
-- variables. In Haskell, all definitions have polytypes, but since the @forall@
|
||||
-- is implicit they look a bit like monotypes, maybe confusingly so. For
|
||||
-- example, the type of @1 :: Int@ is actually @forall <nothing>. Int@, and the
|
||||
-- type of @id@ is @forall a. a -> a@, although GHC displays it as @a -> a@.
|
||||
--
|
||||
-- A polytype claims to work "for all imaginable type parameters", very similar
|
||||
-- to how a lambda claims to work "for all imaginable value parameters". We can
|
||||
-- insert a value into a lambda's parameter to evaluate it to a new value, and
|
||||
-- similarly we'll later insert types into a polytype's quantified variables to
|
||||
-- gain new types.
|
||||
--
|
||||
-- __Example:__ in a definition @id :: forall a. a -> a@, the @a@ after the
|
||||
-- ∀ ("forall") is the collection of type variables, and @a -> a@ is the 'MType'
|
||||
-- quantified over. When we have such an @id@, we also have its specialized
|
||||
-- version @Int -> Int@ available. This process will be the topic of the type
|
||||
-- inference/unification algorithms.
|
||||
--
|
||||
-- In formal notation, 'PType's are often called σ (sigma) types.
|
||||
--
|
||||
-- The purpose of having monotypes and polytypes is that we'd like to only have
|
||||
-- universal quantification at the top level, restricting our language to rank-1
|
||||
-- polymorphism, where type inferece is total (all types can be inferred) and
|
||||
-- simple (only a handful of typing rules). Weakening this constraint would be
|
||||
-- easy: if we allowed universal quantification within function types we would
|
||||
-- get rank-N polymorphism. Taking it even further to allow it anywhere,
|
||||
-- effectively replacing all occurrences of 'MType' with 'PType', yields
|
||||
-- impredicative types. Both these extensions make the type system
|
||||
-- *significantly* more complex though.
|
||||
data PType = Forall (Set Name) MType -- ^ ∀{α}. τ
|
||||
|
||||
-- | >>> putPprLn (Forall ["a"] (TFun "a" "a"))
|
||||
-- ∀a. a → a
|
||||
instance Pretty PType where
|
||||
ppr (Forall qs mType) = "∀" <> pprUniversals <> ". " <> ppr mType
|
||||
where
|
||||
pprUniversals
|
||||
| S.null qs = "∅"
|
||||
| otherwise = (T.intercalate " " . map ppr . S.toList) qs
|
||||
|
||||
|
||||
|
||||
-- | The free variables of a 'PType' are the free variables of the contained
|
||||
-- 'MType', except those universally quantified.
|
||||
--
|
||||
-- >>> let sigma = Forall ["a"] (TFun "a" (TFun (TTuple "b" "a") "c"))
|
||||
-- >>> putPprLn sigma
|
||||
-- ∀a. a → (b, a) → c
|
||||
-- >>> let display = T.putStrLn . T.intercalate ", " . foldMap (\x -> [ppr x])
|
||||
-- >>> display (freePType sigma)
|
||||
-- b, c
|
||||
freePType :: PType -> Set Name
|
||||
freePType (Forall qs mType) = freeMType mType `S.difference` qs
|
||||
|
||||
|
||||
|
||||
-- | Substitute all the free type variables.
|
||||
instance Substitutable PType where
|
||||
applySubst (Subst subst) (Forall qs mType) =
|
||||
let qs' = M.fromSet (const ()) qs
|
||||
subst' = Subst (subst `M.difference` qs')
|
||||
in Forall qs (applySubst subst' mType)
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** The environment
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | The environment consists of all the values available in scope, and their
|
||||
-- associated polytypes. Other common names for it include "(typing) context",
|
||||
-- and because of the commonly used symbol for it sometimes directly
|
||||
-- \"Gamma"/@"Γ"@.
|
||||
--
|
||||
-- There are two kinds of membership in an environment,
|
||||
--
|
||||
-- - @∈@: an environment @Γ@ can be viewed as a set of @(value, type)@ pairs,
|
||||
-- and we can test whether something is /literally contained/ by it via
|
||||
-- x:σ ∈ Γ
|
||||
-- - @⊢@, pronounced /entails/, describes all the things that are well-typed,
|
||||
-- given an environment @Γ@. @Γ ⊢ x:τ@ can thus be seen as a judgement that
|
||||
-- @x:τ@ is /figuratively contained/ in @Γ@.
|
||||
--
|
||||
-- For example, the environment @{x:Int}@ literally contains @x@, but given
|
||||
-- this, it also entails @λy. x@, @λy z. x@, @let id = λy. y in id x@ and so on.
|
||||
--
|
||||
-- In Haskell terms, the environment consists of all the things you currently
|
||||
-- have available, or that can be built by comining them. If you import the
|
||||
-- Prelude, your environment entails
|
||||
--
|
||||
-- @
|
||||
-- id → ∀a. a→a
|
||||
-- map → ∀a b. (a→b) → [a] → [b]
|
||||
-- putStrLn → ∀∅. String → IO ()
|
||||
-- …
|
||||
-- id map → ∀a b. (a→b) → [a] → [b]
|
||||
-- map putStrLn → ∀∅. [String] -> [IO ()]
|
||||
-- …
|
||||
-- @
|
||||
newtype Env = Env (Map Name PType)
|
||||
|
||||
-- | >>> :{
|
||||
-- putPprLn (Env
|
||||
-- [ ("id", Forall ["a"] (TFun "a" "a"))
|
||||
-- , ("const", Forall ["a", "b"] (TFun "a" (TFun "b" "a"))) ])
|
||||
-- :}
|
||||
-- Γ = { const : ∀a b. a → b → a
|
||||
-- , id : ∀a. a → a }
|
||||
instance Pretty Env where
|
||||
ppr (Env env) = "Γ = { " <> T.intercalate "\n , " pprBindings <> " }"
|
||||
where
|
||||
bindings = M.assocs env
|
||||
pprBinding (name, pType) = ppr name <> " : " <> ppr pType
|
||||
pprBindings = map pprBinding bindings
|
||||
|
||||
|
||||
|
||||
-- | The free variables of an 'Env'ironment are all the free variables of the
|
||||
-- 'PType's it contains.
|
||||
freeEnv :: Env -> Set Name
|
||||
freeEnv (Env env) = let allPTypes = M.elems env
|
||||
in S.unions (map freePType allPTypes)
|
||||
|
||||
|
||||
|
||||
-- | Performing a 'Subst'itution in an 'Env'ironment means performing that
|
||||
-- substituion on all the contained 'PType's.
|
||||
instance Substitutable Env where
|
||||
applySubst s (Env env) = Env (M.map (applySubst s) env)
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Substitutions
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | A substitution is a mapping from type variables to 'MType's. Applying a
|
||||
-- substitution means applying those replacements. For example, the substitution
|
||||
-- @a -> Int@ applied to @a -> a@ yields the result @Int -> Int@.
|
||||
--
|
||||
-- A key concept behind Hindley-Milner is that once we dive deeper into an
|
||||
-- expression, we learn more about our type variables. We might learn that @a@
|
||||
-- has to be specialized to @b -> b@, and then later on that @b@ is actually
|
||||
-- @Int@. Substitutions are an organized way of carrying this information along.
|
||||
newtype Subst = Subst (Map Name MType)
|
||||
|
||||
|
||||
|
||||
-- | We're going to apply substitutions to a variety of other values that
|
||||
-- somehow contain type variables, so we overload this application operation in
|
||||
-- a class here.
|
||||
--
|
||||
-- Laws:
|
||||
--
|
||||
-- @
|
||||
-- 'applySubst' 'mempty' ≡ 'id'
|
||||
-- 'applySubst' (s1 '<>' s2) ≡ 'applySubst' s1 . 'applySubst' s2
|
||||
-- @
|
||||
class Substitutable a where
|
||||
applySubst :: Subst -> a -> a
|
||||
|
||||
instance (Substitutable a, Substitutable b) => Substitutable (a,b) where
|
||||
applySubst s (x,y) = (applySubst s x, applySubst s y)
|
||||
|
||||
-- | @'applySubst' s1 s2@ applies one substitution to another, replacing all the
|
||||
-- bindings in the second argument @s2@ with their values mentioned in the first
|
||||
-- one (@s1@).
|
||||
instance Substitutable Subst where
|
||||
applySubst s (Subst target) = Subst (fmap (applySubst s) target)
|
||||
|
||||
-- | >>> :{
|
||||
-- putPprLn (Subst
|
||||
-- [ ("a", TFun "b" "b")
|
||||
-- , ("b", TEither "c" "d") ])
|
||||
-- :}
|
||||
-- { a ––> b → b
|
||||
-- , b ––> Either c d }
|
||||
instance Pretty Subst where
|
||||
ppr (Subst s) = "{ " <> T.intercalate "\n, " [ ppr k <> " ––> " <> ppr v | (k,v) <- M.toList s ] <> " }"
|
||||
|
||||
-- | Combine two substitutions by applying all substitutions mentioned in the
|
||||
-- first argument to the type variables contained in the second.
|
||||
instance Monoid Subst where
|
||||
-- Considering that all we can really do with a substitution is apply it, we
|
||||
-- can use the one of 'Substitutable's laws to show that substitutions
|
||||
-- combine associatively,
|
||||
--
|
||||
-- @
|
||||
-- applySubst (compose s1 (compose s2 s3))
|
||||
-- = applySubst s1 . applySubst (compose s2 s3)
|
||||
-- = applySubst s1 . applySubst s2 . applySubst s3
|
||||
-- = applySubst (compose s1 s2) . applySubst s3
|
||||
-- = applySubst (compose (compose s1 s2) s3)
|
||||
-- @
|
||||
mappend subst1 subst2 = Subst (s1 `M.union` s2)
|
||||
where
|
||||
Subst s1 = subst1
|
||||
Subst s2 = applySubst subst1 subst2
|
||||
|
||||
mempty = Subst M.empty
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
-- * Typechecking
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
|
||||
-- $ Typechecking does two things:
|
||||
--
|
||||
-- 1. If two types are not immediately identical, attempt to 'unify' them
|
||||
-- to get a type compatible with both of them
|
||||
-- 2. 'infer' the most general type of a value by comparing the values in its
|
||||
-- definition with the 'Env'ironment
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Inference context
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | The inference type holds a supply of unique names, and can fail with a
|
||||
-- descriptive error if something goes wrong.
|
||||
--
|
||||
-- /Invariant:/ the supply must be infinite, or we might run out of names to
|
||||
-- give to things.
|
||||
newtype Infer a = Infer (ExceptT InferError (State [Name]) a)
|
||||
deriving (Functor, Applicative, Monad)
|
||||
|
||||
-- | Errors that can happen during the type inference process.
|
||||
data InferError =
|
||||
-- | Two types that don't match were attempted to be unified.
|
||||
--
|
||||
-- For example, @a -> a@ and @Int@ do not unify.
|
||||
--
|
||||
-- >>> putPprLn (CannotUnify (TFun "a" "a") (TConst "Int"))
|
||||
-- Cannot unify a → a with Int
|
||||
CannotUnify MType MType
|
||||
|
||||
-- | A 'TVar' is bound to an 'MType' that already contains it.
|
||||
--
|
||||
-- The canonical example of this is @λx. x x@, where the first @x@
|
||||
-- in the body has to have type @a -> b@, and the second one @a@. Since
|
||||
-- they're both the same @x@, this requires unification of @a@ with
|
||||
-- @a -> b@, which only works if @a = a -> b = (a -> b) -> b = …@, yielding
|
||||
-- an infinite type.
|
||||
--
|
||||
-- >>> putPprLn (OccursCheckFailed "a" (TFun "a" "a"))
|
||||
-- Occurs check failed: a already appears in a → a
|
||||
| OccursCheckFailed Name MType
|
||||
|
||||
-- | The value of an unknown identifier was read.
|
||||
--
|
||||
-- >>> putPprLn (UnknownIdentifier "a")
|
||||
-- Unknown identifier: a
|
||||
| UnknownIdentifier Name
|
||||
deriving Show
|
||||
|
||||
-- | >>> putPprLn (CannotUnify (TEither "a" "b") (TTuple "a" "b"))
|
||||
-- Cannot unify Either a b with (a, b)
|
||||
instance Pretty InferError where
|
||||
ppr = \case
|
||||
CannotUnify t1 t2 ->
|
||||
"Cannot unify " <> ppr t1 <> " with " <> ppr t2
|
||||
OccursCheckFailed name ty ->
|
||||
"Occurs check failed: " <> ppr name <> " already appears in " <> ppr ty
|
||||
UnknownIdentifier name ->
|
||||
"Unknown identifier: " <> ppr name
|
||||
|
||||
|
||||
|
||||
-- | Evaluate a value in an 'Infer'ence context.
|
||||
--
|
||||
-- >>> let expr = EAbs "f" (EAbs "g" (EAbs "x" (EApp (EApp "f" "x") (EApp "g" "x"))))
|
||||
-- >>> putPprLn expr
|
||||
-- λf g x. f x (g x)
|
||||
-- >>> let inferred = runInfer (infer (Env []) expr)
|
||||
-- >>> let demonstrate = \case Right (_, ty) -> T.putStrLn (":: " <> ppr ty)
|
||||
-- >>> demonstrate inferred
|
||||
-- :: (c → e → f) → (c → e) → c → f
|
||||
runInfer :: Infer a -- ^ Inference data
|
||||
-> Either InferError a
|
||||
runInfer (Infer inf) =
|
||||
evalState (runExceptT inf) (map Name (infiniteSupply alphabet))
|
||||
where
|
||||
|
||||
alphabet = map T.singleton ['a'..'z']
|
||||
|
||||
-- [a, b, c] ==> [a,b,c, a1,b1,c1, a2,b2,c2, …]
|
||||
infiniteSupply supply = supply <> addSuffixes supply (1 :: Integer)
|
||||
where
|
||||
addSuffixes xs n = map (\x -> addSuffix x n) xs <> addSuffixes xs (n+1)
|
||||
addSuffix x n = x <> T.pack (show n)
|
||||
|
||||
|
||||
|
||||
-- | Throw an 'InferError' in an 'Infer'ence context.
|
||||
--
|
||||
-- >>> case runInfer (throw (UnknownIdentifier "var")) of Left err -> putPprLn err
|
||||
-- Unknown identifier: var
|
||||
throw :: InferError -> Infer a
|
||||
throw = Infer . throwE
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Unification
|
||||
-- #############################################################################
|
||||
|
||||
-- $ Unification describes the process of making two different types compatible
|
||||
-- by specializing them where needed. A desirable property to have here is being
|
||||
-- able to find the most general unifier. Luckily, we'll be able to do that in
|
||||
-- our type system.
|
||||
|
||||
|
||||
|
||||
-- | The unification of two 'MType's is the most general substituion that can be
|
||||
-- applied to both of them in order to yield the same result.
|
||||
--
|
||||
-- >>> let m1 = TFun "a" "b"
|
||||
-- >>> putPprLn m1
|
||||
-- a → b
|
||||
-- >>> let m2 = TFun "c" (TEither "d" "e")
|
||||
-- >>> putPprLn m2
|
||||
-- c → Either d e
|
||||
-- >>> let inferSubst = unify (m1, m2)
|
||||
-- >>> case runInfer inferSubst of Right subst -> putPprLn subst
|
||||
-- { a ––> c
|
||||
-- , b ––> Either d e }
|
||||
unify :: (MType, MType) -> Infer Subst
|
||||
unify = \case
|
||||
(TFun a b, TFun x y) -> unifyBinary (a,b) (x,y)
|
||||
(TVar v, x) -> v `bindVariableTo` x
|
||||
(x, TVar v) -> v `bindVariableTo` x
|
||||
(TConst a, TConst b) | a == b -> pure mempty
|
||||
(TList a, TList b) -> unify (a,b)
|
||||
(TEither a b, TEither x y) -> unifyBinary (a,b) (x,y)
|
||||
(TTuple a b, TTuple x y) -> unifyBinary (a,b) (x,y)
|
||||
(a, b) -> throw (CannotUnify a b)
|
||||
|
||||
where
|
||||
|
||||
-- Unification of binary type constructors, such as functions and Either.
|
||||
-- Unification is first done for the first operand, and assuming the
|
||||
-- required substitution, for the second one.
|
||||
unifyBinary :: (MType, MType) -> (MType, MType) -> Infer Subst
|
||||
unifyBinary (a,b) (x,y) = do
|
||||
s1 <- unify (a, x)
|
||||
s2 <- unify (applySubst s1 (b, y))
|
||||
pure (s1 <> s2)
|
||||
|
||||
|
||||
|
||||
-- | Build a 'Subst'itution that binds a 'Name' of a 'TVar' to an 'MType'. The
|
||||
-- resulting substitution should be idempotent, i.e. applying it more than once
|
||||
-- to something should not be any different from applying it only once.
|
||||
--
|
||||
-- - In the simplest case, this just means building a substitution that just
|
||||
-- does that.
|
||||
-- - Substituting a 'Name' with a 'TVar' with the same name unifies a type
|
||||
-- variable with itself, and the resulting substitution does nothing new.
|
||||
-- - If the 'Name' we're trying to bind to an 'MType' already occurs in that
|
||||
-- 'MType', the resulting substitution would not be idempotent: the 'MType'
|
||||
-- would be replaced again, yielding a different result. This is known as the
|
||||
-- Occurs Check.
|
||||
bindVariableTo :: Name -> MType -> Infer Subst
|
||||
|
||||
bindVariableTo name (TVar v) | boundToSelf = pure mempty
|
||||
where
|
||||
boundToSelf = name == v
|
||||
|
||||
bindVariableTo name mType | name `occursIn` mType = throw (OccursCheckFailed name mType)
|
||||
where
|
||||
n `occursIn` ty = n `S.member` freeMType ty
|
||||
|
||||
bindVariableTo name mType = pure (Subst (M.singleton name mType))
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Type inference
|
||||
-- #############################################################################
|
||||
|
||||
-- $ Type inference is the act of finding out a value's type by looking at the
|
||||
-- environment it is in, in order to make it compatible with it.
|
||||
--
|
||||
-- In literature, the Hindley-Damas-Milner inference algorithm ("Algorithm W")
|
||||
-- is often presented in the style of logical formulas, and below you'll find
|
||||
-- that version along with code that actually does what they say.
|
||||
--
|
||||
-- These formulas look a bit like fractions, where the "numerator" is a
|
||||
-- collection of premises, and the denominator is the consequence if all of them
|
||||
-- hold.
|
||||
--
|
||||
-- __Example:__
|
||||
--
|
||||
-- @
|
||||
-- Γ ⊢ even : Int → Bool Γ ⊢ 1 : Int
|
||||
-- –––––––––––––––––––––––––––––––––––
|
||||
-- Γ ⊢ even 1 : Bool
|
||||
-- @
|
||||
--
|
||||
-- means that if we have a value of type @Int -> Bool@ called "even" and a value
|
||||
-- of type @Int@ called @1@, then we also have a value of type @Bool@ via
|
||||
-- @even 1@ available to us.
|
||||
--
|
||||
-- The actual inference rules are polymorphic versions of this example, and
|
||||
-- the code comments will explain each step in detail.
|
||||
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- *** The language: typed lambda calculus
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
-- | The syntax tree of the language we'd like to typecheck. You can view it as
|
||||
-- a close relative to simply typed lambda calculus, having only the most
|
||||
-- necessary syntax elements.
|
||||
--
|
||||
-- Since 'ELet' is non-recursive, the usual fixed-point function
|
||||
-- @fix : (a → a) → a@ can be introduced to allow recursive definitions.
|
||||
data Exp = ELit Lit -- ^ True, 1
|
||||
| EVar Name -- ^ @x@
|
||||
| EApp Exp Exp -- ^ @f x@
|
||||
| EAbs Name Exp -- ^ @λx. e@
|
||||
| ELet Name Exp Exp -- ^ @let x = e in e'@ (non-recursive)
|
||||
deriving Show
|
||||
|
||||
|
||||
|
||||
-- | Literals we'd like to support. Since we can't define new data types in our
|
||||
-- simple type system, we'll have to hard-code the possible ones here.
|
||||
data Lit = LBool Bool
|
||||
| LInteger Integer
|
||||
deriving Show
|
||||
|
||||
|
||||
|
||||
-- | >>> putPprLn (EAbs "f" (EAbs "g" (EAbs "x" (EApp (EApp "f" "x") (EApp "g" "x")))))
|
||||
-- λf g x. f x (g x)
|
||||
instance Pretty Exp where
|
||||
ppr (ELit lit) = ppr lit
|
||||
|
||||
ppr (EVar name) = ppr name
|
||||
|
||||
ppr (EApp f x) = pprApp1 f <> " " <> pprApp2 x
|
||||
where
|
||||
pprApp1 = \case
|
||||
eLet@ELet{} -> "(" <> ppr eLet <> ")"
|
||||
eLet@EAbs{} -> "(" <> ppr eLet <> ")"
|
||||
e -> ppr e
|
||||
pprApp2 = \case
|
||||
eApp@EApp{} -> "(" <> ppr eApp <> ")"
|
||||
e -> pprApp1 e
|
||||
|
||||
ppr x@EAbs{} = pprAbs True x
|
||||
where
|
||||
pprAbs True (EAbs name expr) = "λ" <> ppr name <> pprAbs False expr
|
||||
pprAbs False (EAbs name expr) = " " <> ppr name <> pprAbs False expr
|
||||
pprAbs _ expr = ". " <> ppr expr
|
||||
|
||||
ppr (ELet name value body) =
|
||||
"let " <> ppr name <> " = " <> ppr value <> " in " <> ppr body
|
||||
|
||||
-- | >>> putPprLn (LBool True)
|
||||
-- True
|
||||
--
|
||||
-- >>> putPprLn (LInteger 127)
|
||||
-- 127
|
||||
instance Pretty Lit where
|
||||
ppr = \case
|
||||
LBool b -> showT b
|
||||
LInteger i -> showT i
|
||||
where
|
||||
showT :: Show a => a -> Text
|
||||
showT = T.pack . show
|
||||
|
||||
-- | >>> "var" :: Exp
|
||||
-- EVar (Name "var")
|
||||
instance IsString Exp where
|
||||
fromString = EVar . fromString
|
||||
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- *** Some useful definitions
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
-- | Generate a fresh 'Name' in a type 'Infer'ence context. An example use case
|
||||
-- of this is η expansion, which transforms @f@ into @λx. f x@, where "x" is a
|
||||
-- new name, i.e. unbound in the current context.
|
||||
fresh :: Infer MType
|
||||
fresh = drawFromSupply >>= \case
|
||||
Right name -> pure (TVar name)
|
||||
Left err -> throw err
|
||||
|
||||
where
|
||||
|
||||
drawFromSupply :: Infer (Either InferError Name)
|
||||
drawFromSupply = Infer (do
|
||||
s:upply <- lift get
|
||||
lift (put upply)
|
||||
pure (Right s) )
|
||||
|
||||
|
||||
|
||||
-- | Add a new binding to the environment.
|
||||
--
|
||||
-- The Haskell equivalent would be defining a new value, for example in module
|
||||
-- scope or in a @let@ block. This corresponds to the "comma" operation used in
|
||||
-- formal notation,
|
||||
--
|
||||
-- @
|
||||
-- Γ, x:σ ≡ extendEnv Γ (x,σ)
|
||||
-- @
|
||||
extendEnv :: Env -> (Name, PType) -> Env
|
||||
extendEnv (Env env) (name, pType) = Env (M.insert name pType env)
|
||||
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- *** Inferring the types of all language constructs
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
-- | Infer the type of an 'Exp'ression in an 'Env'ironment, resulting in the
|
||||
-- 'Exp's 'MType' along with a substitution that has to be done in order to reach
|
||||
-- this goal.
|
||||
--
|
||||
-- This is widely known as /Algorithm W/.
|
||||
infer :: Env -> Exp -> Infer (Subst, MType)
|
||||
infer env = \case
|
||||
ELit lit -> inferLit lit
|
||||
EVar name -> inferVar env name
|
||||
EApp f x -> inferApp env f x
|
||||
EAbs x e -> inferAbs env x e
|
||||
ELet x e e' -> inferLet env x e e'
|
||||
|
||||
|
||||
|
||||
-- | Literals such as 'True' and '1' have their types hard-coded.
|
||||
inferLit :: Lit -> Infer (Subst, MType)
|
||||
inferLit lit = pure (mempty, TConst litTy)
|
||||
where
|
||||
litTy = case lit of
|
||||
LBool {} -> "Bool"
|
||||
LInteger {} -> "Integer"
|
||||
|
||||
|
||||
|
||||
-- | Inferring the type of a variable is done via
|
||||
--
|
||||
-- @
|
||||
-- x:σ ∈ Γ τ = instantiate(σ)
|
||||
-- –––––––––––––––––––––––––––– [Var]
|
||||
-- Γ ⊢ x:τ
|
||||
-- @
|
||||
--
|
||||
-- This means that if @Γ@ /literally contains/ (@∈@) a value, then it also
|
||||
-- /entails it/ (@⊢@) in all its instantiations.
|
||||
inferVar :: Env -> Name -> Infer (Subst, MType)
|
||||
inferVar env name = do
|
||||
sigma <- lookupEnv env name -- x:σ ∈ Γ
|
||||
tau <- instantiate sigma -- τ = instantiate(σ)
|
||||
-- ------------------
|
||||
pure (mempty, tau) -- Γ ⊢ x:τ
|
||||
|
||||
|
||||
|
||||
-- | Look up the 'PType' of a 'Name' in the 'Env'ironment.
|
||||
--
|
||||
-- This checks whether @x:σ@ is /literally contained/ in @Γ@. For more details
|
||||
-- about this, see the documentation of 'Env'.
|
||||
--
|
||||
-- To give a Haskell analogon, looking up @id@ when @Prelude@ is loaded, the
|
||||
-- resulting 'PType' would be @id@'s type, namely @forall a. a -> a@.
|
||||
lookupEnv :: Env -> Name -> Infer PType
|
||||
lookupEnv (Env env) name = case M.lookup name env of
|
||||
Just x -> pure x
|
||||
Nothing -> throw (UnknownIdentifier name)
|
||||
|
||||
|
||||
|
||||
-- | Bind all quantified variables of a 'PType' to 'fresh' type variables.
|
||||
--
|
||||
-- __Example:__ instantiating @forall a. a -> b -> a@ results in the 'MType'
|
||||
-- @c -> b -> c@, where @c@ is a fresh name (to avoid shadowing issues).
|
||||
--
|
||||
-- You can picture the 'PType' to be the prototype converted to an instantiated
|
||||
-- 'MType', which can now be used in the unification process.
|
||||
--
|
||||
-- Another way of looking at it is by simply forgetting which variables were
|
||||
-- quantified, carefully avoiding name clashes when doing so.
|
||||
--
|
||||
-- 'instantiate' can also be seen as the opposite of 'generalize', which we'll
|
||||
-- need later to convert an 'MType' to a 'PType'.
|
||||
instantiate :: PType -> Infer MType
|
||||
instantiate (Forall qs t) = do
|
||||
subst <- substituteAllWithFresh qs
|
||||
pure (applySubst subst t)
|
||||
|
||||
where
|
||||
-- For each given name, add a substitution from that name to a fresh type
|
||||
-- variable to the result.
|
||||
substituteAllWithFresh :: Set Name -> Infer Subst
|
||||
substituteAllWithFresh xs = do
|
||||
let freshSubstActions = M.fromSet (const fresh) xs
|
||||
freshSubsts <- sequenceA freshSubstActions
|
||||
pure (Subst freshSubsts)
|
||||
|
||||
|
||||
|
||||
-- | Function application captures the fact that if we have a function and an
|
||||
-- argument we can give to that function, we also have the result value of the
|
||||
-- result type available to us.
|
||||
--
|
||||
-- @
|
||||
-- Γ ⊢ f : fτ Γ ⊢ x : xτ fxτ = fresh unify(fτ, xτ → fxτ)
|
||||
-- ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– [App]
|
||||
-- Γ ⊢ f x : fxτ
|
||||
-- @
|
||||
--
|
||||
-- This rule says that given a function and a value with a type, the function
|
||||
-- type has to unify with a function type that allows the value type to be its
|
||||
-- argument.
|
||||
inferApp
|
||||
:: Env
|
||||
-> Exp -- ^ __f__ x
|
||||
-> Exp -- ^ f __x__
|
||||
-> Infer (Subst, MType)
|
||||
inferApp env f x = do
|
||||
(s1, fTau) <- infer env f -- f : fτ
|
||||
(s2, xTau) <- infer (applySubst s1 env) x -- x : xτ
|
||||
fxTau <- fresh -- fxτ = fresh
|
||||
s3 <- unify (applySubst s2 fTau, TFun xTau fxTau) -- unify (fτ, xτ → fxτ)
|
||||
let s = s3 <> s2 <> s1 -- --------------------
|
||||
pure (s, applySubst s3 fxTau) -- f x : fxτ
|
||||
|
||||
|
||||
|
||||
-- | Lambda abstraction is based on the fact that when we introduce a new
|
||||
-- variable, the resulting lambda maps from that variable's type to the type of
|
||||
-- the body.
|
||||
--
|
||||
-- @
|
||||
-- τ = fresh σ = ∀∅. τ Γ, x:σ ⊢ e:τ'
|
||||
-- ––––––––––––––––––––––––––––––––––––– [Abs]
|
||||
-- Γ ⊢ λx.e : τ→τ'
|
||||
-- @
|
||||
--
|
||||
-- Here, @Γ, x:τ@ is @Γ@ extended by one additional mapping, namely @x:τ@.
|
||||
--
|
||||
-- Abstraction is typed by extending the environment by a new 'MType', and if
|
||||
-- under this assumption we can construct a function mapping to a value of that
|
||||
-- type, we can say that the lambda takes a value and maps to it.
|
||||
inferAbs
|
||||
:: Env
|
||||
-> Name -- ^ λ__x__. e
|
||||
-> Exp -- ^ λx. __e__
|
||||
-> Infer (Subst, MType)
|
||||
inferAbs env x e = do
|
||||
tau <- fresh -- τ = fresh
|
||||
let sigma = Forall [] tau -- σ = ∀∅. τ
|
||||
env' = extendEnv env (x, sigma) -- Γ, x:σ …
|
||||
(s, tau') <- infer env' e -- … ⊢ e:τ'
|
||||
-- ---------------
|
||||
pure (s, TFun (applySubst s tau) tau') -- λx.e : τ→τ'
|
||||
|
||||
|
||||
|
||||
-- | A let binding allows extending the environment with new bindings in a
|
||||
-- principled manner. To do this, we first have to typecheck the expression to
|
||||
-- be introduced. The result of this is then generalized to a 'PType', since let
|
||||
-- bindings introduce new polymorphic values, which are then added to the
|
||||
-- environment. Now we can finally typecheck the body of the "in" part of the
|
||||
-- let binding.
|
||||
--
|
||||
-- Note that in our simple language, let is non-recursive, but recursion can be
|
||||
-- introduced as usual by adding a primitive @fix : (a → a) → a@ if desired.
|
||||
--
|
||||
-- @
|
||||
-- Γ ⊢ e:τ σ = gen(Γ,τ) Γ, x:σ ⊢ e':τ'
|
||||
-- ––––––––––––––––––––––––––––––––––––––– [Let]
|
||||
-- Γ ⊢ let x = e in e' : τ'
|
||||
-- @
|
||||
inferLet
|
||||
:: Env
|
||||
-> Name -- ^ let __x__ = e in e'
|
||||
-> Exp -- ^ let x = __e__ in e'
|
||||
-> Exp -- ^ let x = e in __e'__
|
||||
-> Infer (Subst, MType)
|
||||
inferLet env x e e' = do
|
||||
(s1, tau) <- infer env e -- Γ ⊢ e:τ
|
||||
let env' = applySubst s1 env
|
||||
let sigma = generalize env' tau -- σ = gen(Γ,τ)
|
||||
let env'' = extendEnv env' (x, sigma) -- Γ, x:σ
|
||||
(s2, tau') <- infer env'' e' -- Γ ⊢ …
|
||||
-- --------------------------
|
||||
pure (s2 <> s1, tau') -- … let x = e in e' : τ'
|
||||
|
||||
|
||||
|
||||
-- | Generalize an 'MType' to a 'PType' by universally quantifying over all the
|
||||
-- type variables contained in it, except those already free in the environment.
|
||||
--
|
||||
-- >>> let tau = TFun "a" (TFun "b" "a")
|
||||
-- >>> putPprLn tau
|
||||
-- a → b → a
|
||||
-- >>> putPprLn (generalize (Env [("x", Forall [] "b")]) tau)
|
||||
-- ∀a. a → b → a
|
||||
--
|
||||
-- In more formal notation,
|
||||
--
|
||||
-- @
|
||||
-- gen(Γ,τ) = ∀{α}. τ
|
||||
-- where {α} = free(τ) – free(Γ)
|
||||
-- @
|
||||
--
|
||||
-- 'generalize' can also be seen as the opposite of 'instantiate', which
|
||||
-- converts a 'PType' to an 'MType'.
|
||||
generalize :: Env -> MType -> PType
|
||||
generalize env mType = Forall qs mType
|
||||
where
|
||||
qs = freeMType mType `S.difference` freeEnv env
|
185
Main.hs
185
Main.hs
@ -1,185 +0,0 @@
|
||||
{-# LANGUAGE OverloadedLists #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Main where
|
||||
|
||||
|
||||
|
||||
import qualified Data.Map as M
|
||||
import Data.Monoid
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text.IO as T
|
||||
|
||||
import HindleyMilner
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
-- * Testing
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** A small custom Prelude
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
prelude :: Env
|
||||
prelude = Env (M.fromList
|
||||
[ ("(*)", Forall [] (tInteger ~> tInteger ~> tInteger))
|
||||
, ("(+)", Forall [] (tInteger ~> tInteger ~> tInteger))
|
||||
, ("(,)", Forall ["a","b"] ("a" ~> "b" ~> TTuple "a" "b"))
|
||||
, ("(-)", Forall [] (tInteger ~> tInteger ~> tInteger))
|
||||
, ("(.)", Forall ["a", "b", "c"] (("b" ~> "c") ~> ("a" ~> "b") ~> "a" ~> "c"))
|
||||
, ("(<)", Forall [] (tInteger ~> tInteger ~> tBool))
|
||||
, ("(<=)", Forall [] (tInteger ~> tInteger ~> tBool))
|
||||
, ("(>)", Forall [] (tInteger ~> tInteger ~> tBool))
|
||||
, ("(>=)", Forall [] (tInteger ~> tInteger ~> tBool))
|
||||
, ("const", Forall ["a","b"] ("a" ~> "b" ~> "a"))
|
||||
, ("Cont/>>=", Forall ["a"] ((("a" ~> "r") ~> "r") ~> ("a" ~> (("b" ~> "r") ~> "r")) ~> (("b" ~> "r") ~> "r")))
|
||||
, ("find", Forall ["a","b"] (("a" ~> tBool) ~> TList "a" ~> tMaybe "a"))
|
||||
, ("fix", Forall ["a"] (("a" ~> "a") ~> "a"))
|
||||
, ("foldr", Forall ["a","b"] (("a" ~> "b" ~> "b") ~> "b" ~> TList "a" ~> "b"))
|
||||
, ("id", Forall ["a"] ("a" ~> "a"))
|
||||
, ("ifThenElse", Forall ["a"] (tBool ~> "a" ~> "a" ~> "a"))
|
||||
, ("Left", Forall ["a","b"] ("a" ~> TEither "a" "b"))
|
||||
, ("length", Forall ["a"] (TList "a" ~> tInteger))
|
||||
, ("map", Forall ["a","b"] (("a" ~> "b") ~> TList "a" ~> TList "b"))
|
||||
, ("reverse", Forall ["a"] (TList "a" ~> TList "a"))
|
||||
, ("Right", Forall ["a","b"] ("b" ~> TEither "a" "b"))
|
||||
, ("[]", Forall ["a"] (TList "a"))
|
||||
, ("(:)", Forall ["a"] ("a" ~> TList "a" ~> TList "a"))
|
||||
])
|
||||
where
|
||||
tBool = TConst "Bool"
|
||||
tInteger = TConst "Integer"
|
||||
tMaybe = TEither (TConst "()")
|
||||
|
||||
|
||||
|
||||
-- | Synonym for 'TFun' to make writing type signatures easier.
|
||||
--
|
||||
-- Instead of
|
||||
--
|
||||
-- @
|
||||
-- Forall ["a","b"] (TFun "a" (TFun "b" "a"))
|
||||
-- @
|
||||
--
|
||||
-- we can write
|
||||
--
|
||||
-- @
|
||||
-- Forall ["a","b"] ("a" ~> "b" ~> "a")
|
||||
-- @
|
||||
(~>) :: MType -> MType -> MType
|
||||
(~>) = TFun
|
||||
infixr 9 ~>
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Run it!
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | Run type inference on a cuple of values
|
||||
main :: IO ()
|
||||
main = do
|
||||
let inferAndPrint = T.putStrLn . (" " <>) . showType prelude
|
||||
T.putStrLn "Well-typed:"
|
||||
do
|
||||
inferAndPrint (lambda ["x"] "x")
|
||||
inferAndPrint (lambda ["f","g","x"] (apply "f" ["x", apply "g" ["x"]]))
|
||||
inferAndPrint (lambda ["f","g","x"] (apply "f" [apply "g" ["x"]]))
|
||||
inferAndPrint (lambda ["m", "k", "c"] (apply "m" [lambda ["x"] (apply "k" ["x", "c"])])) -- >>= for Cont
|
||||
inferAndPrint (lambda ["f"] (apply "(.)" ["reverse", apply "map" ["f"]]))
|
||||
inferAndPrint (apply "find" [lambda ["x"] (apply "(>)" ["x", int 0])])
|
||||
inferAndPrint (apply "map" [apply "map" ["map"]])
|
||||
inferAndPrint (apply "(*)" [int 1, int 2])
|
||||
inferAndPrint (apply "foldr" ["(+)", int 0])
|
||||
inferAndPrint (apply "map" ["length"])
|
||||
inferAndPrint (apply "map" ["map"])
|
||||
inferAndPrint (lambda ["x"] (apply "ifThenElse" [apply "(<)" ["x", int 0], int 0, "x"]))
|
||||
inferAndPrint (lambda ["x"] (apply "fix" [lambda ["xs"] (apply "(:)" ["x", "xs"])]))
|
||||
T.putStrLn "Ill-typed:"
|
||||
do
|
||||
inferAndPrint (apply "(*)" [int 1, bool True])
|
||||
inferAndPrint (apply "foldr" [int 1])
|
||||
inferAndPrint (lambda ["x"] (apply "x" ["x"]))
|
||||
inferAndPrint (lambda ["x"] (ELet "xs" (apply "(:)" ["x", "xs"]) "xs"))
|
||||
|
||||
|
||||
|
||||
-- | Build multiple lambda bindings.
|
||||
--
|
||||
-- Instead of
|
||||
--
|
||||
-- @
|
||||
-- EAbs "f" (EAbs "x" (EApp "f" "x"))
|
||||
-- @
|
||||
--
|
||||
-- we can write
|
||||
--
|
||||
-- @
|
||||
-- lambda ["f", "x"] (EApp "f" "x")
|
||||
-- @
|
||||
--
|
||||
-- for
|
||||
--
|
||||
-- @
|
||||
-- λf x. f x
|
||||
-- @
|
||||
lambda :: [Name] -> Exp -> Exp
|
||||
lambda names expr = foldr EAbs expr names
|
||||
|
||||
|
||||
|
||||
-- | Apply a function to multiple arguments.
|
||||
--
|
||||
-- Instead of
|
||||
--
|
||||
-- @
|
||||
-- EApp (EApp (EApp "f" "x") "y") "z")
|
||||
-- @
|
||||
--
|
||||
-- we can write
|
||||
--
|
||||
-- @
|
||||
-- apply "f" ["x", "y", "z"]
|
||||
-- @
|
||||
--
|
||||
-- for
|
||||
--
|
||||
-- @
|
||||
-- f x y z
|
||||
-- @
|
||||
apply :: Exp -> [Exp] -> Exp
|
||||
apply = foldl EApp
|
||||
|
||||
|
||||
|
||||
-- | Construct an integer literal.
|
||||
int :: Integer -> Exp
|
||||
int = ELit . LInteger
|
||||
|
||||
|
||||
|
||||
-- | Construct a boolean literal.
|
||||
bool :: Bool -> Exp
|
||||
bool = ELit . LBool
|
||||
|
||||
|
||||
|
||||
-- | Convenience function to run type inference algorithm
|
||||
showType :: Env -- ^ Starting environment, e.g. 'prelude'.
|
||||
-> Exp -- ^ Expression to typecheck
|
||||
-> Text -- ^ Text representation of the result. Contains an error
|
||||
-- message on failure.
|
||||
showType env expr =
|
||||
case (runInfer . fmap (generalize (Env mempty) . uncurry applySubst) . infer env) expr of
|
||||
Left err -> "Error inferring type of " <> ppr expr <>": " <> ppr err
|
||||
Right ty -> ppr expr <> " :: " <> ppr ty
|
112
README.md
112
README.md
@ -1,110 +1,4 @@
|
||||
# Schala - a programming language meta-interpreter
|
||||
# Schala - A Programming Language Implementation
|
||||
|
||||
Schala is a Rust framework written to make it easy to create and experiment
|
||||
with multiple toy programming languages. It provides a cross-language REPL and
|
||||
provisions for tokenizing text, parsing tokens, evaluating an abstract syntax
|
||||
tree, and other tasks that are common to all programming languages, as well as
|
||||
sharing state between multiple programming languages.
|
||||
|
||||
Schala is implemented as a Rust library `schala-repl`, which provides a `Repl`
|
||||
data structure that takes in a value implementing the
|
||||
`ProgrammingLanguageInterface` trait. Individual programming language
|
||||
implementations are Rust types that implement `ProgrammingLanguageInterface`
|
||||
and store whatever persistent state is relevant to that language.
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
Run schala with the normal `cargo run`. This will drop you into a REPL
|
||||
environment. Type `:help` for more information, or type in text in any
|
||||
supported programming language (currently only `schala-lang`) to evaluate it in
|
||||
the REPL.
|
||||
|
||||
### Examples
|
||||
|
||||
Try running the following `schala-lang` code example in the REPL:
|
||||
|
||||
```
|
||||
>> 1 + 1
|
||||
(Total time)=> 736.368µs
|
||||
=> 2
|
||||
>> fn foo(x) { x + 10 }
|
||||
(Total time)=> 772.496µs
|
||||
=>
|
||||
>> foo(0)
|
||||
(Total time)=> 593.591µs
|
||||
=> 10
|
||||
>> 5 + foo(1)
|
||||
(Total time)=> 1.119916ms
|
||||
=> 16
|
||||
>>
|
||||
```
|
||||
|
||||
## History
|
||||
|
||||
Schala started out life as an experiment in writing a Javascript-like
|
||||
programming language that would never encounter any kind of runtime value
|
||||
error, but rather always return `null` under any kind of error condition. I had
|
||||
seen one too many Javascript `Uncaught TypeError: Cannot read property ___ of
|
||||
undefined` messages, and I was a bit frustrated. Plus I had always wanted to
|
||||
write a programming langauge from scratch, and Rust is a fun language to
|
||||
program in. Over time I became interested in playing around with other sorts
|
||||
of programming languages as well, and wanted to make the process as general as
|
||||
possible.
|
||||
|
||||
The name of the project comes from Schala the Princess of Zeal from the 1995
|
||||
SNES RPG *Chrono Trigger*. I like classic JRPGs and enjoyed the thought of
|
||||
creating a language name confusingly close to Scala. The naming scheme for
|
||||
languages implemented with the Schala meta-interpreter is Chrono Trigger
|
||||
characters.
|
||||
|
||||
Schala and languages implemented with it are incomplete alpha software and are
|
||||
not ready for public release.
|
||||
|
||||
## Languages implemented using the meta-interpreter
|
||||
|
||||
* The eponymous *Schala* language is a work-in-progress general purpose
|
||||
programming language with static typing and algebraic data types. Its design
|
||||
goals include having a very straightforward implemenation and being syntactically
|
||||
minimal.
|
||||
|
||||
* *Maaru* is a very simple dynamically-typed scripting language, with the semantics
|
||||
that all runtime errors return a `null` value rather than fail.
|
||||
|
||||
* *Robo* is an experiment in creating a lazy, functional, strongly-typed language
|
||||
much like Haskell
|
||||
|
||||
* *Rukka* is a straightforward LISP implementation
|
||||
|
||||
## Reference works
|
||||
|
||||
Here's a partial list of resources I've made use of in the process
|
||||
of learning how to write a programming language.
|
||||
|
||||
### General
|
||||
|
||||
* http://thume.ca/2019/04/18/writing-a-compiler-in-rust/
|
||||
|
||||
### Type-checking
|
||||
* https://skillsmatter.com/skillscasts/10868-inside-the-rust-compiler
|
||||
* https://www.youtube.com/watch?v=il3gD7XMdmA
|
||||
* http://dev.stephendiehl.com/fun/006_hindley_milner.html
|
||||
* https://rust-lang-nursery.github.io/rustc-guide/type-inference.html
|
||||
* https://eli.thegreenplace.net/2018/unification/
|
||||
* https://eli.thegreenplace.net/2018/type-inference/
|
||||
* http://smallcultfollowing.com/babysteps/blog/2017/03/25/unification-in-chalk-part-1/
|
||||
* http://reasonableapproximation.net/2019/05/05/hindley-milner.html
|
||||
https://rickyhan.com/jekyll/update/2018/05/26/hindley-milner-tutorial-rust.html
|
||||
|
||||
### Evaluation
|
||||
|
||||
* _Understanding Computation_, Tom Stuart, O'Reilly 2013
|
||||
* _Basics of Compiler Design_, Torben Mogensen
|
||||
|
||||
### Parsing
|
||||
* http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
|
||||
* https://soc.github.io/languages/unified-condition-syntax
|
||||
* [Crafting Interpreters](http://www.craftinginterpreters.com/)
|
||||
|
||||
### LLVM
|
||||
* http://blog.ulysse.io/2016/07/03/llvm-getting-started.html
|
||||
`schala` is an implementation of a yet-unnamed quasi-functional programming
|
||||
language.
|
||||
|
213
TODO.md
213
TODO.md
@ -1,213 +0,0 @@
|
||||
# Immediate TODOs / General Code Cleanup
|
||||
|
||||
## Parsing
|
||||
|
||||
* cf. https://siraben.dev/2022/03/22/tree-sitter-linter.html write a tree-sitter parser for Schala
|
||||
|
||||
* Create a macro system, perhaps c.f. Crystal's?
|
||||
* Macro system should be able to implement:
|
||||
* printf-style variadic arguments
|
||||
* something like the Rust/Haskell `Derive` construct
|
||||
* doing useful things with all variants of an enum
|
||||
* (e.g. what https://matklad.github.io//2022/03/26/self-modifying-code.html tries to solve)
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
* Make an automatic (macro-based?) system for numbering compiler errors, this should be every type of error
|
||||
|
||||
## Symbols
|
||||
|
||||
* Add some good printf-debugging impls for SymbolTable-related items
|
||||
|
||||
* the symbol table should probably *only* be for global definitions (maybe rename it to reflect this?)
|
||||
* dealing with variable lookup w/in functions/closures should probably happen in AST -> ReducedAST
|
||||
* b/c that's where we go from a string name to a canonical ID (for e.g. 2nd param in 3rd enclosing scope)
|
||||
|
||||
* In fact to prove this works, the symbol table shoudl _parallelize_ the process of checking subscopes for local items
|
||||
|
||||
* Old notes on a plan of attack:
|
||||
|
||||
1. modify visitor so it can handle scopes
|
||||
-this is needed both to handle import scope correctly
|
||||
-and also to support making FQSNs aware of function parameters
|
||||
|
||||
2. Once FQSNs are aware of function parameters, most of the Rc<String> things in eval.rs can go away
|
||||
|
||||
## Typechecking
|
||||
|
||||
* make a type to represent types rather than relying on string comparisons
|
||||
* look at https://rickyhan.com/jekyll/update/2018/05/26/hindley-milner-tutorial-rust.html
|
||||
|
||||
## General code cleanup
|
||||
* standardize on an error type that isn't String
|
||||
* implement a visitor pattern for the use of scope_resolver
|
||||
* maybe implement this twice: 1) the value-returning, no-default one in the haoyi blogpost,
|
||||
* look at
|
||||
* https://gitlab.haskell.org/ghc/ghc/wikis/pattern-synonyms
|
||||
* the non-value-returning, default one like in rustc (cf. https://github.com/rust-unofficial/patterns/blob/master/patterns/visitor.md)
|
||||
|
||||
|
||||
# Longer-term Ideas
|
||||
|
||||
## Language Syntax
|
||||
|
||||
* the `type` declaration should have some kind of GADT-like syntax
|
||||
* syntactic sugar for typestates? (cf. https://rustype.github.io/notes/notes/rust-typestate-series/rust-typestate-part-1.html )
|
||||
* use `let` sigil to indicate a variable in a pattern explicitly:
|
||||
|
||||
```
|
||||
q is MyStruct(let a, Chrono::Trigga) then {
|
||||
// a is in scope here
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
* if you have a pattern-match where one variant has a variable and the other
|
||||
lacks it instead of treating this as a type error, promote the bound variable
|
||||
to an option type
|
||||
|
||||
* what if there was something like React jsx syntas built in? i.e. a way to
|
||||
automatically transform some kind of markup into a function call, cf. `<h1
|
||||
prop="arg">` -> h1(prop=arg)
|
||||
|
||||
* implement and test open/use statements
|
||||
|
||||
* Include extensible scala-style `html"string ${var}"` string interpolations
|
||||
|
||||
* A neat idea for pattern matching optimization would be if you could match on
|
||||
one of several things in a list
|
||||
ex:
|
||||
```
|
||||
if x {
|
||||
is (comp, LHSPat, RHSPat) if comp in ["==, "<"] -> ...
|
||||
}
|
||||
```
|
||||
|
||||
* Schala should have both currying *and* default arguments!
|
||||
```
|
||||
fn a(b: Int, c:Int, d:Int = 1) -> Int
|
||||
a(1,2) : Int
|
||||
a(1,2,d=2): Int
|
||||
a(_,1,3) : Int -> Int
|
||||
a(1,2, c=_): Int -> Int
|
||||
a(_,_,_) : Int -> Int -> Int -> Int
|
||||
```
|
||||
|
||||
* scoped types - be able to define a quick enum type scoped to a function or other type for
|
||||
something, that only is meant to be used as a quick bespoke interface between
|
||||
two other things
|
||||
|
||||
|
||||
ex.
|
||||
```
|
||||
type enum {
|
||||
type enum MySubVariant {
|
||||
SubVariant1, SubVariant2, etc.
|
||||
}
|
||||
Variant1(MySubVariant),
|
||||
Variant2(...),
|
||||
}
|
||||
```
|
||||
|
||||
* inclusive/exclusive range syntax like .. vs ..=
|
||||
|
||||
* Nameable patterns/ pattern synonyms cf. https://gitlab.haskell.org/ghc/ghc/-/wikis/pattern-synonyms
|
||||
|
||||
|
||||
## Typechecking
|
||||
|
||||
* cf. the notation mentioned in the cardelli paper, the debug information for the `typechecking` pass should
|
||||
* print the generated type variable for every subexpression in an expression
|
||||
* think about idris-related ideas of multiple implementations of a type for an interface (+ vs * impl for monoids, for preorder/inorder/postorder for Foldable)
|
||||
* should have an Idris-like `cast To From` function
|
||||
|
||||
* something like the swift `Never` type ( https://nshipster.com/never/ ) in the stdlib
|
||||
|
||||
|
||||
## Compilation
|
||||
* look into Inkwell for rust LLVM bindings
|
||||
* https://cranelift.readthedocs.io/en/latest/?badge=latest<Paste>
|
||||
|
||||
* look at https://gluon-lang.org/doc/nightly/book/embedding-api.html
|
||||
|
||||
|
||||
# Syntax Playground
|
||||
|
||||
## Trying if-syntax again
|
||||
|
||||
```
|
||||
//simple if expr
|
||||
if x == 10 then "a" else "z"
|
||||
|
||||
//complex if expr
|
||||
if x == 10 then {
|
||||
let a = 1
|
||||
let b = 2
|
||||
a + b
|
||||
} else {
|
||||
55
|
||||
}
|
||||
|
||||
// different comparison ops
|
||||
if x {
|
||||
== 1 then "a"
|
||||
.isPrime() then "b"
|
||||
else "c"
|
||||
}
|
||||
|
||||
/* for now disallow `if x == { 1 then ... }`, b/c hard to parse
|
||||
|
||||
//simple pattern-matching
|
||||
if x is Person("Ivan", age) then age else 0
|
||||
|
||||
//match-block equivalent
|
||||
if x {
|
||||
is Person("Ivan", _) then "Ivan"
|
||||
is Person(_, age) if age > 13 then "barmitzvah'd"
|
||||
else "foo"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## (OLD) Playing around with conditional syntax ideas
|
||||
|
||||
|
||||
- if/match playground
|
||||
|
||||
simple if
|
||||
`if x == 1.0 { "a" } else { "b" }`
|
||||
|
||||
one comparison multiple targets:
|
||||
`if x == { 1.0 -> "a", 2.0 -> "b", else -> "c" }`
|
||||
|
||||
different comparison operators/ method calls:
|
||||
`if x { == 1.0 -> "a", eq NaN -> "n", .hella() -> "h", else -> "z" }`
|
||||
|
||||
pattern matching/introducing bindings:
|
||||
`if alice { .age < 18 -> "18", is Person("Alice", age) -> "${age}", else -> "none" }`
|
||||
|
||||
pattern matching w/ if-let:
|
||||
`if person is Person("Alice", age) { "${age}" } else { "nope" }`
|
||||
|
||||
-https://soc.github.io/languages/unified-condition-syntax syntax:
|
||||
|
||||
`if <cond-expr>" then <then-expr> else <else-expr>`
|
||||
`if <half-expr> \n <rest-expr1> then <result1-expr> \n <rest-expr2> then <result-expr2> else <result3-expr>`
|
||||
-and rest-exprs (or "targets") can have 'is' for pattern-matching, actually so can a full cond-expr
|
||||
|
||||
UNIFIED IF EXPRESSIONS FINAL WORK:
|
||||
|
||||
basic syntax:
|
||||
|
||||
`if_expr := if discriminator '{' (guard_expr)* '}'`
|
||||
`guard_expr := pattern 'then' block_or_expr'`
|
||||
`pattern := rhs | is_pattern`
|
||||
`is_pattern := 'is' ???`
|
||||
`rhs := expression | ???`
|
||||
|
||||
|
||||
if the only two guard patterns are true and false, then the abbreviated syntax:
|
||||
`'if' discriminator 'then' block_or_expr 'else' block_or_expr`
|
||||
can replace `'if' discriminator '{' 'true' 'then' block_or_expr; 'false' 'then' block_or_expr '}'`
|
||||
|
8
experiments/Cargo.toml
Normal file
8
experiments/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "experiments"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
3
experiments/src/main.rs
Normal file
3
experiments/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
48
experiments/tree-sitter-test/grammar.js
Normal file
48
experiments/tree-sitter-test/grammar.js
Normal file
@ -0,0 +1,48 @@
|
||||
module.exports = grammar({
|
||||
name: "TestLang",
|
||||
rules: {
|
||||
source_file: $ => repeat($._definition),
|
||||
|
||||
_definition: $ => choice(
|
||||
$.function_definition
|
||||
//TODO others
|
||||
),
|
||||
|
||||
function_definition: $ => seq(
|
||||
'fn',
|
||||
$.identifier,
|
||||
$.parameter_list,
|
||||
field("return_type", optional($._type)),
|
||||
$.block,
|
||||
),
|
||||
parameter_list: $ => seq("(", /* TODO */ ")"),
|
||||
|
||||
block: $ => seq(
|
||||
"{",
|
||||
choice(
|
||||
repeat($._statement),
|
||||
"",
|
||||
),
|
||||
"}"
|
||||
),
|
||||
|
||||
_statement: $ => choice(
|
||||
$._return_statement
|
||||
),
|
||||
|
||||
_return_statement: $ => seq("return", $._expression, ";"),
|
||||
|
||||
_expression: $ => choice($.identifier, $.unary, $.binary),
|
||||
|
||||
unary: $ => prec(4, choice(seq("-", $._expression), seq("!", $._expression))),
|
||||
binary: $ => choice(prec.left(2, seq($._expression, "*", $._expression)), prec.left(1, seq($._expression, "+", $._expression))),
|
||||
|
||||
_type: $ => "bool",
|
||||
_type: $ => choice(
|
||||
$.primitive_type,
|
||||
),
|
||||
primitive_type: $ => choice("bool", "int"),
|
||||
|
||||
identifier: $ => /[a-z]+/,
|
||||
}
|
||||
});
|
8
experiments/tree-sitter-test/justfile
Normal file
8
experiments/tree-sitter-test/justfile
Normal file
@ -0,0 +1,8 @@
|
||||
_default:
|
||||
just --list
|
||||
|
||||
# Test out the grammar
|
||||
test-grammar:
|
||||
#!/usr/bin/env bash
|
||||
tree-sitter generate
|
||||
tree-sitter test
|
380
experiments/tree-sitter-test/package-lock.json
generated
Normal file
380
experiments/tree-sitter-test/package-lock.json
generated
Normal file
@ -0,0 +1,380 @@
|
||||
{
|
||||
"name": "tree-sitter-test",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tree-sitter-test",
|
||||
"version": "1.0.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.0",
|
||||
"node-gyp-build": "^4.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prebuildify": "^6.0.0",
|
||||
"tree-sitter-cli": "^0.22.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.21.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree_sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.60.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.60.0.tgz",
|
||||
"integrity": "sha512-zcGgwoXbzw9NczqbGzAWL/ToDYAxv1V8gL1D67ClbdkIfeeDBbY0GelZtC25ayLvVjr2q2cloHeQV1R0QAWqRQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
|
||||
"integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==",
|
||||
"engines": {
|
||||
"node": "^16 || ^18 || >= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
|
||||
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz",
|
||||
"integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuildify": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz",
|
||||
"integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"node-abi": "^3.3.0",
|
||||
"npm-run-path": "^3.1.0",
|
||||
"pump": "^3.0.0",
|
||||
"tar-fs": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuildify": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz",
|
||||
"integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==",
|
||||
"hasInstallScript": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.0.0",
|
||||
"node-gyp-build": "^4.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-cli": {
|
||||
"version": "0.22.5",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.22.5.tgz",
|
||||
"integrity": "sha512-c3VT46Bc3a6pEd0JAwufbqEw9Q2FRLDp5E230hGvnr+Hivw+Y6jyeP+3T89KDptvn48MOPVmbgaLm69xYgLVTw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"tree-sitter": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter/node_modules/node-addon-api": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.0.0.tgz",
|
||||
"integrity": "sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
38
experiments/tree-sitter-test/package.json
Normal file
38
experiments/tree-sitter-test/package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "tree-sitter-test",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"types": "bindings/node",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"install": "node-gyp-build",
|
||||
"prebuildify": "prebuildify --napi --strip"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.0",
|
||||
"node-gyp-build": "^4.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.21.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree_sitter": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"prebuildify": "^6.0.0",
|
||||
"tree-sitter-cli": "^0.22.5"
|
||||
},
|
||||
"files": [
|
||||
"grammar.js",
|
||||
"binding.gyp",
|
||||
"prebuilds/**",
|
||||
"bindings/node/*",
|
||||
"queries/*",
|
||||
"src/**"
|
||||
]
|
||||
}
|
26
experiments/tree-sitter-test/test/corpus/test.txt
Normal file
26
experiments/tree-sitter-test/test/corpus/test.txt
Normal file
@ -0,0 +1,26 @@
|
||||
=============
|
||||
Initial test
|
||||
=============
|
||||
fn main() {
|
||||
|
||||
}
|
||||
----
|
||||
(source_file
|
||||
(function_definition
|
||||
(identifier)
|
||||
(parameter_list)
|
||||
(block)
|
||||
)
|
||||
)
|
||||
|
||||
====
|
||||
Another test
|
||||
====
|
||||
|
||||
fn yolo() bool { }
|
||||
|
||||
----
|
||||
|
||||
(source_file
|
||||
(function_definition
|
||||
(identifier) (parameter_list) (primitive_type) (block)))
|
@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "maaru-lang"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.5.8"
|
||||
take_mut = "0.1.3"
|
||||
llvm-sys = "*"
|
||||
|
||||
schala-repl = { path = "../schala-repl" }
|
@ -1,481 +0,0 @@
|
||||
extern crate take_mut;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use parser::{AST, Statement, Expression, Function, Callable, BinOp};
|
||||
use std::rc::Rc;
|
||||
use std::io::{Write, Stdout, BufWriter};
|
||||
use std::convert::From;
|
||||
|
||||
use parser::Expression::*;
|
||||
use parser::Statement::*;
|
||||
|
||||
type Reduction<T> = (T, Option<SideEffect>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ReducedValue {
|
||||
StringLiteral(Rc<String>),
|
||||
ListLiteral(VecDeque<Expression>),
|
||||
StructLiteral(VecDeque<(Rc<String>, Expression)>),
|
||||
Number(f64),
|
||||
Lambda(Function),
|
||||
}
|
||||
|
||||
impl From<ReducedValue> for Expression {
|
||||
fn from(rv: ReducedValue) -> Expression {
|
||||
match rv {
|
||||
ReducedValue::Number(n) => Expression::Number(n),
|
||||
ReducedValue::StringLiteral(n) => Expression::StringLiteral(n),
|
||||
ReducedValue::Lambda(f) => Expression::Lambda(f),
|
||||
ReducedValue::ListLiteral(items) => Expression::ListLiteral(items),
|
||||
ReducedValue::StructLiteral(items) => Expression::StructLiteral(items),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Expression> for ReducedValue {
|
||||
fn from(rv: Expression) -> ReducedValue {
|
||||
match rv {
|
||||
Expression::Number(n) => ReducedValue::Number(n),
|
||||
Expression::StringLiteral(n) => ReducedValue::StringLiteral(n),
|
||||
Expression::Lambda(f) => ReducedValue::Lambda(f),
|
||||
Expression::ListLiteral(items) => ReducedValue::ListLiteral(items),
|
||||
Expression::StructLiteral(items) => ReducedValue::StructLiteral(items),
|
||||
_ => panic!("trying to store a non-fully-reduced variable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_indexer(f: f64) -> Option<usize> {
|
||||
if f.fract() == 0.0 {
|
||||
if f.trunc() >= 0.0 {
|
||||
return Some(f.trunc() as usize);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SideEffect {
|
||||
Print(String),
|
||||
AddBinding(Rc<String>, ReducedValue),
|
||||
}
|
||||
|
||||
pub struct Evaluator<'a> {
|
||||
parent: Option<&'a Evaluator<'a>>,
|
||||
variables: HashMap<String, ReducedValue>,
|
||||
stdout: BufWriter<Stdout>,
|
||||
pub trace_evaluation: bool,
|
||||
}
|
||||
|
||||
impl<'a> Evaluator<'a> {
|
||||
pub fn new(parent: Option<&'a Evaluator>) -> Evaluator<'a> {
|
||||
Evaluator {
|
||||
variables: HashMap::new(),
|
||||
parent: parent,
|
||||
stdout: BufWriter::new(::std::io::stdout()),
|
||||
trace_evaluation: parent.map_or(false, |e| e.trace_evaluation),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self, ast: AST) -> Vec<String> {
|
||||
ast.into_iter()
|
||||
.map(|astnode| format!("{}", self.reduction_loop(astnode)))
|
||||
.collect()
|
||||
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, var: String, value: ReducedValue) {
|
||||
self.variables.insert(var, value);
|
||||
}
|
||||
|
||||
fn lookup_binding(&self, var: &str) -> Option<ReducedValue> {
|
||||
match self.variables.get(var) {
|
||||
Some(expr) => Some(expr.clone()),
|
||||
None => match self.parent {
|
||||
Some(env) => env.lookup_binding(var),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait Evaluable {
|
||||
fn is_reducible(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Evaluable for Statement {
|
||||
fn is_reducible(&self) -> bool {
|
||||
match self {
|
||||
&ExprNode(ref expr) => expr.is_reducible(),
|
||||
&FuncDefNode(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluable for Expression {
|
||||
fn is_reducible(&self) -> bool {
|
||||
match *self {
|
||||
Null => false,
|
||||
StringLiteral(_) => false,
|
||||
Lambda(_) => false,
|
||||
Number(_) => false,
|
||||
ListLiteral(ref items) => {
|
||||
items.iter().any(|x| x.is_reducible())
|
||||
}
|
||||
StructLiteral(ref items) => {
|
||||
items.iter().any(|pair| pair.1.is_reducible())
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
fn is_truthy(&self) -> bool {
|
||||
match *self {
|
||||
Null => false,
|
||||
StringLiteral(ref s) if **s == "" => false,
|
||||
Number(n) if n == 0.0 => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_assignment(op: &BinOp) -> bool {
|
||||
use self::BinOp::*;
|
||||
match *op {
|
||||
Assign | AddAssign | SubAssign |
|
||||
MulAssign | DivAssign => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Evaluator<'a> {
|
||||
fn reduction_loop(&mut self, mut node: Statement) -> Statement {
|
||||
loop {
|
||||
node = self.step(node);
|
||||
if !node.is_reducible() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
fn step(&mut self, node: Statement) -> Statement {
|
||||
let mut trace = String::new();
|
||||
if self.trace_evaluation {
|
||||
trace.push_str(&format!("Step: {:?}", node));
|
||||
}
|
||||
|
||||
let (new_node, side_effect) = self.reduce_astnode(node);
|
||||
|
||||
if self.trace_evaluation {
|
||||
trace.push_str(&format!(" ➜ {:?}", new_node));
|
||||
}
|
||||
if let Some(s) = side_effect {
|
||||
if self.trace_evaluation {
|
||||
trace.push_str(&format!(" | side-effect: {:?}", s));
|
||||
}
|
||||
self.perform_side_effect(s);
|
||||
}
|
||||
if self.trace_evaluation {
|
||||
println!("{}", trace);
|
||||
}
|
||||
new_node
|
||||
}
|
||||
|
||||
fn perform_side_effect(&mut self, side_effect: SideEffect) {
|
||||
use self::SideEffect::*;
|
||||
match side_effect {
|
||||
Print(s) => {
|
||||
write!(self.stdout, "{}\n", s).unwrap();
|
||||
match self.stdout.flush() {
|
||||
Ok(_) => (),
|
||||
Err(_) => println!("Could not flush stdout"),
|
||||
};
|
||||
}
|
||||
AddBinding(var, value) => {
|
||||
self.add_binding((*var).clone(), value);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_astnode(&mut self, node: Statement) -> Reduction<Statement> {
|
||||
match node {
|
||||
ExprNode(expr) => {
|
||||
if expr.is_reducible() {
|
||||
let (new_expr, side_effect) = self.reduce_expr(expr);
|
||||
(ExprNode(new_expr), side_effect)
|
||||
} else {
|
||||
(ExprNode(expr), None)
|
||||
}
|
||||
}
|
||||
FuncDefNode(func) => {
|
||||
let name = func.prototype.name.clone();
|
||||
let reduced_value = ReducedValue::Lambda(func.clone());
|
||||
let binding = Some(SideEffect::AddBinding(name, reduced_value));
|
||||
(ExprNode(Expression::Lambda(func)), binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO I probably want another Expression variant that holds a ReducedValue
|
||||
fn reduce_expr(&mut self, expression: Expression) -> Reduction<Expression> {
|
||||
match expression {
|
||||
Null => (Null, None),
|
||||
e @ StringLiteral(_) => (e, None),
|
||||
e @ Number(_) => (e, None),
|
||||
e @ Lambda(_) => (e, None),
|
||||
Variable(ref var) => {
|
||||
match self.lookup_binding(var).map(|x| x.into()) {
|
||||
None => (Null, None),
|
||||
Some(expr) => (expr, None),
|
||||
}
|
||||
}
|
||||
BinExp(op, mut left, mut right) => {
|
||||
if right.is_reducible() {
|
||||
let mut side_effect = None;
|
||||
take_mut::take(right.as_mut(), |expr| { let (a, b) = self.reduce_expr(expr); side_effect = b; a});
|
||||
return (BinExp(op, left, right), side_effect);
|
||||
}
|
||||
|
||||
if let BinOp::Assign = op {
|
||||
return match *left {
|
||||
Variable(var) => {
|
||||
let reduced_value: ReducedValue = ReducedValue::from(*right);
|
||||
let binding = SideEffect::AddBinding(var, reduced_value);
|
||||
(Null, Some(binding))
|
||||
},
|
||||
_ => (Null, None)
|
||||
};
|
||||
}
|
||||
|
||||
if is_assignment(&op) {
|
||||
use self::BinOp::*;
|
||||
let new_op = match op {
|
||||
AddAssign => Add,
|
||||
SubAssign => Sub,
|
||||
MulAssign => Mul,
|
||||
DivAssign => Div,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let reduction =
|
||||
BinExp(BinOp::Assign,
|
||||
Box::new(*left.clone()),
|
||||
Box::new(BinExp(new_op, left, right))
|
||||
);
|
||||
|
||||
return (reduction, None);
|
||||
}
|
||||
|
||||
if left.is_reducible() {
|
||||
let mut side_effect = None;
|
||||
take_mut::take(left.as_mut(), |expr| { let (a, b) = self.reduce_expr(expr); side_effect = b; a});
|
||||
(BinExp(op, left, right), side_effect)
|
||||
} else {
|
||||
(self.reduce_binop(op, *left, *right), None) //can assume both arguments are maximally reduced
|
||||
}
|
||||
}
|
||||
Call(callable, mut args) => {
|
||||
let mut f = true;
|
||||
for arg in args.iter_mut() {
|
||||
if arg.is_reducible() {
|
||||
take_mut::take(arg, |arg| self.reduce_expr(arg).0);
|
||||
f = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if f {
|
||||
self.reduce_call(callable, args)
|
||||
} else {
|
||||
(Call(callable, args), None)
|
||||
}
|
||||
}
|
||||
While(test, body) => {
|
||||
let mut block = VecDeque::from(body.clone());
|
||||
block.push_back(While(test.clone(), body.clone()));
|
||||
let reduction = Conditional(test, Box::new(Block(block)), None);
|
||||
(reduction, None)
|
||||
}
|
||||
Conditional(box test, then_block, else_block) => {
|
||||
if test.is_reducible() {
|
||||
let (new_test, new_effect) = self.reduce_expr(test);
|
||||
(Conditional(Box::new(new_test), then_block, else_block), new_effect)
|
||||
} else {
|
||||
if test.is_truthy() {
|
||||
(*then_block, None)
|
||||
} else {
|
||||
match else_block {
|
||||
Some(box expr) => (expr, None),
|
||||
None => (Null, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Block(mut exprs) => {
|
||||
let first = exprs.pop_front();
|
||||
match first {
|
||||
None => (Null, None),
|
||||
Some(expr) => {
|
||||
if exprs.len() == 0 {
|
||||
(expr, None)
|
||||
} else {
|
||||
if expr.is_reducible() {
|
||||
let (new, side_effect) = self.reduce_expr(expr);
|
||||
exprs.push_front(new);
|
||||
(Block(exprs), side_effect)
|
||||
} else {
|
||||
(Block(exprs), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Index(mut expr, mut index_expr) => {
|
||||
if index_expr.is_reducible() {
|
||||
let mut side_effect = None;
|
||||
take_mut::take(index_expr.as_mut(), |expr| { let (a, b) = self.reduce_expr(expr); side_effect = b; a});
|
||||
return (Index(expr, index_expr), side_effect)
|
||||
}
|
||||
|
||||
if expr.is_reducible() {
|
||||
let mut side_effect = None;
|
||||
take_mut::take(expr.as_mut(), |expr| { let (a, b) = self.reduce_expr(expr); side_effect = b; a});
|
||||
return (Index(expr, index_expr), side_effect);
|
||||
}
|
||||
|
||||
match (*expr, *index_expr) {
|
||||
(ListLiteral(list_items), Number(n)) => {
|
||||
let indexed_expr = get_indexer(n).and_then(|i| list_items.get(i));
|
||||
if let Some(e) = indexed_expr {
|
||||
(e.clone(), None)
|
||||
} else {
|
||||
(Null, None)
|
||||
}
|
||||
}
|
||||
(StructLiteral(items), StringLiteral(s)) => {
|
||||
for item in items {
|
||||
if s == item.0 {
|
||||
return (item.1.clone(), None); //TODO this is hella inefficient
|
||||
}
|
||||
}
|
||||
(Null, None)
|
||||
},
|
||||
_ => (Null, None)
|
||||
}
|
||||
}
|
||||
ListLiteral(mut exprs) => {
|
||||
let mut side_effect = None;
|
||||
for expr in exprs.iter_mut() {
|
||||
if expr.is_reducible() {
|
||||
take_mut::take(expr, |expr| {
|
||||
let (a, b) = self.reduce_expr(expr);
|
||||
side_effect = b;
|
||||
a
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
(ListLiteral(exprs), side_effect)
|
||||
},
|
||||
|
||||
StructLiteral(mut items) => {
|
||||
let mut side_effect = None;
|
||||
for pair in items.iter_mut() {
|
||||
if pair.1.is_reducible() {
|
||||
take_mut::take(pair, |pair| {
|
||||
let (name, expr) = pair;
|
||||
let (a, b) = self.reduce_expr(expr);
|
||||
side_effect = b;
|
||||
(name, a)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(StructLiteral(items), side_effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_binop(&mut self, op: BinOp, left: Expression, right: Expression) -> Expression {
|
||||
use self::BinOp::*;
|
||||
let truthy = Number(1.0);
|
||||
let falsy = Null;
|
||||
match (op, left, right) {
|
||||
(Add, Number(l), Number(r)) => Number(l + r),
|
||||
(Add, StringLiteral(s1), StringLiteral(s2)) => StringLiteral(Rc::new(format!("{}{}", *s1, *s2))),
|
||||
(Add, StringLiteral(s1), Number(r)) => StringLiteral(Rc::new(format!("{}{}", *s1, r))),
|
||||
(Add, Number(l), StringLiteral(s1)) => StringLiteral(Rc::new(format!("{}{}", l, *s1))),
|
||||
(Sub, Number(l), Number(r)) => Number(l - r),
|
||||
(Mul, Number(l), Number(r)) => Number(l * r),
|
||||
(Div, Number(l), Number(r)) if r != 0.0 => Number(l / r),
|
||||
(Mod, Number(l), Number(r)) => Number(l % r),
|
||||
(Less, Number(l), Number(r)) => if l < r { truthy } else { falsy },
|
||||
(LessEq, Number(l), Number(r)) => if l <= r { truthy } else { falsy },
|
||||
(Greater, Number(l), Number(r)) => if l > r { truthy } else { falsy },
|
||||
(GreaterEq, Number(l), Number(r)) => if l >= r { truthy } else { falsy },
|
||||
(Equal, Number(l), Number(r)) => if l == r { truthy } else { falsy },
|
||||
(Equal, Null, Null) => truthy,
|
||||
(Equal, StringLiteral(s1), StringLiteral(s2)) => if s1 == s2 { truthy } else { falsy },
|
||||
(Equal, _, _) => falsy,
|
||||
_ => falsy,
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_call(&mut self, callable: Callable, arguments: Vec<Expression>) -> Reduction<Expression> {
|
||||
if let Some(res) = handle_builtin(&callable, &arguments) {
|
||||
return res;
|
||||
}
|
||||
|
||||
let function = match callable {
|
||||
Callable::Lambda(func) => func.clone(),
|
||||
Callable::NamedFunction(name) => {
|
||||
match self.lookup_binding(&*name) {
|
||||
Some(ReducedValue::Lambda(func)) => func,
|
||||
_ => return (Null, None),
|
||||
}
|
||||
}
|
||||
};
|
||||
if function.prototype.parameters.len() != arguments.len() {
|
||||
return (Null, None);
|
||||
}
|
||||
|
||||
let mut evaluator = Evaluator::new(Some(self));
|
||||
for (binding, expr) in function.prototype.parameters.iter().zip(arguments.iter()) {
|
||||
evaluator.add_binding((**binding).clone(), expr.clone().into());
|
||||
}
|
||||
|
||||
let nodes = function.body.iter().map(|node| node.clone());
|
||||
let mut retval = ExprNode(Null);
|
||||
for n in nodes {
|
||||
retval = evaluator.reduction_loop(n);
|
||||
}
|
||||
|
||||
match retval {
|
||||
ExprNode(expr) => (expr, None),
|
||||
FuncDefNode(_) => panic!("This should never happen! A maximally-reduced node\
|
||||
should never be a function definition!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_builtin(callable: &Callable, arguments: &Vec<Expression>) -> Option<Reduction<Expression>> {
|
||||
let name: &str = match *callable {
|
||||
Callable::NamedFunction(ref name) => *&name,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
match name {
|
||||
"print" => {
|
||||
let mut s = String::new();
|
||||
for arg in arguments {
|
||||
s.push_str(&format!("{} ", arg));
|
||||
}
|
||||
return Some((Null, Some(SideEffect::Print(s))));
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
#![feature(box_patterns)]
|
||||
|
||||
extern crate schala_repl;
|
||||
|
||||
mod tokenizer;
|
||||
mod parser;
|
||||
mod eval;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TokenError {
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
impl TokenError {
|
||||
pub fn new(msg: &str) -> TokenError {
|
||||
TokenError { msg: msg.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
pub use self::eval::Evaluator as MaaruEvaluator;
|
||||
|
||||
pub struct Maaru<'a> {
|
||||
evaluator: MaaruEvaluator<'a>
|
||||
}
|
||||
|
||||
impl<'a> Maaru<'a> {
|
||||
pub fn new() -> Maaru<'a> {
|
||||
Maaru {
|
||||
evaluator: MaaruEvaluator::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn execute_pipeline(&mut self, input: &str, options: &EvalOptions) -> Result<String, String> {
|
||||
let mut output = UnfinishedComputation::default();
|
||||
|
||||
let tokens = match tokenizer::tokenize(input) {
|
||||
Ok(tokens) => {
|
||||
if let Some(_) = options.debug_passes.get("tokens") {
|
||||
output.add_artifact(TraceArtifact::new("tokens", format!("{:?}", tokens)));
|
||||
}
|
||||
tokens
|
||||
},
|
||||
Err(err) => {
|
||||
return output.finish(Err(format!("Tokenization error: {:?}\n", err.msg)))
|
||||
}
|
||||
};
|
||||
|
||||
let ast = match parser::parse(&tokens, &[]) {
|
||||
Ok(ast) => {
|
||||
if let Some(_) = options.debug_passes.get("ast") {
|
||||
output.add_artifact(TraceArtifact::new("ast", format!("{:?}", ast)));
|
||||
}
|
||||
ast
|
||||
},
|
||||
Err(err) => {
|
||||
return output.finish(Err(format!("Parse error: {:?}\n", err.msg)))
|
||||
}
|
||||
};
|
||||
let mut evaluation_output = String::new();
|
||||
for s in self.evaluator.run(ast).iter() {
|
||||
evaluation_output.push_str(s);
|
||||
}
|
||||
Ok(evaluation_output)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
impl<'a> ProgrammingLanguageInterface for Maaru<'a> {
|
||||
fn get_language_name(&self) -> String {
|
||||
"Maaru".to_string()
|
||||
}
|
||||
fn get_source_file_suffix(&self) -> String {
|
||||
format!("maaru")
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,755 +0,0 @@
|
||||
use tokenizer::{Token, Kw, OpTok};
|
||||
use tokenizer::Token::*;
|
||||
|
||||
use std::fmt;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
use std::convert::From;
|
||||
|
||||
// Grammar
|
||||
// program := (statement delimiter ?)*
|
||||
// delimiter := Newline | Semicolon
|
||||
// statement := declaration | expression
|
||||
// declaration := FN prototype LCurlyBrace (statement)* RCurlyBrace
|
||||
// prototype := identifier LParen identlist RParen
|
||||
// identlist := Ident (Comma Ident)* | ε
|
||||
// exprlist := Expression (Comma Expression)* | ε
|
||||
// itemlist := Ident COLON Expression (Comma Ident COLON Expression)* | ε
|
||||
//
|
||||
// expression := postop_expression (op postop_expression)*
|
||||
// postop_expression := primary_expression postop
|
||||
// primary_expression := number_expr | String | identifier_expr | paren_expr | conditional_expr | while_expr | lambda_expr | list_expr | struct_expr
|
||||
// number_expr := (PLUS | MINUS ) number_expr | Number
|
||||
// identifier_expr := call_expression | Variable
|
||||
// list_expr := LSquareBracket exprlist RSquareBracket
|
||||
// struct_expr := LCurlyBrace itemlist RCurlyBrace
|
||||
// call_expression := Identifier LParen exprlist RParen
|
||||
// while_expr := WHILE primary_expression LCurlyBrace (expression delimiter)* RCurlyBrace
|
||||
// paren_expr := LParen expression RParen
|
||||
// conditional_expr := IF expression LCurlyBrace (expression delimiter)* RCurlyBrace (LCurlyBrace (expresion delimiter)* RCurlyBrace)?
|
||||
// lambda_expr := FN LParen identlist RParen LCurlyBrace (expression delimiter)* RCurlyBrace
|
||||
// lambda_call := | LParen exprlist RParen
|
||||
// postop := ε | LParen exprlist RParen | LBracket expression RBracket
|
||||
// op := '+', '-', etc.
|
||||
//
|
||||
|
||||
pub type AST = Vec<Statement>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Statement {
|
||||
ExprNode(Expression),
|
||||
FuncDefNode(Function),
|
||||
}
|
||||
|
||||
impl fmt::Display for Statement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Statement::*;
|
||||
match *self {
|
||||
ExprNode(ref expr) => write!(f, "{}", expr),
|
||||
FuncDefNode(_) => write!(f, "UNIMPLEMENTED"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function {
|
||||
pub prototype: Prototype,
|
||||
pub body: Vec<Statement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Prototype {
|
||||
pub name: Rc<String>,
|
||||
pub parameters: Vec<Rc<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expression {
|
||||
Null,
|
||||
StringLiteral(Rc<String>),
|
||||
Number(f64),
|
||||
Variable(Rc<String>),
|
||||
BinExp(BinOp, Box<Expression>, Box<Expression>),
|
||||
Call(Callable, Vec<Expression>),
|
||||
Conditional(Box<Expression>, Box<Expression>, Option<Box<Expression>>),
|
||||
Lambda(Function),
|
||||
Block(VecDeque<Expression>),
|
||||
While(Box<Expression>, Vec<Expression>),
|
||||
Index(Box<Expression>, Box<Expression>),
|
||||
ListLiteral(VecDeque<Expression>),
|
||||
StructLiteral(VecDeque<(Rc<String>, Expression)>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Callable {
|
||||
NamedFunction(Rc<String>),
|
||||
Lambda(Function),
|
||||
}
|
||||
|
||||
//TODO this ought to be ReducedExpression
|
||||
impl fmt::Display for Expression {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Expression::*;
|
||||
match *self {
|
||||
Null => write!(f, "null"),
|
||||
StringLiteral(ref s) => write!(f, "\"{}\"", s),
|
||||
Number(n) => write!(f, "{}", n),
|
||||
Lambda(Function { prototype: Prototype { ref name, ref parameters, .. }, .. }) => {
|
||||
write!(f, "«function: {}, {} arg(s)»", name, parameters.len())
|
||||
}
|
||||
ListLiteral(ref items) => {
|
||||
write!(f, "[ ")?;
|
||||
let mut iter = items.iter().peekable();
|
||||
while let Some(item) = iter.next() {
|
||||
write!(f, "{}", item)?;
|
||||
if let Some(_) = iter.peek() {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
write!(f, " ]")
|
||||
}
|
||||
StructLiteral(ref items) => {
|
||||
write!(f, "{} ", "{")?;
|
||||
let mut iter = items.iter().peekable();
|
||||
while let Some(pair) = iter.next() {
|
||||
write!(f, "{}: {}", pair.0, pair.1)?;
|
||||
if let Some(_) = iter.peek() {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
write!(f, "{} ", "}")
|
||||
}
|
||||
_ => write!(f, "UNIMPLEMENTED"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BinOp {
|
||||
Add,
|
||||
AddAssign,
|
||||
Sub,
|
||||
SubAssign,
|
||||
Mul,
|
||||
MulAssign,
|
||||
Div,
|
||||
DivAssign,
|
||||
Mod,
|
||||
Less,
|
||||
LessEq,
|
||||
Greater,
|
||||
GreaterEq,
|
||||
Equal,
|
||||
Assign,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl From<OpTok> for BinOp {
|
||||
fn from(token: OpTok) -> BinOp {
|
||||
use self::BinOp::*;
|
||||
match &token.0[..] {
|
||||
"+" => Add,
|
||||
"+=" => AddAssign,
|
||||
"-" => Sub,
|
||||
"-=" => SubAssign,
|
||||
"*" => Mul,
|
||||
"*=" => MulAssign,
|
||||
"/" => Div,
|
||||
"/=" => DivAssign,
|
||||
"%" => Mod,
|
||||
"<" => Less,
|
||||
"<=" => LessEq,
|
||||
">" => Greater,
|
||||
">=" => GreaterEq,
|
||||
"==" => Equal,
|
||||
"=" => Assign,
|
||||
op => Custom(op.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Precedence = u8;
|
||||
|
||||
// TODO make this support incomplete parses
|
||||
pub type ParseResult<T> = Result<T, ParseError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseError {
|
||||
pub msg: String,
|
||||
pub remaining_tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
fn result_from_str<T>(msg: &str) -> ParseResult<T> {
|
||||
Err(ParseError {
|
||||
msg: msg.to_string(),
|
||||
remaining_tokens: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn initialize(tokens: &[Token]) -> Parser {
|
||||
let mut tokens = tokens.to_vec();
|
||||
tokens.reverse();
|
||||
Parser { tokens: tokens }
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<Token> {
|
||||
self.tokens.last().map(|x| x.clone())
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Option<Token> {
|
||||
self.tokens.pop()
|
||||
}
|
||||
|
||||
fn get_precedence(&self, op: &OpTok) -> Precedence {
|
||||
match &op.0[..] {
|
||||
"+" => 10,
|
||||
"-" => 10,
|
||||
"*" => 20,
|
||||
"/" => 20,
|
||||
"%" => 20,
|
||||
"==" => 40,
|
||||
"=" | "+=" | "-=" | "*=" | "/=" => 1,
|
||||
">" | ">=" | "<" | "<=" => 30,
|
||||
_ => 255,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! expect {
|
||||
($self_:expr, $token:pat) => {
|
||||
match $self_.peek() {
|
||||
Some($token) => {$self_.next();},
|
||||
Some(x) => {
|
||||
let err = format!("Expected `{:?}` but got `{:?}`", stringify!($token), x);
|
||||
return ParseError::result_from_str(&err)
|
||||
},
|
||||
None => {
|
||||
let err = format!("Expected `{:?}` but got end of input", stringify!($token));
|
||||
return ParseError::result_from_str(&err) //TODO make this not require 2 stringifications
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! expect_identifier {
|
||||
($self_:expr) => {
|
||||
match $self_.peek() {
|
||||
Some(Identifier(s)) => {$self_.next(); s},
|
||||
Some(x) => return ParseError::result_from_str(&format!("Expected identifier, but got {:?}", x)),
|
||||
None => return ParseError::result_from_str("Expected identifier, but got end of input"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! skip_whitespace {
|
||||
($_self: expr) => {
|
||||
loop {
|
||||
match $_self.peek() {
|
||||
Some(ref t) if is_delimiter(t) => {
|
||||
$_self.next();
|
||||
continue;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! delimiter_block {
|
||||
($_self: expr, $try_parse: ident, $($break_pattern: pat)|+) => {
|
||||
{
|
||||
let mut acc = Vec::new();
|
||||
loop {
|
||||
match $_self.peek() {
|
||||
None => break,
|
||||
Some(ref t) if is_delimiter(t) => { $_self.next(); continue; },
|
||||
$($break_pattern)|+ => break,
|
||||
_ => {
|
||||
let a = try!($_self.$try_parse());
|
||||
acc.push(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_delimiter(token: &Token) -> bool {
|
||||
match *token {
|
||||
Newline | Semicolon => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn program(&mut self) -> ParseResult<AST> {
|
||||
let mut ast = Vec::new(); //TODO have this come from previously-parsed tree
|
||||
loop {
|
||||
let result: ParseResult<Statement> = match self.peek() {
|
||||
Some(ref t) if is_delimiter(t) => {
|
||||
self.next();
|
||||
continue;
|
||||
}
|
||||
Some(_) => self.statement(),
|
||||
None => break,
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(node) => ast.push(node),
|
||||
Err(mut err) => {
|
||||
err.remaining_tokens = self.tokens.clone();
|
||||
err.remaining_tokens.reverse();
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ast)
|
||||
}
|
||||
|
||||
fn statement(&mut self) -> ParseResult<Statement> {
|
||||
let node: Statement = match self.peek() {
|
||||
Some(Keyword(Kw::Fn)) => self.declaration()?,
|
||||
Some(_) => Statement::ExprNode(self.expression()?),
|
||||
None => panic!("Unexpected end of tokens"),
|
||||
};
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
fn declaration(&mut self) -> ParseResult<Statement> {
|
||||
expect!(self, Keyword(Kw::Fn));
|
||||
let prototype = self.prototype()?;
|
||||
expect!(self, LCurlyBrace);
|
||||
let body = self.body()?;
|
||||
expect!(self, RCurlyBrace);
|
||||
Ok(Statement::FuncDefNode(Function {
|
||||
prototype: prototype,
|
||||
body: body,
|
||||
}))
|
||||
}
|
||||
|
||||
fn prototype(&mut self) -> ParseResult<Prototype> {
|
||||
let name = expect_identifier!(self);
|
||||
expect!(self, LParen);
|
||||
let parameters = self.identlist()?;
|
||||
expect!(self, RParen);
|
||||
Ok(Prototype {
|
||||
name: name,
|
||||
parameters: parameters,
|
||||
})
|
||||
}
|
||||
|
||||
fn identlist(&mut self) -> ParseResult<Vec<Rc<String>>> {
|
||||
let mut args = Vec::new();
|
||||
while let Some(Identifier(name)) = self.peek() {
|
||||
args.push(name.clone());
|
||||
self.next();
|
||||
match self.peek() {
|
||||
Some(Comma) => {self.next();},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn exprlist(&mut self) -> ParseResult<Vec<Expression>> {
|
||||
let mut exprs = Vec::new();
|
||||
loop {
|
||||
if let Some(RParen) = self.peek() {
|
||||
break;
|
||||
}
|
||||
let exp = self.expression()?;
|
||||
exprs.push(exp);
|
||||
match self.peek() {
|
||||
Some(Comma) => {self.next();},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
fn itemlist(&mut self) -> ParseResult<VecDeque<(Rc<String>, Expression)>> {
|
||||
let mut items = VecDeque::new();
|
||||
loop {
|
||||
if let Some(RCurlyBrace) = self.peek() {
|
||||
break;
|
||||
}
|
||||
let name = expect_identifier!(self);
|
||||
expect!(self, Colon);
|
||||
let expr = self.expression()?;
|
||||
items.push_back((name, expr));
|
||||
match self.peek() {
|
||||
Some(Comma) => {self.next();},
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn body(&mut self) -> ParseResult<Vec<Statement>> {
|
||||
let statements = delimiter_block!(
|
||||
self,
|
||||
statement,
|
||||
Some(RCurlyBrace)
|
||||
);
|
||||
Ok(statements)
|
||||
}
|
||||
|
||||
fn expression(&mut self) -> ParseResult<Expression> {
|
||||
let lhs: Expression = self.postop_expression()?;
|
||||
self.precedence_expr(lhs, 0)
|
||||
}
|
||||
|
||||
fn precedence_expr(&mut self,
|
||||
mut lhs: Expression,
|
||||
min_precedence: u8)
|
||||
-> ParseResult<Expression> {
|
||||
while let Some(Operator(op)) = self.peek() {
|
||||
let precedence = self.get_precedence(&op);
|
||||
if precedence < min_precedence {
|
||||
break;
|
||||
}
|
||||
self.next();
|
||||
let mut rhs = self.postop_expression()?;
|
||||
while let Some(Operator(ref op)) = self.peek() {
|
||||
if self.get_precedence(op) > precedence {
|
||||
let new_prec = self.get_precedence(op);
|
||||
rhs = self.precedence_expr(rhs, new_prec)?;
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lhs = Expression::BinExp(op.into(), Box::new(lhs), Box::new(rhs));
|
||||
}
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn postop_expression(&mut self) -> ParseResult<Expression> {
|
||||
use self::Expression::*;
|
||||
let expr = self.primary_expression()?;
|
||||
let ret = match self.peek() {
|
||||
Some(LParen) => {
|
||||
let args = self.call_expression()?;
|
||||
match expr {
|
||||
Lambda(f) => Call(Callable::Lambda(f), args),
|
||||
e => {
|
||||
let err = format!("Expected lambda expression before a call, got {:?}", e);
|
||||
return ParseError::result_from_str(&err);
|
||||
},
|
||||
}
|
||||
},
|
||||
Some(LSquareBracket) => {
|
||||
expect!(self, LSquareBracket);
|
||||
let index_expr = self.expression()?;
|
||||
expect!(self, RSquareBracket);
|
||||
Index(Box::new(expr), Box::new(index_expr))
|
||||
},
|
||||
_ => {
|
||||
expr
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn primary_expression(&mut self) -> ParseResult<Expression> {
|
||||
Ok(match self.peek() {
|
||||
Some(Keyword(Kw::Null)) => {
|
||||
self.next();
|
||||
Expression::Null
|
||||
}
|
||||
Some(NumLiteral(_)) => self.number_expression()?,
|
||||
Some(Operator(OpTok(ref a))) if **a == "+" || **a == "-" => self.number_expression()?,
|
||||
Some(StrLiteral(s)) => {
|
||||
self.next();
|
||||
Expression::StringLiteral(s)
|
||||
}
|
||||
Some(Keyword(Kw::If)) => self.conditional_expr()?,
|
||||
Some(Keyword(Kw::While)) => self.while_expr()?,
|
||||
Some(Identifier(_)) => self.identifier_expr()?,
|
||||
Some(Token::LParen) => self.paren_expr()?,
|
||||
Some(Keyword(Kw::Fn)) => self.lambda_expr()?,
|
||||
Some(Token::LSquareBracket) => self.list_expr()?,
|
||||
Some(Token::LCurlyBrace) => self.struct_expr()?,
|
||||
Some(e) => {
|
||||
return ParseError::result_from_str(&format!("Expected primary expression, got \
|
||||
{:?}",
|
||||
e));
|
||||
}
|
||||
None => return ParseError::result_from_str("Expected primary expression received EoI"),
|
||||
})
|
||||
}
|
||||
|
||||
fn list_expr(&mut self) -> ParseResult<Expression> {
|
||||
expect!(self, LSquareBracket);
|
||||
let exprlist: Vec<Expression> = self.exprlist()?;
|
||||
expect!(self, RSquareBracket);
|
||||
|
||||
Ok(Expression::ListLiteral(VecDeque::from(exprlist)))
|
||||
}
|
||||
|
||||
fn struct_expr(&mut self) -> ParseResult<Expression> {
|
||||
expect!(self, LCurlyBrace);
|
||||
let struct_items = self.itemlist()?;
|
||||
expect!(self, RCurlyBrace);
|
||||
Ok(Expression::StructLiteral(struct_items))
|
||||
}
|
||||
|
||||
fn number_expression(&mut self) -> ParseResult<Expression> {
|
||||
let mut multiplier = 1;
|
||||
loop {
|
||||
match self.peek() {
|
||||
Some(NumLiteral(n)) => {
|
||||
self.next();
|
||||
return Ok(Expression::Number(n * multiplier as f64));
|
||||
}
|
||||
Some(Operator(OpTok(ref a))) if **a == "+" => {
|
||||
self.next();
|
||||
}
|
||||
Some(Operator(OpTok(ref a))) if **a == "-" => {
|
||||
multiplier *= -1;
|
||||
self.next();
|
||||
}
|
||||
Some(e) => {
|
||||
return ParseError::result_from_str(
|
||||
&format!("Expected +, - or number, got {:?}", e));
|
||||
}
|
||||
None => {
|
||||
return ParseError::result_from_str(
|
||||
&format!("Expected +, - or number, got EoI"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lambda_expr(&mut self) -> ParseResult<Expression> {
|
||||
use self::Expression::*;
|
||||
expect!(self, Keyword(Kw::Fn));
|
||||
skip_whitespace!(self);
|
||||
expect!(self, LParen);
|
||||
let parameters = self.identlist()?;
|
||||
expect!(self, RParen);
|
||||
skip_whitespace!(self);
|
||||
expect!(self, LCurlyBrace);
|
||||
let body = self.body()?;
|
||||
expect!(self, RCurlyBrace);
|
||||
|
||||
let prototype = Prototype {
|
||||
name: Rc::new("a lambda yo!".to_string()),
|
||||
parameters: parameters,
|
||||
};
|
||||
|
||||
let function = Function {
|
||||
prototype: prototype,
|
||||
body: body,
|
||||
};
|
||||
|
||||
Ok(Lambda(function))
|
||||
}
|
||||
|
||||
fn while_expr(&mut self) -> ParseResult<Expression> {
|
||||
use self::Expression::*;
|
||||
expect!(self, Keyword(Kw::While));
|
||||
let test = self.expression()?;
|
||||
expect!(self, LCurlyBrace);
|
||||
let body = delimiter_block!(
|
||||
self,
|
||||
expression,
|
||||
Some(RCurlyBrace)
|
||||
);
|
||||
expect!(self, RCurlyBrace);
|
||||
Ok(While(Box::new(test), body))
|
||||
}
|
||||
|
||||
fn conditional_expr(&mut self) -> ParseResult<Expression> {
|
||||
use self::Expression::*;
|
||||
expect!(self, Keyword(Kw::If));
|
||||
let test = self.expression()?;
|
||||
skip_whitespace!(self);
|
||||
expect!(self, LCurlyBrace);
|
||||
skip_whitespace!(self);
|
||||
let then_block = delimiter_block!(
|
||||
self,
|
||||
expression,
|
||||
Some(RCurlyBrace)
|
||||
);
|
||||
expect!(self, RCurlyBrace);
|
||||
skip_whitespace!(self);
|
||||
let else_block = if let Some(Keyword(Kw::Else)) = self.peek() {
|
||||
self.next();
|
||||
skip_whitespace!(self);
|
||||
expect!(self, LCurlyBrace);
|
||||
let else_exprs = delimiter_block!(
|
||||
self,
|
||||
expression,
|
||||
Some(RCurlyBrace)
|
||||
);
|
||||
Some(else_exprs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
expect!(self, RCurlyBrace);
|
||||
Ok(Conditional(Box::new(test),
|
||||
Box::new(Block(VecDeque::from(then_block))),
|
||||
else_block.map(|list| Box::new(Block(VecDeque::from(list))))))
|
||||
}
|
||||
|
||||
fn identifier_expr(&mut self) -> ParseResult<Expression> {
|
||||
let name = expect_identifier!(self);
|
||||
let expr = match self.peek() {
|
||||
Some(LParen) => {
|
||||
let args = self.call_expression()?;
|
||||
Expression::Call(Callable::NamedFunction(name), args)
|
||||
}
|
||||
__ => Expression::Variable(name),
|
||||
};
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn call_expression(&mut self) -> ParseResult<Vec<Expression>> {
|
||||
expect!(self, LParen);
|
||||
let args: Vec<Expression> = self.exprlist()?;
|
||||
expect!(self, RParen);
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn paren_expr(&mut self) -> ParseResult<Expression> {
|
||||
expect!(self, Token::LParen);
|
||||
let expr = self.expression()?;
|
||||
expect!(self, Token::RParen);
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(tokens: &[Token], _parsed_tree: &[Statement]) -> ParseResult<AST> {
|
||||
let mut parser = Parser::initialize(tokens);
|
||||
parser.program()
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use schala_lang::tokenizer;
|
||||
use super::*;
|
||||
use super::Statement::*;
|
||||
use super::Expression::*;
|
||||
|
||||
macro_rules! parsetest {
|
||||
($input:expr, $output:pat, $ifexpr:expr) => {
|
||||
{
|
||||
let tokens = tokenizer::tokenize($input).unwrap();
|
||||
let ast = parse(&tokens, &[]).unwrap();
|
||||
match &ast[..] {
|
||||
$output if $ifexpr => (),
|
||||
x => panic!("Error in parse test, got {:?} instead", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_parse_test() {
|
||||
use super::Function;
|
||||
parsetest!(
|
||||
"fn a() { 1 + 2 }",
|
||||
&[FuncDefNode(Function {prototype: Prototype { ref name, ref parameters }, ref body})],
|
||||
match &body[..] { &[ExprNode(BinExp(_, box Number(1.0), box Number(2.0)))] => true, _ => false }
|
||||
&& **name == "a" && match ¶meters[..] { &[] => true, _ => false }
|
||||
);
|
||||
|
||||
parsetest!(
|
||||
"fn a(x,y){ 1 + 2 }",
|
||||
&[FuncDefNode(Function {prototype: Prototype { ref name, ref parameters }, ref body})],
|
||||
match &body[..] { &[ExprNode(BinExp(_, box Number(1.0), box Number(2.0)))] => true, _ => false }
|
||||
&& **name == "a" && *parameters[0] == "x" && *parameters[1] == "y" && parameters.len() == 2
|
||||
);
|
||||
|
||||
let t3 = "fn (x) { x + 2 }";
|
||||
let tokens3 = tokenizer::tokenize(t3).unwrap();
|
||||
assert!(parse(&tokens3, &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expression_parse_test() {
|
||||
parsetest!("a", &[ExprNode(Variable(ref s))], **s == "a");
|
||||
parsetest!("a + b",
|
||||
&[ExprNode(BinExp(BinOp::Add, box Variable(ref a), box Variable(ref b)))],
|
||||
**a == "a" && **b == "b");
|
||||
parsetest!("a + b * c",
|
||||
&[ExprNode(BinExp(BinOp::Add, box Variable(ref a), box BinExp(BinOp::Mul, box Variable(ref b), box Variable(ref c))))],
|
||||
**a == "a" && **b == "b" && **c == "c");
|
||||
parsetest!("a * b + c",
|
||||
&[ExprNode(BinExp(BinOp::Add, box BinExp(BinOp::Mul, box Variable(ref a), box Variable(ref b)), box Variable(ref c)))],
|
||||
**a == "a" && **b == "b" && **c == "c");
|
||||
parsetest!("(a + b) * c",
|
||||
&[ExprNode(BinExp(BinOp::Mul, box BinExp(BinOp::Add, box Variable(ref a), box Variable(ref b)), box Variable(ref c)))],
|
||||
**a == "a" && **b == "b" && **c == "c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda_parse_test() {
|
||||
use schala_lang::tokenizer;
|
||||
let t1 = "(fn(x) { x + 2 })";
|
||||
let tokens1 = tokenizer::tokenize(t1).unwrap();
|
||||
match parse(&tokens1, &[]).unwrap()[..] {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let t2 = "fn(x) { x + 2 }";
|
||||
let tokens2 = tokenizer::tokenize(t2).unwrap();
|
||||
assert!(parse(&tokens2, &[]).is_err());
|
||||
|
||||
let t3 = "(fn(x) { x + 10 })(20)";
|
||||
let tokens3 = tokenizer::tokenize(t3).unwrap();
|
||||
match parse(&tokens3, &[]).unwrap() {
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_parse_test() {
|
||||
use schala_lang::tokenizer;
|
||||
let t1 = "if null { 20 } else { 40 }";
|
||||
let tokens = tokenizer::tokenize(t1).unwrap();
|
||||
match parse(&tokens, &[]).unwrap()[..] {
|
||||
[ExprNode(Conditional(box Null, box Block(_), Some(box Block(_))))] => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
let t2 = r"
|
||||
if null {
|
||||
20
|
||||
} else {
|
||||
40
|
||||
}
|
||||
";
|
||||
let tokens2 = tokenizer::tokenize(t2).unwrap();
|
||||
match parse(&tokens2, &[]).unwrap()[..] {
|
||||
[ExprNode(Conditional(box Null, box Block(_), Some(box Block(_))))] => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
let t2 = r"
|
||||
if null {
|
||||
20 } else
|
||||
{
|
||||
40
|
||||
}
|
||||
";
|
||||
let tokens3 = tokenizer::tokenize(t2).unwrap();
|
||||
match parse(&tokens3, &[]).unwrap()[..] {
|
||||
[ExprNode(Conditional(box Null, box Block(_), Some(box Block(_))))] => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,208 +0,0 @@
|
||||
extern crate itertools;
|
||||
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use self::itertools::Itertools;
|
||||
use std::rc::Rc;
|
||||
|
||||
use TokenError;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Token {
|
||||
Newline,
|
||||
Semicolon,
|
||||
LParen,
|
||||
RParen,
|
||||
LSquareBracket,
|
||||
RSquareBracket,
|
||||
LCurlyBrace,
|
||||
RCurlyBrace,
|
||||
Comma,
|
||||
Period,
|
||||
Colon,
|
||||
NumLiteral(f64),
|
||||
StrLiteral(Rc<String>),
|
||||
Identifier(Rc<String>),
|
||||
Operator(OpTok),
|
||||
Keyword(Kw),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct OpTok(pub Rc<String>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Kw {
|
||||
If,
|
||||
Else,
|
||||
While,
|
||||
Let,
|
||||
Fn,
|
||||
Null,
|
||||
}
|
||||
|
||||
pub type TokenizeResult = Result<Vec<Token>, TokenError>;
|
||||
|
||||
fn is_digit(c: &char) -> bool {
|
||||
c.is_digit(10)
|
||||
}
|
||||
|
||||
pub fn tokenize(input: &str) -> TokenizeResult {
|
||||
use self::Token::*;
|
||||
let mut tokens = Vec::new();
|
||||
let mut iter: Peekable<Chars> = input.chars().peekable();
|
||||
while let Some(c) = iter.next() {
|
||||
if c == '#' {
|
||||
while let Some(c) = iter.next() {
|
||||
if c == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let cur_tok = match c {
|
||||
c if char::is_whitespace(c) && c != '\n' => continue,
|
||||
'\n' => Newline,
|
||||
';' => Semicolon,
|
||||
'(' => LParen,
|
||||
')' => RParen,
|
||||
':' => Colon,
|
||||
',' => Comma,
|
||||
'{' => LCurlyBrace,
|
||||
'}' => RCurlyBrace,
|
||||
'[' => LSquareBracket,
|
||||
']' => RSquareBracket,
|
||||
'"' => tokenize_str(&mut iter)?,
|
||||
c if !char::is_alphanumeric(c) => tokenize_operator(c, &mut iter)?,
|
||||
c @ '.' | c if is_digit(&c) => tokenize_number_or_period(c, &mut iter)?,
|
||||
c => tokenize_identifier(c, &mut iter)?,
|
||||
};
|
||||
tokens.push(cur_tok);
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn tokenize_str(iter: &mut Peekable<Chars>) -> Result<Token, TokenError> {
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
// TODO handle string escapes, interpolation
|
||||
match iter.next() {
|
||||
Some(x) if x == '"' => break,
|
||||
Some(x) => buffer.push(x),
|
||||
None => return Err(TokenError::new("Unclosed quote")),
|
||||
}
|
||||
}
|
||||
Ok(Token::StrLiteral(Rc::new(buffer)))
|
||||
}
|
||||
|
||||
fn tokenize_operator(c: char, iter: &mut Peekable<Chars>) -> Result<Token, TokenError> {
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| !char::is_alphanumeric(*x) && !char::is_whitespace(*x)));
|
||||
Ok(Token::Operator(OpTok(Rc::new(buffer))))
|
||||
}
|
||||
|
||||
fn tokenize_number_or_period(c: char, iter: &mut Peekable<Chars>) -> Result<Token, TokenError> {
|
||||
if c == '.' && !iter.peek().map_or(false, is_digit) {
|
||||
return Ok(Token::Period);
|
||||
}
|
||||
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| is_digit(x) || *x == '.'));
|
||||
|
||||
match buffer.parse::<f64>() {
|
||||
Ok(f) => Ok(Token::NumLiteral(f)),
|
||||
Err(_) => Err(TokenError::new("Failed to parse digit")),
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenize_identifier(c: char, iter: &mut Peekable<Chars>) -> Result<Token, TokenError> {
|
||||
fn ends_identifier(c: &char) -> bool {
|
||||
let c = *c;
|
||||
char::is_whitespace(c) || is_digit(&c) || c == ';' || c == '(' || c == ')' ||
|
||||
c == ',' || c == '.' || c == ',' || c == ':' || c == '[' || c == ']'
|
||||
}
|
||||
|
||||
use self::Token::*;
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| !ends_identifier(x)));
|
||||
|
||||
Ok(match &buffer[..] {
|
||||
"if" => Keyword(Kw::If),
|
||||
"else" => Keyword(Kw::Else),
|
||||
"while" => Keyword(Kw::While),
|
||||
"let" => Keyword(Kw::Let),
|
||||
"fn" => Keyword(Kw::Fn),
|
||||
"null" => Keyword(Kw::Null),
|
||||
b => Identifier(Rc::new(b.to_string())),
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::Token::*;
|
||||
|
||||
macro_rules! token_test {
|
||||
($input: expr, $output: pat, $ifexpr: expr) => {
|
||||
let tokens = tokenize($input).unwrap();
|
||||
match tokens[..] {
|
||||
$output if $ifexpr => (),
|
||||
_ => panic!("Actual output: {:?}", tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_tokeniziation_tests() {
|
||||
token_test!("let a = 3\n",
|
||||
[Keyword(Kw::Let), Identifier(ref a), Operator(OpTok(ref b)), NumLiteral(3.0), Newline],
|
||||
**a == "a" && **b == "=");
|
||||
|
||||
token_test!("2+1",
|
||||
[NumLiteral(2.0), Operator(OpTok(ref a)), NumLiteral(1.0)],
|
||||
**a == "+");
|
||||
|
||||
token_test!("2 + 1",
|
||||
[NumLiteral(2.0), Operator(OpTok(ref a)), NumLiteral(1.0)],
|
||||
**a == "+");
|
||||
|
||||
token_test!("2.3*49.2",
|
||||
[NumLiteral(2.3), Operator(OpTok(ref a)), NumLiteral(49.2)],
|
||||
**a == "*");
|
||||
|
||||
token_test!("a+3",
|
||||
[Identifier(ref a), NumLiteral(3.0)],
|
||||
**a == "a+");
|
||||
|
||||
assert!(tokenize("2.4.5").is_err());
|
||||
|
||||
token_test!("fn my_func(a) { a ? 3[1] }",
|
||||
[Keyword(Kw::Fn), Identifier(ref a), LParen, Identifier(ref b), RParen, LCurlyBrace, Identifier(ref c),
|
||||
Operator(OpTok(ref d)), NumLiteral(3.0), LSquareBracket, NumLiteral(1.0), RSquareBracket, RCurlyBrace],
|
||||
**a == "my_func" && **b == "a" && **c == "a" && **d == "?");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_test() {
|
||||
token_test!("null + \"a string\"",
|
||||
[Keyword(Kw::Null), Operator(OpTok(ref a)), StrLiteral(ref b)],
|
||||
**a == "+" && **b == "a string");
|
||||
|
||||
token_test!("\"{?'q@?\"",
|
||||
[StrLiteral(ref a)],
|
||||
**a == "{?'q@?");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operator_test() {
|
||||
token_test!("a *> b",
|
||||
[Identifier(ref a), Operator(OpTok(ref b)), Identifier(ref c)],
|
||||
**a == "a" && **b == "*>" && **c == "b");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "robo-lang"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.5.8"
|
||||
take_mut = "0.1.3"
|
||||
llvm-sys = "*"
|
||||
|
||||
schala-repl = { path = "../schala-repl" }
|
158
robo/src/lib.rs
158
robo/src/lib.rs
@ -1,158 +0,0 @@
|
||||
#![feature(box_patterns)]
|
||||
|
||||
extern crate itertools;
|
||||
extern crate schala_repl;
|
||||
|
||||
use itertools::Itertools;
|
||||
use schala_repl::{ProgrammingLanguageInterface, EvalOptions};
|
||||
|
||||
pub struct Robo {
|
||||
}
|
||||
|
||||
impl Robo {
|
||||
pub fn new() -> Robo {
|
||||
Robo { }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TokenError {
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
impl TokenError {
|
||||
pub fn new(msg: &str) -> TokenError {
|
||||
TokenError { msg: msg.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Token {
|
||||
StrLiteral(String),
|
||||
Backtick,
|
||||
Newline,
|
||||
LParen,
|
||||
RParen,
|
||||
LBracket,
|
||||
RBracket,
|
||||
LBrace,
|
||||
RBrace,
|
||||
Period,
|
||||
Comma,
|
||||
Colon,
|
||||
Semicolon,
|
||||
SingleQuote,
|
||||
Identifier(String),
|
||||
Operator(String),
|
||||
NumLiteral(Number),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Number {
|
||||
IntegerRep(String),
|
||||
FloatRep(String)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub type AST = Vec<ASTNode>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum ASTNode {
|
||||
FunctionDefinition(String, Expression),
|
||||
ImportStatement(String),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Expression {
|
||||
|
||||
}
|
||||
|
||||
fn tokenize(input: &str) -> Result<Vec<Token>, TokenError> {
|
||||
use self::Token::*;
|
||||
let mut tokens = Vec::new();
|
||||
let mut iter = input.chars().peekable();
|
||||
while let Some(c) = iter.next() {
|
||||
if c == ';' {
|
||||
while let Some(c) = iter.next() {
|
||||
if c == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let cur_tok = match c {
|
||||
c if char::is_whitespace(c) && c != '\n' => continue,
|
||||
'\n' => Newline,
|
||||
'(' => LParen,
|
||||
')' => RParen,
|
||||
'[' => LBracket,
|
||||
']' => RBracket,
|
||||
'{' => LBrace,
|
||||
'}' => RBrace,
|
||||
',' => Comma,
|
||||
':' => Colon,
|
||||
';' => Semicolon,
|
||||
'.' => Period,
|
||||
'`' => Backtick,
|
||||
'\'' => SingleQuote,
|
||||
'"' => {
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
match iter.next() {
|
||||
Some(x) if x == '"' => break,
|
||||
Some(x) => buffer.push(x),
|
||||
None => return Err(TokenError::new("Unclosed quote")),
|
||||
}
|
||||
}
|
||||
StrLiteral(buffer)
|
||||
}
|
||||
c if c.is_digit(10) => {
|
||||
let mut integer = true;
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| x.is_digit(10)));
|
||||
if let Some(&'.') = iter.peek() {
|
||||
buffer.push(iter.next().unwrap());
|
||||
integer = false;
|
||||
}
|
||||
buffer.extend(iter.peeking_take_while(|x| x.is_digit(10)));
|
||||
let inner = if integer {
|
||||
Number::IntegerRep(buffer)
|
||||
} else {
|
||||
Number::FloatRep(buffer)
|
||||
};
|
||||
NumLiteral(inner)
|
||||
},
|
||||
c if char::is_alphanumeric(c) => {
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| char::is_alphanumeric(*x)));
|
||||
Identifier(buffer)
|
||||
},
|
||||
c => {
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| !char::is_whitespace(*x)));
|
||||
Operator(buffer)
|
||||
}
|
||||
};
|
||||
tokens.push(cur_tok);
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
impl ProgrammingLanguageInterface for Robo {
|
||||
fn get_language_name(&self) -> String {
|
||||
"Robo".to_string()
|
||||
}
|
||||
|
||||
fn get_source_file_suffix(&self) -> String {
|
||||
format!("robo")
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "rukka-lang"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.5.8"
|
||||
take_mut = "0.1.3"
|
||||
llvm-sys = "*"
|
||||
|
||||
schala-repl = { path = "../schala-repl" }
|
417
rukka/src/lib.rs
417
rukka/src/lib.rs
@ -1,417 +0,0 @@
|
||||
#![feature(box_patterns)]
|
||||
|
||||
extern crate itertools;
|
||||
extern crate schala_repl;
|
||||
|
||||
use itertools::Itertools;
|
||||
use schala_repl::{ProgrammingLanguageInterface, EvalOptions};
|
||||
use std::iter::Peekable;
|
||||
use std::vec::IntoIter;
|
||||
use std::str::Chars;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct EvaluatorState {
|
||||
binding_stack: Vec<HashMap<String, Sexp>>
|
||||
}
|
||||
|
||||
impl EvaluatorState {
|
||||
fn new() -> EvaluatorState {
|
||||
use self::Sexp::Primitive;
|
||||
use self::PrimitiveFn::*;
|
||||
let mut default_map = HashMap::new();
|
||||
default_map.insert(format!("+"), Primitive(Plus));
|
||||
default_map.insert(format!("-"), Primitive(Minus));
|
||||
default_map.insert(format!("*"), Primitive(Mult));
|
||||
default_map.insert(format!("/"), Primitive(Div));
|
||||
default_map.insert(format!("%"), Primitive(Mod));
|
||||
default_map.insert(format!(">"), Primitive(Greater));
|
||||
default_map.insert(format!("<"), Primitive(Less));
|
||||
default_map.insert(format!("<="), Primitive(LessThanOrEqual));
|
||||
default_map.insert(format!(">="), Primitive(GreaterThanOrEqual));
|
||||
default_map.insert(format!("display"), Primitive(Display));
|
||||
|
||||
EvaluatorState {
|
||||
binding_stack: vec![default_map],
|
||||
}
|
||||
}
|
||||
fn set_var(&mut self, var: String, value: Sexp) {
|
||||
let binding = self.binding_stack.last_mut().unwrap();
|
||||
binding.insert(var, value);
|
||||
}
|
||||
fn get_var(&self, var: &str) -> Option<&Sexp> {
|
||||
for bindings in self.binding_stack.iter().rev() {
|
||||
match bindings.get(var) {
|
||||
Some(x) => return Some(x),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn push_env(&mut self) {
|
||||
self.binding_stack.push(HashMap::new());
|
||||
}
|
||||
fn pop_env(&mut self) {
|
||||
self.binding_stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rukka {
|
||||
state: EvaluatorState
|
||||
}
|
||||
|
||||
impl Rukka {
|
||||
pub fn new() -> Rukka { Rukka { state: EvaluatorState::new() } }
|
||||
}
|
||||
|
||||
impl ProgrammingLanguageInterface for Rukka {
|
||||
fn get_language_name(&self) -> String {
|
||||
"Rukka".to_string()
|
||||
}
|
||||
|
||||
fn get_source_file_suffix(&self) -> String {
|
||||
format!("rukka")
|
||||
}
|
||||
}
|
||||
|
||||
impl EvaluatorState {
|
||||
fn eval(&mut self, expr: Sexp) -> Result<Sexp, String> {
|
||||
use self::Sexp::*;
|
||||
Ok(match expr {
|
||||
SymbolAtom(ref sym) => match self.get_var(sym) {
|
||||
Some(ref sexp) => {
|
||||
let q: &Sexp = sexp; //WTF? if I delete this line, the copy doesn't work??
|
||||
q.clone() //TODO make this not involve a clone
|
||||
},
|
||||
None => return Err(format!("Variable {} not bound", sym)),
|
||||
},
|
||||
expr @ Primitive(_) => expr,
|
||||
expr @ FnLiteral { .. } => expr,
|
||||
expr @ StringAtom(_) => expr,
|
||||
expr @ NumberAtom(_) => expr,
|
||||
expr @ BoolAtom(_) => expr,
|
||||
Cons(box operator, box operands) => match operator {
|
||||
SymbolAtom(ref sym) if match &sym[..] {
|
||||
"quote" | "eq?" | "cons" | "car" | "cdr" | "atom?" | "define" | "lambda" | "if" | "cond" => true, _ => false
|
||||
} => self.eval_special_form(sym, operands)?,
|
||||
_ => {
|
||||
let evaled = self.eval(operator)?;
|
||||
self.apply(evaled, operands)?
|
||||
}
|
||||
},
|
||||
Nil => Nil,
|
||||
})
|
||||
}
|
||||
fn eval_special_form(&mut self, form: &str, operands: Sexp) -> Result<Sexp, String> {
|
||||
use self::Sexp::*;
|
||||
Ok(match form {
|
||||
"quote" => match operands {
|
||||
Cons(box quoted, box Nil) => quoted,
|
||||
_ => return Err(format!("Bad syntax in quote")),
|
||||
},
|
||||
"eq?" => match operands {//TODO make correct
|
||||
Cons(box lhs, box Cons(box rhs, _)) => BoolAtom(lhs == rhs),
|
||||
_ => BoolAtom(true),
|
||||
},
|
||||
"cons" => match operands {
|
||||
Cons(box cadr, box Cons(box caddr, box Nil)) => {
|
||||
let newl = self.eval(cadr)?;
|
||||
let newr = self.eval(caddr)?;
|
||||
Cons(Box::new(newl), Box::new(newr))
|
||||
},
|
||||
_ => return Err(format!("Bad arguments for cons")),
|
||||
},
|
||||
"car" => match operands {
|
||||
Cons(box car, _) => car,
|
||||
_ => return Err(format!("called car with a non-pair argument")),
|
||||
},
|
||||
"cdr" => match operands {
|
||||
Cons(_, box cdr) => cdr,
|
||||
_ => return Err(format!("called cdr with a non-pair argument")),
|
||||
},
|
||||
"atom?" => match operands {
|
||||
Cons(_, _) => BoolAtom(false),
|
||||
_ => BoolAtom(true),
|
||||
},
|
||||
"define" => match operands {
|
||||
Cons(box SymbolAtom(sym), box Cons(box expr, box Nil)) => {
|
||||
let evaluated = self.eval(expr)?;
|
||||
self.set_var(sym, evaluated);
|
||||
Nil
|
||||
},
|
||||
_ => return Err(format!("Bad assignment")),
|
||||
}
|
||||
"lambda" => match operands {
|
||||
Cons(box mut paramlist, box Cons(box formalexp, box Nil)) => {
|
||||
let mut formal_params = vec![];
|
||||
{
|
||||
let mut ptr = ¶mlist;
|
||||
loop {
|
||||
match ptr {
|
||||
&Cons(ref arg, ref rest) => {
|
||||
if let SymbolAtom(ref sym) = **arg {
|
||||
formal_params.push(sym.clone());
|
||||
ptr = rest;
|
||||
} else {
|
||||
return Err(format!("Bad lambda format"));
|
||||
}
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
FnLiteral {
|
||||
formal_params,
|
||||
body: Box::new(formalexp)
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Bad lambda expression")),
|
||||
},
|
||||
"if" => match operands {
|
||||
Cons(box test, box body) => {
|
||||
let truth_value = test.truthy();
|
||||
match (truth_value, body) {
|
||||
(true, Cons(box consequent, _)) => consequent,
|
||||
(false, Cons(_, box Cons(box alternative, _))) => alternative,
|
||||
_ => return Err(format!("Bad if expression"))
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Bad if expression"))
|
||||
},
|
||||
s => return Err(format!("Non-existent special form {}; this should never happen", s)),
|
||||
})
|
||||
}
|
||||
|
||||
fn apply(&mut self, function: Sexp, operands: Sexp) -> Result<Sexp, String> {
|
||||
use self::Sexp::*;
|
||||
match function {
|
||||
FnLiteral { formal_params, body } => {
|
||||
self.push_env();
|
||||
|
||||
let mut cur = operands;
|
||||
for param in formal_params {
|
||||
match cur {
|
||||
Cons(box arg, box rest) => {
|
||||
cur = rest;
|
||||
self.set_var(param, arg);
|
||||
},
|
||||
_ => return Err(format!("Bad argument for function application")),
|
||||
}
|
||||
}
|
||||
let result = self.eval(*body);
|
||||
self.pop_env();
|
||||
result
|
||||
},
|
||||
Primitive(prim) => {
|
||||
let mut evaled_operands = Vec::new();
|
||||
let mut cur_operand = operands;
|
||||
loop {
|
||||
match cur_operand {
|
||||
Nil => break,
|
||||
Cons(box l, box rest) => {
|
||||
evaled_operands.push(self.eval(l)?);
|
||||
cur_operand = rest;
|
||||
},
|
||||
_ => return Err(format!("Bad operands list"))
|
||||
}
|
||||
}
|
||||
|
||||
prim.apply(evaled_operands)
|
||||
}
|
||||
_ => return Err(format!("Bad type to apply")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read(input: &str) -> Result<Vec<Sexp>, String> {
|
||||
let mut chars: Peekable<Chars> = input.chars().peekable();
|
||||
let mut tokens = tokenize(&mut chars).into_iter().peekable();
|
||||
let mut sexps = Vec::new();
|
||||
while let Some(_) = tokens.peek() {
|
||||
sexps.push(parse(&mut tokens)?);
|
||||
}
|
||||
Ok(sexps)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Token {
|
||||
LParen,
|
||||
RParen,
|
||||
Quote,
|
||||
Word(String),
|
||||
StringLiteral(String),
|
||||
NumLiteral(u64),
|
||||
}
|
||||
|
||||
//TODO make this notion of Eq more sophisticated
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum Sexp {
|
||||
SymbolAtom(String),
|
||||
StringAtom(String),
|
||||
NumberAtom(u64),
|
||||
BoolAtom(bool),
|
||||
Cons(Box<Sexp>, Box<Sexp>),
|
||||
Nil,
|
||||
FnLiteral {
|
||||
formal_params: Vec<String>,
|
||||
body: Box<Sexp>
|
||||
},
|
||||
Primitive(PrimitiveFn)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum PrimitiveFn {
|
||||
Plus, Minus, Mult, Div, Mod, Greater, Less, GreaterThanOrEqual, LessThanOrEqual, Display
|
||||
}
|
||||
|
||||
impl PrimitiveFn {
|
||||
fn apply(&self, evaled_operands: Vec<Sexp>) -> Result<Sexp, String> {
|
||||
use self::Sexp::*;
|
||||
use self::PrimitiveFn::*;
|
||||
let op = self.clone();
|
||||
Ok(match op {
|
||||
Display => {
|
||||
for arg in evaled_operands {
|
||||
print!("{}\n", arg.print());
|
||||
}
|
||||
Nil
|
||||
},
|
||||
Plus | Mult => {
|
||||
let mut result = match op { Plus => 0, Mult => 1, _ => unreachable!() };
|
||||
for arg in evaled_operands {
|
||||
if let NumberAtom(n) = arg {
|
||||
if let Plus = op {
|
||||
result += n;
|
||||
} else if let Mult = op {
|
||||
result *= n;
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Bad operand: {:?}", arg));
|
||||
}
|
||||
}
|
||||
NumberAtom(result)
|
||||
},
|
||||
op => return Err(format!("Primitive op {:?} not implemented", op)),
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Sexp {
|
||||
fn print(&self) -> String {
|
||||
use self::Sexp::*;
|
||||
match self {
|
||||
&BoolAtom(true) => format!("#t"),
|
||||
&BoolAtom(false) => format!("#f"),
|
||||
&SymbolAtom(ref sym) => format!("{}", sym),
|
||||
&StringAtom(ref s) => format!("\"{}\"", s),
|
||||
&NumberAtom(ref n) => format!("{}", n),
|
||||
&Cons(ref car, ref cdr) => format!("({} . {})", car.print(), cdr.print()),
|
||||
&Nil => format!("()"),
|
||||
&FnLiteral { ref formal_params, .. } => format!("<lambda {:?}>", formal_params),
|
||||
&Primitive(ref sym) => format!("<primitive \"{:?}\">", sym),
|
||||
}
|
||||
}
|
||||
|
||||
fn truthy(&self) -> bool {
|
||||
use self::Sexp::*;
|
||||
match self {
|
||||
&BoolAtom(false) => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenize(input: &mut Peekable<Chars>) -> Vec<Token> {
|
||||
use self::Token::*;
|
||||
let mut tokens = Vec::new();
|
||||
loop {
|
||||
match input.next() {
|
||||
None => break,
|
||||
Some('(') => tokens.push(LParen),
|
||||
Some(')') => tokens.push(RParen),
|
||||
Some('\'') => tokens.push(Quote),
|
||||
Some(c) if c.is_whitespace() => continue,
|
||||
Some(c) if c.is_numeric() => {
|
||||
let tok: String = input.peeking_take_while(|next| next.is_numeric()).collect();
|
||||
let n: u64 = format!("{}{}", c, tok).parse().unwrap();
|
||||
tokens.push(NumLiteral(n));
|
||||
},
|
||||
Some('"') => {
|
||||
let string: String = input.scan(false, |escape, cur_char| {
|
||||
let seen_escape = *escape;
|
||||
*escape = cur_char == '\\' && !seen_escape;
|
||||
match (cur_char, seen_escape) {
|
||||
('"', false) => None,
|
||||
('\\', false) => Some(None),
|
||||
(c, _) => Some(Some(c))
|
||||
}
|
||||
}).filter_map(|x| x).collect();
|
||||
tokens.push(StringLiteral(string));
|
||||
}
|
||||
Some(c) => {
|
||||
let sym: String = input.peeking_take_while(|next| {
|
||||
match *next {
|
||||
'(' | ')' => false,
|
||||
c if c.is_whitespace() => false,
|
||||
_ => true
|
||||
}
|
||||
}).collect();
|
||||
tokens.push(Word(format!("{}{}", c, sym)));
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
fn parse(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Sexp, String> {
|
||||
use self::Token::*;
|
||||
use self::Sexp::*;
|
||||
match tokens.next() {
|
||||
Some(Word(ref s)) if s == "#f" => Ok(BoolAtom(false)),
|
||||
Some(Word(ref s)) if s == "#t" => Ok(BoolAtom(true)),
|
||||
Some(Word(s)) => Ok(SymbolAtom(s)),
|
||||
Some(StringLiteral(s)) => Ok(StringAtom(s)),
|
||||
Some(LParen) => parse_sexp(tokens),
|
||||
Some(RParen) => Err(format!("Unexpected ')'")),
|
||||
Some(Quote) => {
|
||||
let quoted = parse(tokens)?;
|
||||
Ok(Cons(Box::new(SymbolAtom(format!("quote"))), Box::new(Cons(Box::new(quoted), Box::new(Nil)))))
|
||||
},
|
||||
Some(NumLiteral(n)) => Ok(NumberAtom(n)),
|
||||
None => Err(format!("Unexpected end of input")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sexp(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Sexp, String> {
|
||||
use self::Token::*;
|
||||
use self::Sexp::*;
|
||||
let mut cell = Nil;
|
||||
{
|
||||
let mut cell_ptr = &mut cell;
|
||||
loop {
|
||||
match tokens.peek() {
|
||||
None => return Err(format!("Unexpected end of input")),
|
||||
Some(&RParen) => {
|
||||
tokens.next();
|
||||
break;
|
||||
},
|
||||
_ => {
|
||||
let current = parse(tokens)?;
|
||||
let new_cdr = Cons(Box::new(current), Box::new(Nil));
|
||||
match cell_ptr {
|
||||
&mut Cons(_, ref mut cdr) => **cdr = new_cdr,
|
||||
&mut Nil => *cell_ptr = new_cdr,
|
||||
_ => unreachable!()
|
||||
};
|
||||
|
||||
let old_ptr = cell_ptr;
|
||||
let new_ptr: &mut Sexp = match old_ptr { &mut Cons(_, ref mut cdr) => cdr, _ => unreachable!() } as &mut Sexp;
|
||||
cell_ptr = new_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(cell)
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
@ -1,8 +0,0 @@
|
||||
|
||||
max_width = 110
|
||||
use_small_heuristics = "max"
|
||||
imports_indent = "block"
|
||||
imports_granularity = "crate"
|
||||
group_imports = "stdexternalcrate"
|
||||
match_arm_blocks = false
|
||||
where_single_line = true
|
@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "schala-lang"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.10"
|
||||
take_mut = "0.2.2"
|
||||
failure = "0.1.5"
|
||||
ena = "0.11.0"
|
||||
stopwatch = "0.0.7"
|
||||
derivative = "2.2.0"
|
||||
colored = "1.8"
|
||||
radix_trie = "0.1.5"
|
||||
assert_matches = "1.5"
|
||||
#peg = "0.7.0"
|
||||
peg = "0.8.1"
|
||||
nom = "7.1.0"
|
||||
nom_locate = "4.0.0"
|
||||
|
||||
|
||||
schala-repl = { path = "../schala-repl" }
|
||||
|
||||
[dev-dependencies]
|
||||
test-case = "1.2.0"
|
||||
pretty_assertions = "1.0.0"
|
@ -1,22 +0,0 @@
|
||||
let _SCHALA_VERSION = "0.1.0"
|
||||
|
||||
type Option<T> = Some(T) | None
|
||||
type Ord = LT | EQ | GT
|
||||
|
||||
@register_builtin(print)
|
||||
fn print(arg) { }
|
||||
|
||||
@register_builtin(println)
|
||||
fn println(arg) { }
|
||||
|
||||
@register_builtin(getline)
|
||||
fn getline(arg) { }
|
||||
|
||||
fn map(input: Option<T>, func: Func): Option<T> {
|
||||
if input {
|
||||
is Option::Some(x) then Option::Some(func(x))
|
||||
is Option::None then Option::None
|
||||
}
|
||||
}
|
||||
|
||||
type Complicated = Sunrise | Metal { black: bool, norwegian: bool } | Fella(String, Int)
|
@ -1,345 +0,0 @@
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
#![allow(clippy::enum_variant_names)]
|
||||
|
||||
use std::{
|
||||
convert::{AsRef, From},
|
||||
fmt,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
mod operators;
|
||||
mod visitor;
|
||||
mod visualize;
|
||||
|
||||
pub use operators::{BinOp, PrefixOp};
|
||||
pub use visitor::*;
|
||||
|
||||
use crate::{
|
||||
derivative::Derivative,
|
||||
identifier::{define_id_kind, Id},
|
||||
parsing::Location,
|
||||
util::delim_wrapped,
|
||||
};
|
||||
|
||||
define_id_kind!(ASTItem);
|
||||
|
||||
pub type ItemId = Id<ASTItem>;
|
||||
|
||||
#[derive(Derivative, Debug)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct AST {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
pub id: ItemId,
|
||||
pub statements: Block,
|
||||
}
|
||||
|
||||
impl fmt::Display for AST {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", visualize::render_ast(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Derivative, Debug, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct Statement<K> {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
pub id: ItemId,
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
pub location: Location,
|
||||
pub kind: K,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum StatementKind {
|
||||
Expression(Expression),
|
||||
Declaration(Declaration),
|
||||
Import(ImportSpecifier),
|
||||
Flow(FlowControl),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FlowControl {
|
||||
Continue,
|
||||
Break,
|
||||
Return(Option<Expression>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Block {
|
||||
pub statements: Vec<Statement<StatementKind>>,
|
||||
}
|
||||
|
||||
impl From<Vec<Statement<StatementKind>>> for Block {
|
||||
fn from(statements: Vec<Statement<StatementKind>>) -> Self {
|
||||
Self { statements }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Statement<StatementKind>> for Block {
|
||||
fn from(statement: Statement<StatementKind>) -> Self {
|
||||
Self { statements: vec![statement] }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[Statement<StatementKind>]> for Block {
|
||||
fn as_ref(&self) -> &[Statement<StatementKind>] {
|
||||
self.statements.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub type ParamName = Rc<String>;
|
||||
|
||||
#[derive(Debug, Derivative, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct QualifiedName {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
pub id: ItemId,
|
||||
pub components: Vec<Rc<String>>,
|
||||
}
|
||||
|
||||
impl fmt::Display for QualifiedName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self.components[..] {
|
||||
[] => write!(f, "[<empty>]"),
|
||||
[name] => write!(f, "{}", name),
|
||||
[name, rest @ ..] => {
|
||||
write!(f, "{}", name)?;
|
||||
for c in rest {
|
||||
write!(f, "::{}", c)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct FormalParam {
|
||||
pub name: ParamName,
|
||||
pub default: Option<Expression>,
|
||||
pub anno: Option<TypeIdentifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Declaration {
|
||||
FuncSig(Signature),
|
||||
FuncDecl(Signature, Block),
|
||||
TypeDecl {
|
||||
name: TypeSingletonName,
|
||||
body: TypeBody,
|
||||
mutable: bool,
|
||||
},
|
||||
//TODO TypeAlias `original` needs to be a more complex type definition
|
||||
TypeAlias {
|
||||
alias: Rc<String>,
|
||||
original: Rc<String>,
|
||||
},
|
||||
Binding {
|
||||
name: Rc<String>,
|
||||
constant: bool,
|
||||
type_anno: Option<TypeIdentifier>,
|
||||
expr: Expression,
|
||||
},
|
||||
Impl {
|
||||
type_name: TypeIdentifier,
|
||||
interface_name: Option<TypeSingletonName>,
|
||||
block: Vec<Statement<Declaration>>,
|
||||
},
|
||||
Interface {
|
||||
name: Rc<String>,
|
||||
signatures: Vec<Signature>,
|
||||
},
|
||||
//TODO need to limit the types of statements that can be annotated
|
||||
Annotation {
|
||||
name: Rc<String>,
|
||||
arguments: Vec<Expression>,
|
||||
inner: Box<Statement<StatementKind>>,
|
||||
},
|
||||
Module {
|
||||
name: Rc<String>,
|
||||
items: Block,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Signature {
|
||||
pub name: Rc<String>,
|
||||
pub operator: bool,
|
||||
pub params: Vec<FormalParam>,
|
||||
pub type_anno: Option<TypeIdentifier>,
|
||||
}
|
||||
|
||||
//TODO I can probably get rid of TypeBody
|
||||
#[derive(Debug, Derivative, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub enum TypeBody {
|
||||
Variants(Vec<Variant>),
|
||||
ImmediateRecord {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
id: ItemId,
|
||||
fields: Vec<(Rc<String>, TypeIdentifier)>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Derivative, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct Variant {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
pub id: ItemId,
|
||||
pub name: Rc<String>,
|
||||
pub kind: VariantKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum VariantKind {
|
||||
UnitStruct,
|
||||
TupleStruct(Vec<TypeIdentifier>),
|
||||
Record(Vec<(Rc<String>, TypeIdentifier)>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Derivative, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct Expression {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
pub id: ItemId,
|
||||
pub kind: ExpressionKind,
|
||||
//TODO this should only allow singletons, not tuples
|
||||
pub type_anno: Option<TypeIdentifier>,
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn new(id: ItemId, kind: ExpressionKind) -> Expression {
|
||||
Expression { id, kind, type_anno: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TypeIdentifier {
|
||||
Tuple(Vec<TypeIdentifier>),
|
||||
Singleton(TypeSingletonName),
|
||||
}
|
||||
|
||||
impl fmt::Display for TypeIdentifier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TypeIdentifier::Tuple(items) =>
|
||||
write!(f, "{}", delim_wrapped('(', ')', items.iter().map(|item| item.to_string()))),
|
||||
TypeIdentifier::Singleton(tsn) => {
|
||||
write!(f, "{}", tsn.name)?;
|
||||
if !tsn.params.is_empty() {
|
||||
write!(f, "{}", delim_wrapped('<', '>', tsn.params.iter().map(|item| item.to_string())))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TypeSingletonName {
|
||||
pub name: Rc<String>,
|
||||
pub params: Vec<TypeIdentifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ExpressionKind {
|
||||
NatLiteral(u64),
|
||||
FloatLiteral(f64),
|
||||
StringLiteral { prefix: Option<Rc<String>>, s: Rc<String> },
|
||||
BoolLiteral(bool),
|
||||
BinExp(BinOp, Box<Expression>, Box<Expression>),
|
||||
PrefixExp(PrefixOp, Box<Expression>),
|
||||
TupleLiteral(Vec<Expression>),
|
||||
Value(QualifiedName),
|
||||
SelfValue,
|
||||
NamedStruct { name: QualifiedName, fields: Vec<(Rc<String>, Expression)> },
|
||||
Call { f: Box<Expression>, arguments: Vec<InvocationArgument> },
|
||||
Index { indexee: Box<Expression>, indexers: Vec<Expression> },
|
||||
IfExpression { discriminator: Option<Box<Expression>>, body: Box<IfExpressionBody> },
|
||||
WhileExpression { condition: Option<Box<Expression>>, body: Block },
|
||||
ForExpression { enumerators: Vec<Enumerator>, body: Box<ForBody> },
|
||||
Lambda { params: Vec<FormalParam>, type_anno: Option<TypeIdentifier>, body: Block },
|
||||
Access { name: Rc<String>, expr: Box<Expression> },
|
||||
ListLiteral(Vec<Expression>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum InvocationArgument {
|
||||
Positional(Expression),
|
||||
Keyword { name: Rc<String>, expr: Expression },
|
||||
Ignored,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum IfExpressionBody {
|
||||
SimpleConditional { then_case: Block, else_case: Option<Block> },
|
||||
SimplePatternMatch { pattern: Pattern, then_case: Block, else_case: Option<Block> },
|
||||
CondList(Vec<ConditionArm>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ConditionArm {
|
||||
pub condition: Condition,
|
||||
pub guard: Option<Expression>,
|
||||
pub body: Block,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Condition {
|
||||
Pattern(Pattern),
|
||||
TruncatedOp(BinOp, Expression),
|
||||
//Expression(Expression), //I'm pretty sure I don't actually want this
|
||||
Else,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Pattern {
|
||||
Ignored,
|
||||
TuplePattern(Vec<Pattern>),
|
||||
Literal(PatternLiteral),
|
||||
TupleStruct(QualifiedName, Vec<Pattern>),
|
||||
Record(QualifiedName, Vec<(Rc<String>, Pattern)>),
|
||||
VarOrName(QualifiedName),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum PatternLiteral {
|
||||
NumPattern { neg: bool, num: ExpressionKind },
|
||||
StringPattern(Rc<String>),
|
||||
BoolPattern(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Enumerator {
|
||||
pub identifier: Rc<String>,
|
||||
pub generator: Expression,
|
||||
pub assignment: bool, //true if `=`, false if `<-`
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ForBody {
|
||||
MonadicReturn(Expression),
|
||||
StatementBlock(Block),
|
||||
}
|
||||
|
||||
#[derive(Debug, Derivative, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct ImportSpecifier {
|
||||
#[derivative(PartialEq = "ignore")]
|
||||
pub id: ItemId,
|
||||
pub path_components: Vec<Rc<String>>,
|
||||
pub imported_names: ImportedNames,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ImportedNames {
|
||||
All,
|
||||
LastOfPath,
|
||||
List(Vec<Rc<String>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ModuleSpecifier {
|
||||
pub name: Rc<String>,
|
||||
pub contents: Block,
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct PrefixOp {
|
||||
sigil: Rc<String>,
|
||||
}
|
||||
|
||||
impl PrefixOp {
|
||||
pub fn from_sigil(sigil: &str) -> PrefixOp {
|
||||
PrefixOp { sigil: Rc::new(sigil.to_string()) }
|
||||
}
|
||||
|
||||
pub fn sigil(&self) -> &str {
|
||||
&self.sigil
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct BinOp {
|
||||
sigil: Rc<String>,
|
||||
}
|
||||
|
||||
impl BinOp {
|
||||
pub fn from_sigil(sigil: &str) -> BinOp {
|
||||
BinOp { sigil: Rc::new(sigil.to_string()) }
|
||||
}
|
||||
|
||||
pub fn sigil(&self) -> &str {
|
||||
&self.sigil
|
||||
}
|
||||
|
||||
pub fn min_precedence() -> i32 {
|
||||
i32::min_value()
|
||||
}
|
||||
pub fn get_precedence(&self) -> i32 {
|
||||
binop_precedences(self.sigil.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn binop_precedences(s: &str) -> i32 {
|
||||
let default = 10_000_000;
|
||||
match s {
|
||||
"+" => 10,
|
||||
"-" => 10,
|
||||
"*" => 20,
|
||||
"/" => 20,
|
||||
"%" => 20,
|
||||
"++" => 30,
|
||||
"^" => 30,
|
||||
"&" => 20,
|
||||
"|" => 20,
|
||||
">" => 20,
|
||||
">=" => 20,
|
||||
"<" => 20,
|
||||
"<=" => 20,
|
||||
"==" => 40,
|
||||
"<=>" => 30,
|
||||
"=" => 5, // Assignment shoudl have highest precedence
|
||||
_ => default,
|
||||
}
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
use crate::ast::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Recursion {
|
||||
Continue,
|
||||
Stop,
|
||||
}
|
||||
|
||||
pub trait ASTVisitor: Sized {
|
||||
fn expression(&mut self, _expression: &Expression) -> Recursion {
|
||||
Recursion::Continue
|
||||
}
|
||||
fn declaration(&mut self, _declaration: &Declaration, _id: &ItemId) -> Recursion {
|
||||
Recursion::Continue
|
||||
}
|
||||
|
||||
fn import(&mut self, _import: &ImportSpecifier) -> Recursion {
|
||||
Recursion::Continue
|
||||
}
|
||||
fn pattern(&mut self, _pat: &Pattern) -> Recursion {
|
||||
Recursion::Continue
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_ast<V: ASTVisitor>(v: &mut V, ast: &AST) {
|
||||
walk_block(v, &ast.statements);
|
||||
}
|
||||
|
||||
pub fn walk_block<V: ASTVisitor>(v: &mut V, block: &Block) {
|
||||
use StatementKind::*;
|
||||
for statement in block.statements.iter() {
|
||||
match statement.kind {
|
||||
StatementKind::Expression(ref expr) => {
|
||||
walk_expression(v, expr);
|
||||
}
|
||||
Declaration(ref decl) => {
|
||||
walk_declaration(v, decl, &statement.id);
|
||||
}
|
||||
Import(ref import_spec) => {
|
||||
v.import(import_spec);
|
||||
}
|
||||
Flow(ref flow_control) =>
|
||||
if let FlowControl::Return(Some(ref retval)) = flow_control {
|
||||
walk_expression(v, retval);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_declaration<V: ASTVisitor>(v: &mut V, decl: &Declaration, id: &ItemId) {
|
||||
use Declaration::*;
|
||||
|
||||
if let Recursion::Continue = v.declaration(decl, id) {
|
||||
match decl {
|
||||
FuncDecl(_sig, block) => {
|
||||
walk_block(v, block);
|
||||
}
|
||||
Binding { name: _, constant: _, type_anno: _, expr } => {
|
||||
walk_expression(v, expr);
|
||||
}
|
||||
Module { name: _, items } => {
|
||||
walk_block(v, items);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_expression<V: ASTVisitor>(v: &mut V, expr: &Expression) {
|
||||
use ExpressionKind::*;
|
||||
|
||||
if let Recursion::Continue = v.expression(expr) {
|
||||
match &expr.kind {
|
||||
NatLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StringLiteral { .. }
|
||||
| BoolLiteral(_)
|
||||
| Value(_)
|
||||
| SelfValue => (),
|
||||
BinExp(_, lhs, rhs) => {
|
||||
walk_expression(v, lhs);
|
||||
walk_expression(v, rhs);
|
||||
}
|
||||
PrefixExp(_, arg) => {
|
||||
walk_expression(v, arg);
|
||||
}
|
||||
TupleLiteral(exprs) =>
|
||||
for expr in exprs {
|
||||
walk_expression(v, expr);
|
||||
},
|
||||
NamedStruct { name: _, fields } =>
|
||||
for (_, expr) in fields.iter() {
|
||||
walk_expression(v, expr);
|
||||
},
|
||||
Call { f, arguments } => {
|
||||
walk_expression(v, f);
|
||||
for arg in arguments.iter() {
|
||||
match arg {
|
||||
InvocationArgument::Positional(expr) | InvocationArgument::Keyword { expr, .. } =>
|
||||
walk_expression(v, expr),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
Index { indexee, indexers } => {
|
||||
walk_expression(v, indexee);
|
||||
for indexer in indexers.iter() {
|
||||
walk_expression(v, indexer);
|
||||
}
|
||||
}
|
||||
IfExpression { discriminator, body } => {
|
||||
if let Some(d) = discriminator.as_ref() {
|
||||
walk_expression(v, d);
|
||||
}
|
||||
walk_if_expr_body(v, body.as_ref());
|
||||
}
|
||||
WhileExpression { condition, body } => {
|
||||
if let Some(d) = condition.as_ref() {
|
||||
walk_expression(v, d);
|
||||
}
|
||||
walk_block(v, body);
|
||||
}
|
||||
ForExpression { enumerators, body } => {
|
||||
for enumerator in enumerators {
|
||||
walk_expression(v, &enumerator.generator);
|
||||
}
|
||||
match body.as_ref() {
|
||||
ForBody::MonadicReturn(expr) => walk_expression(v, expr),
|
||||
ForBody::StatementBlock(block) => walk_block(v, block),
|
||||
};
|
||||
}
|
||||
Lambda { params: _, type_anno: _, body } => {
|
||||
walk_block(v, body);
|
||||
}
|
||||
Access { name: _, expr } => {
|
||||
walk_expression(v, expr);
|
||||
}
|
||||
ListLiteral(exprs) =>
|
||||
for expr in exprs {
|
||||
walk_expression(v, expr);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_if_expr_body<V: ASTVisitor>(v: &mut V, body: &IfExpressionBody) {
|
||||
use IfExpressionBody::*;
|
||||
|
||||
match body {
|
||||
SimpleConditional { then_case, else_case } => {
|
||||
walk_block(v, then_case);
|
||||
if let Some(block) = else_case.as_ref() {
|
||||
walk_block(v, block)
|
||||
}
|
||||
}
|
||||
SimplePatternMatch { pattern, then_case, else_case } => {
|
||||
walk_pattern(v, pattern);
|
||||
walk_block(v, then_case);
|
||||
if let Some(block) = else_case.as_ref() {
|
||||
walk_block(v, block)
|
||||
}
|
||||
}
|
||||
CondList(arms) =>
|
||||
for arm in arms {
|
||||
match arm.condition {
|
||||
Condition::Pattern(ref pat) => {
|
||||
walk_pattern(v, pat);
|
||||
}
|
||||
Condition::TruncatedOp(ref _binop, ref expr) => {
|
||||
walk_expression(v, expr);
|
||||
}
|
||||
Condition::Else => (),
|
||||
}
|
||||
if let Some(ref guard) = arm.guard {
|
||||
walk_expression(v, guard);
|
||||
}
|
||||
walk_block(v, &arm.body);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_pattern<V: ASTVisitor>(v: &mut V, pat: &Pattern) {
|
||||
use Pattern::*;
|
||||
|
||||
if let Recursion::Continue = v.pattern(pat) {
|
||||
match pat {
|
||||
TuplePattern(patterns) =>
|
||||
for pat in patterns {
|
||||
walk_pattern(v, pat);
|
||||
},
|
||||
TupleStruct(_, patterns) =>
|
||||
for pat in patterns {
|
||||
walk_pattern(v, pat);
|
||||
},
|
||||
Record(_, name_and_patterns) =>
|
||||
for (_, pat) in name_and_patterns {
|
||||
walk_pattern(v, pat);
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
@ -1,282 +0,0 @@
|
||||
#![allow(clippy::single_char_add_str)]
|
||||
use std::fmt::Write;
|
||||
|
||||
use super::{
|
||||
Block, Declaration, Expression, ExpressionKind, FlowControl, ImportSpecifier, InvocationArgument,
|
||||
Signature, Statement, StatementKind, AST,
|
||||
};
|
||||
|
||||
const LEVEL: usize = 2;
|
||||
|
||||
fn do_indent(n: usize, buf: &mut String) {
|
||||
for _ in 0..n {
|
||||
buf.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
fn newline(buf: &mut String) {
|
||||
buf.push('\n');
|
||||
}
|
||||
|
||||
pub(super) fn render_ast(ast: &AST) -> String {
|
||||
let AST { statements, .. } = ast;
|
||||
|
||||
let mut buf = "(AST\n".to_string();
|
||||
render_block(statements, LEVEL, &mut buf);
|
||||
|
||||
buf.push(')');
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
fn render_statement(stmt: &Statement<StatementKind>, indent: usize, buf: &mut String) {
|
||||
use StatementKind::*;
|
||||
do_indent(indent, buf);
|
||||
match stmt.kind {
|
||||
Expression(ref expr) => render_expression(expr, indent, buf),
|
||||
Declaration(ref decl) => render_declaration(decl, indent, buf),
|
||||
Import(ref spec) => render_import(spec, indent, buf),
|
||||
Flow(ref flow_control) => render_flow_control(flow_control, indent, buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_expression(expr: &Expression, indent: usize, buf: &mut String) {
|
||||
use ExpressionKind::*;
|
||||
|
||||
buf.push_str("(Expr ");
|
||||
match &expr.kind {
|
||||
SelfValue => write!(buf, "(SelfValue)").unwrap(),
|
||||
NatLiteral(n) => buf.push_str(&format!("(NatLiteral {})", n)),
|
||||
FloatLiteral(f) => buf.push_str(&format!("(FloatLiteral {})", f)),
|
||||
StringLiteral { s, prefix } => buf.push_str(&format!("(StringLiteral prefix: {:?} {})", prefix, s)),
|
||||
BoolLiteral(b) => buf.push_str(&format!("(BoolLiteral {})", b)),
|
||||
BinExp(binop, lhs, rhs) => {
|
||||
let new_indent = indent + LEVEL;
|
||||
buf.push_str(&format!("Binop {}\n", binop.sigil()));
|
||||
|
||||
do_indent(new_indent, buf);
|
||||
render_expression(lhs, new_indent, buf);
|
||||
newline(buf);
|
||||
|
||||
do_indent(new_indent, buf);
|
||||
render_expression(rhs, new_indent, buf);
|
||||
newline(buf);
|
||||
|
||||
do_indent(indent, buf);
|
||||
}
|
||||
PrefixExp(prefix, expr) => {
|
||||
let new_indent = indent + LEVEL;
|
||||
buf.push_str(&format!("PrefixOp {}\n", prefix.sigil()));
|
||||
|
||||
do_indent(new_indent, buf);
|
||||
render_expression(expr, new_indent, buf);
|
||||
newline(buf);
|
||||
|
||||
do_indent(indent, buf);
|
||||
}
|
||||
TupleLiteral(..) => (),
|
||||
Value(name) => {
|
||||
buf.push_str(&format!("Value {})", name));
|
||||
}
|
||||
NamedStruct { name: _, fields: _ } => (),
|
||||
Call { f, arguments } => {
|
||||
let new_indent = indent + LEVEL;
|
||||
buf.push_str("Call ");
|
||||
render_expression(f, new_indent, buf);
|
||||
newline(buf);
|
||||
|
||||
for arg in arguments {
|
||||
do_indent(new_indent, buf);
|
||||
match arg {
|
||||
InvocationArgument::Positional(expr) => render_expression(expr, new_indent, buf),
|
||||
InvocationArgument::Keyword { .. } => buf.push_str("<keyword>"),
|
||||
InvocationArgument::Ignored => buf.push_str("<ignored>"),
|
||||
}
|
||||
newline(buf);
|
||||
do_indent(indent, buf);
|
||||
}
|
||||
}
|
||||
Index { .. } => buf.push_str("<index>"),
|
||||
IfExpression { .. } => buf.push_str("<if-expr>"),
|
||||
WhileExpression { .. } => buf.push_str("<while-expr>"),
|
||||
ForExpression { .. } => buf.push_str("<for-expr>"),
|
||||
Lambda { params, type_anno: _, body } => {
|
||||
let new_indent = indent + LEVEL;
|
||||
buf.push_str("Lambda ");
|
||||
newline(buf);
|
||||
|
||||
do_indent(new_indent, buf);
|
||||
buf.push_str("(Args ");
|
||||
for p in params {
|
||||
buf.push_str(&format!("{} ", p.name));
|
||||
}
|
||||
buf.push(')');
|
||||
newline(buf);
|
||||
|
||||
do_indent(new_indent, buf);
|
||||
buf.push_str("(Body ");
|
||||
newline(buf);
|
||||
render_block(body, new_indent + LEVEL, buf);
|
||||
do_indent(new_indent, buf);
|
||||
buf.push(')');
|
||||
|
||||
newline(buf);
|
||||
do_indent(indent, buf);
|
||||
}
|
||||
Access { .. } => buf.push_str("<access-expr>"),
|
||||
ListLiteral(..) => buf.push_str("<list-literal>"),
|
||||
}
|
||||
buf.push(')');
|
||||
}
|
||||
fn render_declaration(decl: &Declaration, indent: usize, buf: &mut String) {
|
||||
use Declaration::*;
|
||||
|
||||
buf.push_str("(Decl ");
|
||||
match decl {
|
||||
FuncSig(ref sig) => render_signature(sig, indent, buf),
|
||||
FuncDecl(ref sig, ref block) => {
|
||||
let indent = indent + LEVEL;
|
||||
buf.push_str("Function");
|
||||
newline(buf);
|
||||
|
||||
do_indent(indent, buf);
|
||||
render_signature(sig, indent, buf);
|
||||
newline(buf);
|
||||
|
||||
do_indent(indent, buf);
|
||||
buf.push_str("(Body");
|
||||
newline(buf);
|
||||
|
||||
render_block(block, indent + LEVEL, buf);
|
||||
do_indent(indent, buf);
|
||||
buf.push_str(")");
|
||||
newline(buf);
|
||||
}
|
||||
TypeDecl { name: _, body: _, .. } => {
|
||||
buf.push_str("<type-decl>");
|
||||
}
|
||||
TypeAlias { alias: _, original: _ } => {
|
||||
buf.push_str("<type-alias>");
|
||||
}
|
||||
Binding { name, constant: _, type_anno: _, expr } => {
|
||||
let new_indent = indent + LEVEL;
|
||||
buf.push_str(&format!("Binding {}", name));
|
||||
newline(buf);
|
||||
do_indent(new_indent, buf);
|
||||
render_expression(expr, new_indent, buf);
|
||||
newline(buf);
|
||||
}
|
||||
Module { name, items: _ } => {
|
||||
write!(buf, "(Module {} <body>)", name).unwrap();
|
||||
}
|
||||
_ => (), /*
|
||||
Impl { type_name: TypeIdentifier, interface_name: Option<TypeSingletonName>, block: Vec<Declaration> },
|
||||
Interface { name: Rc<String>, signatures: Vec<Signature> },
|
||||
Annotation { name: Rc<String>, arguments: Vec<Expression> },
|
||||
*/
|
||||
}
|
||||
do_indent(indent, buf);
|
||||
buf.push(')');
|
||||
}
|
||||
|
||||
fn render_block(block: &Block, indent: usize, buf: &mut String) {
|
||||
for stmt in block.statements.iter() {
|
||||
render_statement(stmt, indent, buf);
|
||||
newline(buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_signature(sig: &Signature, _indent: usize, buf: &mut String) {
|
||||
buf.push_str(&format!("(Signature {} )", sig.name));
|
||||
}
|
||||
|
||||
fn render_import(_import: &ImportSpecifier, _indent: usize, buf: &mut String) {
|
||||
buf.push_str("(Import <some import>)");
|
||||
}
|
||||
|
||||
fn render_flow_control(flow: &FlowControl, _indent: usize, buf: &mut String) {
|
||||
use FlowControl::*;
|
||||
match flow {
|
||||
Return(ref _expr) => write!(buf, "return <expr>").unwrap(),
|
||||
Break => write!(buf, "break").unwrap(),
|
||||
Continue => write!(buf, "continue").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::render_ast;
|
||||
use crate::util::quick_ast;
|
||||
|
||||
#[test]
|
||||
fn test_visualization() {
|
||||
let ast = quick_ast(
|
||||
r#"
|
||||
fn test(x) {
|
||||
let m = 9
|
||||
1 * 4 <> m |> somemod::output(x)
|
||||
}
|
||||
|
||||
let quincy = \(no, yes, maybe) {
|
||||
let a = 10
|
||||
yes * no + a
|
||||
}
|
||||
|
||||
let b = 54
|
||||
test(b) == 3
|
||||
"#,
|
||||
);
|
||||
|
||||
let expected_output = r#"(AST
|
||||
(Decl Function
|
||||
(Signature test )
|
||||
(Body
|
||||
(Decl Binding m
|
||||
(Expr (NatLiteral 9))
|
||||
)
|
||||
(Expr Binop *
|
||||
(Expr (NatLiteral 1))
|
||||
(Expr Binop |>
|
||||
(Expr Binop <>
|
||||
(Expr (NatLiteral 4))
|
||||
(Expr Value m))
|
||||
)
|
||||
(Expr Call (Expr Value somemod::output))
|
||||
(Expr Value x))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(Decl Binding quincy
|
||||
(Expr Lambda
|
||||
(Args no yes maybe )
|
||||
(Body
|
||||
(Decl Binding a
|
||||
(Expr (NatLiteral 10))
|
||||
)
|
||||
(Expr Binop +
|
||||
(Expr Binop *
|
||||
(Expr Value yes))
|
||||
(Expr Value no))
|
||||
)
|
||||
(Expr Value a))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(Decl Binding b
|
||||
(Expr (NatLiteral 54))
|
||||
)
|
||||
(Expr Binop ==
|
||||
(Expr Call (Expr Value test))
|
||||
(Expr Value b))
|
||||
)
|
||||
(Expr (NatLiteral 3))
|
||||
)
|
||||
)"#;
|
||||
|
||||
let rendered = render_ast(&ast);
|
||||
assert_eq!(rendered, expected_output);
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
use std::{convert::TryFrom, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
ast::{BinOp, PrefixOp},
|
||||
type_inference::Type,
|
||||
};
|
||||
|
||||
/// "Builtin" computational operations with some kind of semantics, mostly mathematical operations.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Builtin {
|
||||
Add,
|
||||
Increment,
|
||||
Subtract,
|
||||
Negate,
|
||||
Multiply,
|
||||
Divide,
|
||||
Quotient,
|
||||
Modulo,
|
||||
Exponentiation,
|
||||
BitwiseAnd,
|
||||
BitwiseOr,
|
||||
BooleanAnd,
|
||||
BooleanOr,
|
||||
BooleanNot,
|
||||
Equality,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
Comparison,
|
||||
IOPrint,
|
||||
IOPrintLn,
|
||||
IOGetLine,
|
||||
Assignment,
|
||||
Concatenate,
|
||||
NotEqual,
|
||||
}
|
||||
|
||||
impl Builtin {
|
||||
#[allow(dead_code)]
|
||||
pub fn get_type(&self) -> Type {
|
||||
use Builtin::*;
|
||||
match self {
|
||||
Add => ty!(Nat -> Nat -> Nat),
|
||||
Subtract => ty!(Nat -> Nat -> Nat),
|
||||
Multiply => ty!(Nat -> Nat -> Nat),
|
||||
Divide => ty!(Nat -> Nat -> Float),
|
||||
Quotient => ty!(Nat -> Nat -> Nat),
|
||||
Modulo => ty!(Nat -> Nat -> Nat),
|
||||
Exponentiation => ty!(Nat -> Nat -> Nat),
|
||||
BitwiseAnd => ty!(Nat -> Nat -> Nat),
|
||||
BitwiseOr => ty!(Nat -> Nat -> Nat),
|
||||
BooleanAnd => ty!(Bool -> Bool -> Bool),
|
||||
BooleanOr => ty!(Bool -> Bool -> Bool),
|
||||
BooleanNot => ty!(Bool -> Bool),
|
||||
Equality => ty!(Nat -> Nat -> Bool),
|
||||
LessThan => ty!(Nat -> Nat -> Bool),
|
||||
LessThanOrEqual => ty!(Nat -> Nat -> Bool),
|
||||
GreaterThan => ty!(Nat -> Nat -> Bool),
|
||||
GreaterThanOrEqual => ty!(Nat -> Nat -> Bool),
|
||||
Comparison => ty!(Nat -> Nat -> Ordering),
|
||||
IOPrint => ty!(Unit),
|
||||
IOPrintLn => ty!(Unit),
|
||||
IOGetLine => ty!(StringT),
|
||||
Assignment => ty!(Unit),
|
||||
Concatenate => ty!(StringT -> StringT -> StringT),
|
||||
Increment => ty!(Nat -> Int),
|
||||
Negate => ty!(Nat -> Int),
|
||||
NotEqual => ty!(Nat -> Nat -> Bool),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&BinOp> for Builtin {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(binop: &BinOp) -> Result<Self, Self::Error> {
|
||||
FromStr::from_str(binop.sigil())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&PrefixOp> for Builtin {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(prefix_op: &PrefixOp) -> Result<Self, Self::Error> {
|
||||
use Builtin::*;
|
||||
|
||||
match prefix_op.sigil() {
|
||||
"+" => Ok(Increment),
|
||||
"-" => Ok(Negate),
|
||||
"!" => Ok(BooleanNot),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Builtin {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use Builtin::*;
|
||||
Ok(match s {
|
||||
"+" => Add,
|
||||
"-" => Subtract,
|
||||
"*" => Multiply,
|
||||
"/" => Divide,
|
||||
"quot" => Quotient,
|
||||
"%" => Modulo,
|
||||
"++" => Concatenate,
|
||||
"^" => Exponentiation,
|
||||
"&" => BitwiseAnd,
|
||||
"&&" => BooleanAnd,
|
||||
"|" => BitwiseOr,
|
||||
"||" => BooleanOr,
|
||||
"!" => BooleanNot,
|
||||
">" => GreaterThan,
|
||||
">=" => GreaterThanOrEqual,
|
||||
"<" => LessThan,
|
||||
"<=" => LessThanOrEqual,
|
||||
"==" => Equality,
|
||||
"!=" => NotEqual,
|
||||
"=" => Assignment,
|
||||
"<=>" => Comparison,
|
||||
"print" => IOPrint,
|
||||
"println" => IOPrintLn,
|
||||
"getline" => IOGetLine,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
use crate::{
|
||||
parsing::{Location, ParseError},
|
||||
schala::{SourceReference, Stage},
|
||||
symbol_table::SymbolError,
|
||||
type_inference::TypeError,
|
||||
};
|
||||
|
||||
pub struct SchalaError {
|
||||
errors: Vec<Error>,
|
||||
}
|
||||
|
||||
impl SchalaError {
|
||||
pub(crate) fn display(&self) -> String {
|
||||
match self.errors[0] {
|
||||
Error::Parse(ref parse_err) => parse_err.to_string(),
|
||||
Error::Standard { ref text, .. } => text.as_ref().cloned().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn from_type_error(err: TypeError) -> Self {
|
||||
Self {
|
||||
errors: vec![Error::Standard { location: None, text: Some(err.msg), stage: Stage::Typechecking }],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_symbol_table(symbol_errs: Vec<SymbolError>) -> Self {
|
||||
//TODO this could be better
|
||||
let errors = symbol_errs
|
||||
.into_iter()
|
||||
.map(|_symbol_err| Error::Standard {
|
||||
location: None,
|
||||
text: Some("symbol table error".to_string()),
|
||||
stage: Stage::Symbols,
|
||||
})
|
||||
.collect();
|
||||
Self { errors }
|
||||
}
|
||||
|
||||
pub(crate) fn from_string(text: String, stage: Stage) -> Self {
|
||||
Self { errors: vec![Error::Standard { location: None, text: Some(text), stage }] }
|
||||
}
|
||||
|
||||
pub(crate) fn from_parse_error(parse_error: ParseError, source_reference: &SourceReference) -> Self {
|
||||
let formatted_parse_error = format_parse_error(parse_error, source_reference);
|
||||
Self { errors: vec![Error::Parse(formatted_parse_error)] }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum Error {
|
||||
Standard { location: Option<Location>, text: Option<String>, stage: Stage },
|
||||
Parse(String),
|
||||
}
|
||||
|
||||
fn format_parse_error(error: ParseError, source_reference: &SourceReference) -> String {
|
||||
let offset = error.location.offset;
|
||||
let (line_start, line_num, line_from_program) = source_reference.get_line(offset);
|
||||
let ch = offset - line_start;
|
||||
|
||||
let location_pointer = format!("{}^", " ".repeat(ch));
|
||||
|
||||
let line_num_digits = format!("{}", line_num).chars().count();
|
||||
let space_padding = " ".repeat(line_num_digits);
|
||||
|
||||
format!(
|
||||
r#"
|
||||
{error_msg}
|
||||
{space_padding} |
|
||||
{line_num} | {}
|
||||
{space_padding} | {}
|
||||
"#,
|
||||
line_from_program,
|
||||
location_pointer,
|
||||
error_msg = error.msg,
|
||||
space_padding = space_padding,
|
||||
line_num = line_num,
|
||||
)
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
hash::Hash,
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
pub trait IdKind: Debug + Copy + Clone + Hash + PartialEq + Eq + Default {
|
||||
fn tag() -> &'static str;
|
||||
}
|
||||
|
||||
/// A generalized abstract identifier type of up to 2^32-1 entries.
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Default)]
|
||||
pub struct Id<T>
|
||||
where T: IdKind
|
||||
{
|
||||
idx: u32,
|
||||
t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Id<T>
|
||||
where T: IdKind
|
||||
{
|
||||
fn new(n: u32) -> Self {
|
||||
Self { idx: n, t: PhantomData }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn as_u32(&self) -> u32 {
|
||||
self.idx
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Display for Id<T>
|
||||
where T: IdKind
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.idx, T::tag())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IdStore<T>
|
||||
where T: IdKind
|
||||
{
|
||||
last_idx: u32,
|
||||
t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> IdStore<T>
|
||||
where T: IdKind
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self { last_idx: 0, t: PhantomData }
|
||||
}
|
||||
|
||||
pub fn fresh(&mut self) -> Id<T> {
|
||||
let idx = self.last_idx;
|
||||
self.last_idx += 1;
|
||||
Id::new(idx)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_id_kind {
|
||||
($name:ident) => {
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Default)]
|
||||
pub struct $name;
|
||||
impl crate::identifier::IdKind for $name {
|
||||
fn tag() -> &'static str {
|
||||
stringify!($name)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use define_id_kind;
|
@ -1,31 +0,0 @@
|
||||
#![feature(trace_macros)]
|
||||
//#![feature(unrestricted_attribute_tokens)]
|
||||
#![feature(box_patterns, iter_intersperse)]
|
||||
|
||||
//! `schala-lang` is where the Schala programming language is actually implemented.
|
||||
//! It defines the `Schala` type, which contains the state for a Schala REPL, and implements
|
||||
//! `ProgrammingLanguageInterface` and the chain of compiler passes for it.
|
||||
|
||||
extern crate derivative;
|
||||
extern crate schala_repl;
|
||||
|
||||
#[macro_use]
|
||||
mod util;
|
||||
|
||||
#[macro_use]
|
||||
mod type_inference;
|
||||
|
||||
mod ast;
|
||||
mod parsing;
|
||||
#[macro_use]
|
||||
mod symbol_table;
|
||||
mod builtin;
|
||||
mod error;
|
||||
mod reduced_ir;
|
||||
mod tree_walk_eval;
|
||||
#[macro_use]
|
||||
mod identifier;
|
||||
|
||||
mod schala;
|
||||
|
||||
pub use schala::{Schala, SchalaConfig};
|
File diff suppressed because it is too large
Load Diff
@ -1,126 +0,0 @@
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
pub mod combinator;
|
||||
mod peg_parser;
|
||||
mod test;
|
||||
|
||||
use std::{cell::RefCell, fmt, rc::Rc};
|
||||
|
||||
use combinator::Span;
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::ast::{Block, Expression};
|
||||
use crate::{
|
||||
ast::{ASTItem, AST},
|
||||
identifier::{Id, IdStore},
|
||||
};
|
||||
|
||||
pub(crate) type StoreRef = Rc<RefCell<IdStore<ASTItem>>>;
|
||||
pub struct Parser {
|
||||
id_store: StoreRef,
|
||||
use_combinator: bool,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub(crate) fn new() -> Self {
|
||||
let id_store: IdStore<ASTItem> = IdStore::new();
|
||||
Self { id_store: Rc::new(RefCell::new(id_store)), use_combinator: true }
|
||||
}
|
||||
pub(crate) fn parse(&mut self, input: &str) -> Result<AST, ParseError> {
|
||||
if self.use_combinator {
|
||||
self.parse_comb(input)
|
||||
} else {
|
||||
self.parse_peg(input)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_peg(&mut self, input: &str) -> Result<AST, ParseError> {
|
||||
peg_parser::schala_parser::program(input, self).map_err(ParseError::from_peg)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_comb(&mut self, input: &str) -> Result<AST, ParseError> {
|
||||
let span = Span::new_extra(input, self.id_store.clone());
|
||||
convert(input, combinator::program(span))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn expression(&mut self, input: &str) -> Result<Expression, ParseError> {
|
||||
peg_parser::schala_parser::expression(input, self).map_err(ParseError::from_peg)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn expression_comb(&mut self, input: &str) -> Result<Expression, ParseError> {
|
||||
let span = Span::new_extra(input, self.id_store.clone());
|
||||
convert(input, combinator::expression(span))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn block(&mut self, input: &str) -> Result<Block, ParseError> {
|
||||
peg_parser::schala_parser::block(input, self).map_err(ParseError::from_peg)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn block_comb(&mut self, input: &str) -> Result<Block, ParseError> {
|
||||
let span = Span::new_extra(input, self.id_store.clone());
|
||||
convert(input, combinator::block(span))
|
||||
}
|
||||
|
||||
fn fresh(&mut self) -> Id<ASTItem> {
|
||||
self.id_store.borrow_mut().fresh()
|
||||
}
|
||||
}
|
||||
|
||||
fn convert<'a, O>(input: &'a str, result: combinator::ParseResult<'a, O>) -> Result<O, ParseError> {
|
||||
use nom::{error::VerboseError, Finish};
|
||||
|
||||
match result.finish() {
|
||||
Ok((rest, output)) => {
|
||||
if rest.fragment() != &"" {
|
||||
return Err(ParseError {
|
||||
location: Default::default(),
|
||||
msg: format!("Bad parse state, remaining text: `{}`", rest.fragment()),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
Err(err) => {
|
||||
let err = VerboseError {
|
||||
errors: err.errors.into_iter().map(|(sp, kind)| (*sp.fragment(), kind)).collect(),
|
||||
};
|
||||
let msg = nom::error::convert_error(input, err);
|
||||
Err(ParseError { msg, location: (0).into() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a parsing error
|
||||
#[derive(Debug)]
|
||||
pub struct ParseError {
|
||||
pub msg: String,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
fn from_peg(err: peg::error::ParseError<peg::str::LineCol>) -> Self {
|
||||
let msg = err.to_string();
|
||||
Self { msg, location: err.location.offset.into() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Location {
|
||||
pub(crate) offset: usize,
|
||||
}
|
||||
|
||||
impl From<usize> for Location {
|
||||
fn from(offset: usize) -> Self {
|
||||
Self { offset }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Location {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.offset)
|
||||
}
|
||||
}
|
@ -1,567 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::Parser;
|
||||
use crate::ast::*;
|
||||
|
||||
fn rc_string(s: &str) -> Rc<String> {
|
||||
Rc::new(s.to_string())
|
||||
}
|
||||
|
||||
enum ExtendedPart<'a> {
|
||||
Index(Vec<Expression>),
|
||||
Accessor(&'a str),
|
||||
Call(Vec<InvocationArgument>),
|
||||
}
|
||||
|
||||
peg::parser! {
|
||||
pub grammar schala_parser() for str {
|
||||
|
||||
rule whitespace() = [' ' | '\t' ]
|
||||
rule whitespace_or_newline() = [' ' | '\t' | '\n' ]
|
||||
|
||||
rule _ = quiet!{ (block_comment() / line_comment() / whitespace())* }
|
||||
|
||||
rule __ = quiet!{ (block_comment() / line_comment() / whitespace_or_newline())* }
|
||||
|
||||
rule block_comment() = "/*" (block_comment() / !"*/" [_])* "*/"
|
||||
rule line_comment() = "//" (!['\n'] [_])* &"\n"
|
||||
|
||||
|
||||
pub rule program(parser: &mut Parser) -> AST =
|
||||
__ statements:(statement(parser) ** (delimiter()+) ) __ { AST { id: parser.fresh(), statements: statements.into() } }
|
||||
|
||||
rule delimiter() = (";" / "\n")+
|
||||
|
||||
//Note - this is a hack, ideally the rule `rule block() -> Block = "{" _ items:(statement() **
|
||||
//delimiter()) _ "}" { items.into() }` would've worked, but it doesn't.
|
||||
pub rule block(parser: &mut Parser) -> Block =
|
||||
"{" __ items:(statement(parser) ** delimiter()) delimiter()? __ "}" { items.into() } /
|
||||
"{" __ stmt:statement(parser) __ "}" { vec![stmt].into() }
|
||||
|
||||
rule block_item(parser: &mut Parser) -> Statement<StatementKind> =
|
||||
_ stmt:statement(parser) _ delimiter()+ { stmt }
|
||||
|
||||
rule statement(parser: &mut Parser) -> Statement<StatementKind> =
|
||||
_ pos:position!() kind:statement_kind(parser) _ { Statement { id: parser.fresh(), location: pos.into(), kind } }
|
||||
|
||||
rule statement_kind(parser: &mut Parser) -> StatementKind =
|
||||
__ import:import(parser) { StatementKind::Import(import) } /
|
||||
__ decl:declaration(parser) { StatementKind::Declaration(decl) } /
|
||||
__ flow:flow(parser) { StatementKind::Flow(flow) } /
|
||||
__ expr:expression(parser) { StatementKind::Expression(expr) }
|
||||
|
||||
rule flow(parser: &mut Parser) -> FlowControl =
|
||||
"continue" { FlowControl::Continue } /
|
||||
"break" { FlowControl::Break } /
|
||||
"return" _ expr:expression(parser)? { FlowControl::Return(expr) }
|
||||
|
||||
//TODO add the ability to rename and exclude imports
|
||||
rule import(parser: &mut Parser) -> ImportSpecifier =
|
||||
"import" _ path_components:path_components() suffix:import_suffix()? {
|
||||
ImportSpecifier {
|
||||
id: parser.fresh(),
|
||||
path_components,
|
||||
imported_names: suffix.unwrap_or(ImportedNames::LastOfPath)
|
||||
}
|
||||
}
|
||||
|
||||
rule path_components() -> Vec<Rc<String>> =
|
||||
"::"? name:identifier() rest:path_component()* {
|
||||
let mut items = vec![rc_string(name)];
|
||||
items.extend(rest.into_iter().map(rc_string));
|
||||
items
|
||||
}
|
||||
|
||||
rule path_component() -> &'input str = "::" ident:identifier() { ident }
|
||||
|
||||
rule import_suffix() -> ImportedNames =
|
||||
"::*" { ImportedNames::All } /
|
||||
"::{" __ names:(identifier() ** (_ "," _)) __ "}" {?
|
||||
if names.is_empty() {
|
||||
Err("import groups must have at least one item")
|
||||
} else {
|
||||
Ok(ImportedNames::List(names.into_iter().map(rc_string).collect()))
|
||||
}
|
||||
}
|
||||
|
||||
rule declaration(parser: &mut Parser) -> Declaration =
|
||||
binding(parser) / type_decl(parser) / annotation(parser) / func(parser) / interface(parser) /
|
||||
implementation(parser) / module(parser)
|
||||
|
||||
rule module(parser: &mut Parser) -> Declaration =
|
||||
"module" _ name:identifier() _ items:block(parser) { Declaration::Module { name: rc_string(name), items } }
|
||||
|
||||
rule implementation(parser: &mut Parser) -> Declaration =
|
||||
"impl" _ interface:type_singleton_name() _ "for" _ type_name:type_identifier() _ block:decl_block(parser) {
|
||||
Declaration::Impl { type_name, interface_name: Some(interface), block }
|
||||
|
||||
} /
|
||||
"impl" _ type_name:type_identifier() _ block:decl_block(parser) {
|
||||
Declaration::Impl { type_name, interface_name: None, block }
|
||||
}
|
||||
|
||||
rule decl_block(parser: &mut Parser) -> Vec<Statement<Declaration>> =
|
||||
"{" __ decls:(func_declaration_stmt(parser) ** (delimiter()+)) delimiter()? __ "}" { decls }
|
||||
|
||||
rule func_declaration_stmt(parser: &mut Parser) -> Statement<Declaration> =
|
||||
pos:position!() decl:func_declaration(parser) { Statement { id: parser.fresh(), location: pos.into(), kind: decl } }
|
||||
|
||||
rule interface(parser: &mut Parser) -> Declaration =
|
||||
"interface" _ name:identifier() _ signatures:signature_block(parser) { Declaration::Interface { name: rc_string(name), signatures } }
|
||||
|
||||
rule signature_block(parser: &mut Parser) -> Vec<Signature> =
|
||||
"{" __ signatures:(func_signature(parser) ** (delimiter()+)) __ "}" { signatures }
|
||||
|
||||
rule func(parser: &mut Parser) -> Declaration =
|
||||
decl:func_declaration(parser) { decl } /
|
||||
sig:func_signature(parser) { Declaration::FuncSig(sig) }
|
||||
|
||||
rule func_declaration(parser: &mut Parser) -> Declaration =
|
||||
_ sig:func_signature(parser) __ body:block(parser) { Declaration::FuncDecl(sig, body) }
|
||||
|
||||
rule func_signature(parser: &mut Parser) -> Signature =
|
||||
_ "fn" _ name:identifier() "(" _ params:formal_params(parser) _ ")" _ type_anno:type_anno()? { Signature {
|
||||
name: rc_string(name), operator: false, params, type_anno
|
||||
} } /
|
||||
_ "fn" _ "(" op:operator() ")" _ "(" _ params:formal_params(parser) _ ")" _ type_anno:type_anno()? { Signature {
|
||||
name: rc_string(op), operator: true, params, type_anno
|
||||
} }
|
||||
|
||||
rule formal_params(parser: &mut Parser) -> Vec<FormalParam> =
|
||||
params:(formal_param(parser) ** (_ "," _)) {? if params.len() < 256 { Ok(params) } else {
|
||||
Err("function-too-long") }
|
||||
}
|
||||
|
||||
rule formal_param(parser: &mut Parser) -> FormalParam =
|
||||
name:identifier() _ anno:type_anno()? _ "=" expr:expression(parser) { FormalParam { name: rc_string(name),
|
||||
default: Some(expr), anno } } /
|
||||
name:identifier() _ anno:type_anno()? { FormalParam { name: rc_string(name), default: None, anno } }
|
||||
|
||||
|
||||
rule annotation(parser: &mut Parser) -> Declaration =
|
||||
"@" name:identifier() args:annotation_args(parser)? delimiter()+ _ inner:statement(parser) { Declaration::Annotation {
|
||||
name: rc_string(name), arguments: if let Some(args) = args { args } else { vec![] }, inner: Box::new(inner) }
|
||||
}
|
||||
|
||||
rule annotation_args(parser: &mut Parser) -> Vec<Expression> =
|
||||
"(" _ args:(expression(parser) ** (_ "," _)) _ ")" { args }
|
||||
|
||||
|
||||
rule binding(parser: &mut Parser) -> Declaration =
|
||||
"let" _ mutable:"mut"? _ ident:identifier() _ type_anno:type_anno()? _ "=" _ expr:expression(parser) {
|
||||
Declaration::Binding { name: Rc::new(ident.to_string()), constant: mutable.is_none(),
|
||||
type_anno, expr }
|
||||
}
|
||||
|
||||
|
||||
rule type_decl(parser: &mut Parser) -> Declaration =
|
||||
"type" _ "alias" _ alias:type_alias() { alias } /
|
||||
"type" _ mutable:"mut"? _ name:type_singleton_name() _ "=" _ body:type_body(parser) {
|
||||
Declaration::TypeDecl { name, body, mutable: mutable.is_some() }
|
||||
}
|
||||
|
||||
rule type_singleton_name() -> TypeSingletonName =
|
||||
name:identifier() params:type_params()? { TypeSingletonName {
|
||||
name: rc_string(name), params: if let Some(params) = params { params } else { vec![] }
|
||||
} }
|
||||
|
||||
rule type_params() -> Vec<TypeIdentifier> =
|
||||
"<" _ idents:(type_identifier() ** (_ "," _)) _ ">" { idents }
|
||||
|
||||
rule type_identifier() -> TypeIdentifier =
|
||||
"(" _ items:(type_identifier() ** (_ "," _)) _ ")" { TypeIdentifier::Tuple(items) } /
|
||||
singleton:type_singleton_name() { TypeIdentifier::Singleton(singleton) }
|
||||
|
||||
rule type_body(parser: &mut Parser) -> TypeBody =
|
||||
"{" _ items:(record_variant_item() ** (__ "," __)) __ "}" { TypeBody::ImmediateRecord { id: parser.fresh(), fields: items } } /
|
||||
variants:(variant_spec(parser) ** (__ "|" __)) { TypeBody::Variants(variants) }
|
||||
|
||||
rule variant_spec(parser: &mut Parser) -> Variant =
|
||||
name:identifier() __ "{" __ typed_identifier_list:(record_variant_item() ** (__ "," __)) __ ","? __ "}" { Variant {
|
||||
id: parser.fresh(), name: rc_string(name), kind: VariantKind::Record(typed_identifier_list)
|
||||
} } /
|
||||
name:identifier() "(" tuple_members:(type_identifier() ++ (__ "," __)) ")" { Variant {
|
||||
id: parser.fresh(), name: rc_string(name), kind: VariantKind::TupleStruct(tuple_members) } } /
|
||||
name:identifier() { Variant { id: parser.fresh(), name: rc_string(name), kind: VariantKind::UnitStruct } }
|
||||
|
||||
rule record_variant_item() -> (Rc<String>, TypeIdentifier) =
|
||||
name:identifier() _ ":" _ ty:type_identifier() { (rc_string(name), ty) }
|
||||
|
||||
rule type_alias() -> Declaration =
|
||||
alias:identifier() _ "=" _ name:identifier() { Declaration::TypeAlias { alias: rc_string(alias), original: rc_string(name), } }
|
||||
|
||||
rule type_anno() -> TypeIdentifier =
|
||||
":" _ identifier:type_identifier() { identifier }
|
||||
|
||||
pub rule expression(parser: &mut Parser) -> Expression =
|
||||
__ kind:expression_kind(true, parser) _ type_anno:type_anno()? { Expression { id: parser.fresh(), type_anno, kind } }
|
||||
|
||||
rule expression_no_struct(parser: &mut Parser) -> Expression =
|
||||
__ kind:expression_kind(false, parser) { Expression { id: parser.fresh(), type_anno: None, kind } }
|
||||
|
||||
rule expression_kind(struct_ok: bool, parser: &mut Parser) -> ExpressionKind =
|
||||
precedence_expr(struct_ok, parser)
|
||||
|
||||
rule precedence_expr(struct_ok: bool, parser: &mut Parser) -> ExpressionKind =
|
||||
first:prefix_expr(struct_ok, parser) _ next:(precedence_continuation(struct_ok, parser))* {
|
||||
let next = next.into_iter().map(|(sigil, expr)| (BinOp::from_sigil(sigil), expr)).collect();
|
||||
BinopSequence { first, next }.do_precedence(parser)
|
||||
}
|
||||
|
||||
rule precedence_continuation(struct_ok: bool, parser: &mut Parser) -> (&'input str, ExpressionKind) =
|
||||
op:operator() _ expr:prefix_expr(struct_ok, parser) _ { (op, expr) }
|
||||
|
||||
rule prefix_expr(struct_ok: bool, parser: &mut Parser) -> ExpressionKind =
|
||||
prefix:prefix()? expr:extended_expr(struct_ok, parser) {
|
||||
if let Some(p) = prefix {
|
||||
let expr = Expression::new(parser.fresh(), expr);
|
||||
let prefix = PrefixOp::from_sigil(p);
|
||||
ExpressionKind::PrefixExp(prefix, Box::new(expr))
|
||||
} else {
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rule prefix() -> &'input str =
|
||||
$(['+' | '-' | '!' ])
|
||||
|
||||
//TODO make the definition of operators more complex
|
||||
rule operator() -> &'input str =
|
||||
quiet!{!"*/" s:$( ['+' | '-' | '*' | '/' | '%' | '<' | '>' | '=' | '!' | '$' | '&' | '|' | '?' | '^' | '`']+ ) { s } } /
|
||||
expected!("operator")
|
||||
|
||||
rule extended_expr(struct_ok: bool, parser: &mut Parser) -> ExpressionKind =
|
||||
primary:primary(struct_ok, parser) parts:(extended_expr_part(parser)*) {
|
||||
let mut expression = Expression::new(parser.fresh(), primary);
|
||||
for part in parts.into_iter() {
|
||||
let kind = match part {
|
||||
ExtendedPart::Index(indexers) => {
|
||||
ExpressionKind::Index { indexee: Box::new(expression), indexers }
|
||||
},
|
||||
ExtendedPart::Accessor(name) => {
|
||||
let name = rc_string(name);
|
||||
ExpressionKind::Access { name, expr: Box::new(expression) }
|
||||
},
|
||||
ExtendedPart::Call(arguments) => {
|
||||
ExpressionKind::Call { f: Box::new(expression), arguments }
|
||||
}
|
||||
};
|
||||
|
||||
expression = Expression::new(parser.fresh(), kind);
|
||||
}
|
||||
|
||||
expression.kind
|
||||
}
|
||||
|
||||
rule extended_expr_part(parser: &mut Parser) -> ExtendedPart<'input> =
|
||||
indexers:index_part(parser) { ExtendedPart::Index(indexers) } /
|
||||
arguments:call_part(parser) { ExtendedPart::Call(arguments) } /
|
||||
"." name:identifier() { ExtendedPart::Accessor(name) }
|
||||
|
||||
rule index_part(parser: &mut Parser) -> Vec<Expression> =
|
||||
"[" indexers:(expression(parser) ++ ",") "]" { indexers }
|
||||
|
||||
rule call_part(parser: &mut Parser) -> Vec<InvocationArgument> =
|
||||
"(" arguments:(invocation_argument(parser) ** ",") ")" { arguments }
|
||||
|
||||
rule invocation_argument(parser: &mut Parser) -> InvocationArgument =
|
||||
_ "_" _ { InvocationArgument::Ignored } /
|
||||
_ ident:identifier() _ "=" _ expr:expression(parser) { InvocationArgument::Keyword {
|
||||
name: Rc::new(ident.to_string()),
|
||||
expr
|
||||
} } /
|
||||
_ expr:expression(parser) _ { InvocationArgument::Positional(expr) }
|
||||
|
||||
|
||||
rule primary(struct_ok: bool, parser: &mut Parser) -> ExpressionKind =
|
||||
while_expr(parser) / for_expr(parser) / float_literal() / nat_literal() / bool_literal() /
|
||||
string_literal() / paren_expr(parser) /
|
||||
list_expr(parser) / if_expr(parser) / lambda_expr(parser) /
|
||||
item:named_struct(parser) {? if struct_ok { Ok(item) } else { Err("no-struct-allowed") } } /
|
||||
identifier_expr(parser)
|
||||
|
||||
rule lambda_expr(parser: &mut Parser) -> ExpressionKind =
|
||||
r#"\"# __ "(" _ params:formal_params(parser) _ ")" _ type_anno:(type_anno()?) _ body:block(parser) {
|
||||
ExpressionKind::Lambda { params, type_anno, body }
|
||||
} /
|
||||
r#"\"# param:formal_param(parser) _ type_anno:(type_anno()?) _ body:block(parser) {
|
||||
ExpressionKind::Lambda { params: vec![param], type_anno, body }
|
||||
}
|
||||
|
||||
rule for_expr(parser: &mut Parser) -> ExpressionKind =
|
||||
"for" _ enumerators:for_enumerators(parser) _ body:for_body(parser) {
|
||||
ExpressionKind::ForExpression { enumerators, body }
|
||||
}
|
||||
|
||||
rule for_enumerators(parser: &mut Parser) -> Vec<Enumerator> =
|
||||
"{" _ enumerators:(enumerator(parser) ++ ",") _ "}" { enumerators } /
|
||||
enumerator:enumerator(parser) { vec![enumerator] }
|
||||
|
||||
//TODO add guards, etc.
|
||||
rule enumerator(parser: &mut Parser) -> Enumerator =
|
||||
ident:identifier() _ "<-" _ generator:expression_no_struct(parser) {
|
||||
Enumerator { identifier: Rc::new(ident.to_string()), generator, assignment: false }
|
||||
} /
|
||||
//TODO need to distinguish these two cases in AST
|
||||
ident:identifier() _ "=" _ generator:expression_no_struct(parser) {
|
||||
Enumerator { identifier: Rc::new(ident.to_string()), generator, assignment: true }
|
||||
}
|
||||
|
||||
rule for_body(parser: &mut Parser) -> Box<ForBody> =
|
||||
"return" _ expr:expression(parser) { Box::new(ForBody::MonadicReturn(expr)) } /
|
||||
body:block(parser) { Box::new(ForBody::StatementBlock(body)) }
|
||||
|
||||
rule while_expr(parser: &mut Parser) -> ExpressionKind =
|
||||
"while" _ cond:expression_kind(false, parser)? _ body:block(parser) {
|
||||
ExpressionKind::WhileExpression {
|
||||
condition: cond.map(|kind| Box::new(Expression::new(parser.fresh(), kind))),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rule identifier_expr(parser: &mut Parser) -> ExpressionKind =
|
||||
qn:qualified_identifier(parser) { ExpressionKind::Value(qn) }
|
||||
|
||||
rule named_struct(parser: &mut Parser) -> ExpressionKind =
|
||||
name:qualified_identifier(parser) _ fields:record_block(parser) {
|
||||
ExpressionKind::NamedStruct {
|
||||
name,
|
||||
fields: fields.into_iter().map(|(n, exp)| (Rc::new(n.to_string()), exp)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO support anonymous structs and Elm-style update syntax for structs
|
||||
rule record_block(parser: &mut Parser) -> Vec<(&'input str, Expression)> =
|
||||
"{" _ entries:(record_entry(parser) ** ",") _ "}" { entries }
|
||||
|
||||
rule record_entry(parser: &mut Parser) -> (&'input str, Expression) =
|
||||
_ name:identifier() _ ":" _ expr:expression(parser) _ { (name, expr) }
|
||||
|
||||
rule qualified_identifier(parser: &mut Parser) -> QualifiedName =
|
||||
names:(identifier() ++ "::") { QualifiedName { id: parser.fresh(), components: names.into_iter().map(|name| Rc::new(name.to_string())).collect() } }
|
||||
|
||||
//TODO improve the definition of identifiers
|
||||
rule identifier() -> &'input str =
|
||||
!(reserved() !(ident_continuation())) text:$(['a'..='z' | 'A'..='Z' | '_'] ident_continuation()*) { text }
|
||||
|
||||
rule ident_continuation() -> &'input str =
|
||||
text:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '_'])
|
||||
|
||||
rule reserved() = "if" / "then" / "else" / "is" / "fn" / "for" / "while" / "let" / "in" / "mut" / "return" /
|
||||
"break" / "alias" / "type" / "self" / "Self" / "interface" / "impl" / "true" / "false" / "module" / "import"
|
||||
|
||||
|
||||
rule if_expr(parser: &mut Parser) -> ExpressionKind =
|
||||
"if" _ discriminator:(expression(parser)?) _ body:if_expr_body(parser) {
|
||||
ExpressionKind::IfExpression {
|
||||
discriminator: discriminator.map(Box::new),
|
||||
body: Box::new(body),
|
||||
}
|
||||
}
|
||||
|
||||
rule if_expr_body(parser: &mut Parser) -> IfExpressionBody =
|
||||
cond_block(parser) / simple_pattern_match(parser) / simple_conditional(parser)
|
||||
|
||||
rule simple_conditional(parser: &mut Parser) -> IfExpressionBody =
|
||||
"then" _ then_case:expr_or_block(parser) _ else_case:else_case(parser) {
|
||||
IfExpressionBody::SimpleConditional { then_case, else_case }
|
||||
}
|
||||
|
||||
rule simple_pattern_match(parser: &mut Parser) -> IfExpressionBody =
|
||||
"is" _ pattern:pattern(parser) _ "then" _ then_case:expr_or_block(parser) _ else_case:else_case(parser) {
|
||||
IfExpressionBody::SimplePatternMatch { pattern, then_case, else_case }
|
||||
}
|
||||
|
||||
rule cond_block(parser: &mut Parser) -> IfExpressionBody =
|
||||
"{" __ cond_arms:(cond_arm(parser) ++ (delimiter()+)) __ "}" { IfExpressionBody::CondList(cond_arms) }
|
||||
|
||||
rule cond_arm(parser: &mut Parser) -> ConditionArm =
|
||||
_ "else" _ body:expr_or_block(parser) { ConditionArm { condition: Condition::Else, guard: None, body } } /
|
||||
_ condition:condition(parser) _ guard:condition_guard(parser) _ "then" _ body:expr_or_block(parser)
|
||||
{ ConditionArm { condition, guard, body } }
|
||||
|
||||
rule condition(parser: &mut Parser) -> Condition =
|
||||
"is" _ pat:pattern(parser) { Condition::Pattern(pat) } /
|
||||
op:operator() _ expr:expression(parser) { Condition::TruncatedOp(BinOp::from_sigil(op), expr) }
|
||||
|
||||
rule condition_guard(parser: &mut Parser) -> Option<Expression> =
|
||||
("if" _ expr:expression(parser) { expr } )?
|
||||
|
||||
rule expr_or_block(parser: &mut Parser) -> Block = block(parser) / pos:position!() ex:expression(parser) {
|
||||
Statement {
|
||||
id: parser.fresh() , location: pos.into(),
|
||||
kind: StatementKind::Expression(ex)
|
||||
}.into()
|
||||
}
|
||||
|
||||
rule else_case(parser: &mut Parser) -> Option<Block> =
|
||||
("else" _ eorb:expr_or_block(parser) { eorb })?
|
||||
|
||||
rule pattern(parser: &mut Parser) -> Pattern =
|
||||
"(" _ variants:(pattern(parser) ++ ",") _ ")" { Pattern::TuplePattern(variants) } /
|
||||
_ pat:simple_pattern(parser) { pat }
|
||||
|
||||
rule simple_pattern(parser: &mut Parser) -> Pattern =
|
||||
pattern_literal() /
|
||||
qn:qualified_identifier(parser) "(" members:(pattern(parser) ** ",") ")" {
|
||||
Pattern::TupleStruct(qn, members)
|
||||
} /
|
||||
qn:qualified_identifier(parser) _ "{" _ items:(record_pattern_entry(parser) ** ",") "}" _ {
|
||||
let items = items.into_iter().map(|(name, pat)| (Rc::new(name.to_string()), pat)).collect();
|
||||
Pattern::Record(qn, items)
|
||||
} /
|
||||
qn:qualified_identifier(parser) { Pattern::VarOrName(qn) }
|
||||
|
||||
rule record_pattern_entry(parser: &mut Parser) -> (&'input str, Pattern) =
|
||||
_ name:identifier() _ ":" _ pat:pattern(parser) _ { (name, pat) } /
|
||||
_ name:identifier() _ {
|
||||
let qn = QualifiedName {
|
||||
id: parser.fresh(),
|
||||
components: vec![Rc::new(name.to_string())],
|
||||
};
|
||||
(name, Pattern::VarOrName(qn))
|
||||
}
|
||||
|
||||
|
||||
rule pattern_literal() -> Pattern =
|
||||
"true" { Pattern::Literal(PatternLiteral::BoolPattern(true)) } /
|
||||
"false" { Pattern::Literal(PatternLiteral::BoolPattern(false)) } /
|
||||
s:bare_string_literal() { Pattern::Literal(PatternLiteral::StringPattern(Rc::new(s))) } /
|
||||
sign:("-"?) num:(float_literal() / nat_literal()) {
|
||||
let neg = sign.is_some();
|
||||
Pattern::Literal(PatternLiteral::NumPattern { neg, num })
|
||||
} /
|
||||
"_" { Pattern::Ignored }
|
||||
|
||||
|
||||
rule list_expr(parser: &mut Parser) -> ExpressionKind =
|
||||
"[" exprs:(expression(parser) ** ",") "]" {
|
||||
let mut exprs = exprs;
|
||||
ExpressionKind::ListLiteral(exprs)
|
||||
}
|
||||
|
||||
rule paren_expr(parser: &mut Parser) -> ExpressionKind =
|
||||
"(" exprs:(expression(parser) ** ",") ")" {
|
||||
let mut exprs = exprs;
|
||||
match exprs.len() {
|
||||
1 => exprs.pop().unwrap().kind,
|
||||
_ => ExpressionKind::TupleLiteral(exprs),
|
||||
}
|
||||
}
|
||||
|
||||
rule string_literal() -> ExpressionKind =
|
||||
prefix:identifier()? s:bare_string_literal(){ ExpressionKind::StringLiteral{ s: Rc::new(s),
|
||||
prefix: prefix.map(rc_string)
|
||||
} }
|
||||
|
||||
rule bare_string_literal() -> String =
|
||||
"\"" chars:string_component()* "\"" { chars.into_iter().collect::<String>() }
|
||||
|
||||
rule string_component() -> char =
|
||||
!(r#"""# / r#"\"#) ch:$([_]) { ch.chars().next().unwrap() } /
|
||||
r#"\u{"# value:$(['0'..='9' | 'a'..='f' | 'A'..='F']+) "}" { char::from_u32(u32::from_str_radix(value, 16).unwrap()).unwrap() } /
|
||||
r#"\n"# { '\n' } / r#"\t"# { '\t' } / r#"\""# { '"' } / r#"\\"# { '\\' } /
|
||||
expected!("Valid escape sequence")
|
||||
|
||||
rule bool_literal() -> ExpressionKind =
|
||||
"true" { ExpressionKind::BoolLiteral(true) } / "false" { ExpressionKind::BoolLiteral(false) }
|
||||
|
||||
rule nat_literal() -> ExpressionKind =
|
||||
bin_literal() / hex_literal() / unmarked_literal()
|
||||
|
||||
rule unmarked_literal() -> ExpressionKind =
|
||||
digits:digits() { let n = digits.chars().filter(|ch| *ch != '_').collect::<String>().parse().unwrap(); ExpressionKind::NatLiteral(n) }
|
||||
|
||||
rule bin_literal() -> ExpressionKind =
|
||||
"0b" digits:bin_digits() {? parse_binary(digits).map(ExpressionKind::NatLiteral) }
|
||||
|
||||
rule hex_literal() -> ExpressionKind =
|
||||
"0x" digits:hex_digits() {? parse_hex(digits).map(ExpressionKind::NatLiteral) }
|
||||
|
||||
rule float_literal() -> ExpressionKind =
|
||||
ds:$( digits() "." digits()? / "." digits() ) { ExpressionKind::FloatLiteral(ds.parse().unwrap()) }
|
||||
|
||||
rule digits() -> &'input str = $((digit_group() "_"*)+)
|
||||
rule bin_digits() -> &'input str = $((bin_digit_group() "_"*)+)
|
||||
rule hex_digits() -> &'input str = $((hex_digit_group() "_"*)+)
|
||||
|
||||
rule digit_group() -> &'input str = $(['0'..='9']+)
|
||||
rule bin_digit_group() -> &'input str = $(['0' | '1']+)
|
||||
rule hex_digit_group() -> &'input str = $(['0'..='9' | 'a'..='f' | 'A'..='F']+)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_binary(digits: &str) -> Result<u64, &'static str> {
|
||||
let mut result: u64 = 0;
|
||||
let mut multiplier = 1;
|
||||
for d in digits.chars().rev() {
|
||||
match d {
|
||||
'1' => result += multiplier,
|
||||
'0' => (),
|
||||
'_' => continue,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
multiplier = match multiplier.checked_mul(2) {
|
||||
Some(m) => m,
|
||||
None => return Err("Binary expression will overflow"),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn parse_hex(digits: &str) -> Result<u64, &'static str> {
|
||||
let mut result: u64 = 0;
|
||||
let mut multiplier: u64 = 1;
|
||||
for d in digits.chars().rev() {
|
||||
if d == '_' {
|
||||
continue;
|
||||
}
|
||||
match d.to_digit(16) {
|
||||
Some(n) => result += n as u64 * multiplier,
|
||||
None => return Err("Internal parser error: invalid hex digit"),
|
||||
}
|
||||
multiplier = match multiplier.checked_mul(16) {
|
||||
Some(m) => m,
|
||||
None => return Err("Hexadecimal expression will overflow"),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BinopSequence {
|
||||
first: ExpressionKind,
|
||||
next: Vec<(BinOp, ExpressionKind)>,
|
||||
}
|
||||
|
||||
impl BinopSequence {
|
||||
fn do_precedence(self, parser: &mut Parser) -> ExpressionKind {
|
||||
fn helper(
|
||||
precedence: i32,
|
||||
lhs: ExpressionKind,
|
||||
rest: &mut Vec<(BinOp, ExpressionKind)>,
|
||||
parser: &mut Parser,
|
||||
) -> Expression {
|
||||
let mut lhs = Expression::new(parser.fresh(), lhs);
|
||||
while let Some((next_op, next_rhs)) = rest.pop() {
|
||||
let new_precedence = next_op.get_precedence();
|
||||
if precedence >= new_precedence {
|
||||
rest.push((next_op, next_rhs));
|
||||
break;
|
||||
}
|
||||
let rhs = helper(new_precedence, next_rhs, rest, parser);
|
||||
lhs = Expression::new(
|
||||
parser.fresh(),
|
||||
ExpressionKind::BinExp(next_op, Box::new(lhs), Box::new(rhs)),
|
||||
);
|
||||
}
|
||||
lhs
|
||||
}
|
||||
let mut as_stack = self.next.into_iter().rev().collect();
|
||||
helper(BinOp::min_precedence(), self.first, &mut as_stack, parser).kind
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,484 +0,0 @@
|
||||
use std::{collections::HashMap, rc::Rc, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
ast,
|
||||
builtin::Builtin,
|
||||
symbol_table::{DefId, SymbolSpec, SymbolTable},
|
||||
type_inference::{TypeContext, TypeId},
|
||||
};
|
||||
|
||||
mod test;
|
||||
mod types;
|
||||
|
||||
pub use types::*;
|
||||
|
||||
pub fn reduce(ast: &ast::AST, symbol_table: &SymbolTable, type_context: &TypeContext) -> ReducedIR {
|
||||
let reducer = Reducer::new(symbol_table, type_context);
|
||||
reducer.reduce(ast)
|
||||
}
|
||||
|
||||
struct Reducer<'a, 'b> {
|
||||
symbol_table: &'a SymbolTable,
|
||||
functions: HashMap<DefId, FunctionDefinition>,
|
||||
type_context: &'b TypeContext,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Reducer<'a, 'b> {
|
||||
fn new(symbol_table: &'a SymbolTable, type_context: &'b TypeContext) -> Self {
|
||||
Self { symbol_table, functions: HashMap::new(), type_context }
|
||||
}
|
||||
|
||||
fn reduce(mut self, ast: &ast::AST) -> ReducedIR {
|
||||
// First reduce all functions
|
||||
// TODO once this works, maybe rewrite it using the Visitor
|
||||
for statement in ast.statements.statements.iter() {
|
||||
self.top_level_definition(statement);
|
||||
}
|
||||
|
||||
// Then compute the entrypoint statements (which may reference previously-computed
|
||||
// functions by ID)
|
||||
let mut entrypoint = vec![];
|
||||
for statement in ast.statements.statements.iter() {
|
||||
let ast::Statement { id: item_id, kind, .. } = statement;
|
||||
match &kind {
|
||||
ast::StatementKind::Expression(expr) => {
|
||||
entrypoint.push(Statement::Expression(self.expression(expr)));
|
||||
}
|
||||
ast::StatementKind::Declaration(ast::Declaration::Binding {
|
||||
name: _,
|
||||
constant,
|
||||
expr,
|
||||
..
|
||||
}) => {
|
||||
let symbol = self.symbol_table.lookup_symbol(item_id).unwrap();
|
||||
entrypoint.push(Statement::Binding {
|
||||
id: symbol.def_id(),
|
||||
constant: *constant,
|
||||
expr: self.expression(expr),
|
||||
});
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
ReducedIR { functions: self.functions, entrypoint }
|
||||
}
|
||||
|
||||
fn top_level_definition(&mut self, statement: &ast::Statement<ast::StatementKind>) {
|
||||
let ast::Statement { id: item_id, kind, .. } = statement;
|
||||
match kind {
|
||||
ast::StatementKind::Expression(_expr) => {
|
||||
//TODO expressions can in principle contain definitions, but I won't worry
|
||||
//about it now
|
||||
}
|
||||
ast::StatementKind::Declaration(decl) => match decl {
|
||||
ast::Declaration::FuncDecl(_, statements) => {
|
||||
self.insert_function_definition(item_id, statements);
|
||||
}
|
||||
ast::Declaration::Impl { type_name: _, interface_name: _, block } =>
|
||||
for item in block {
|
||||
if let ast::Statement {
|
||||
id: item_id,
|
||||
kind: ast::Declaration::FuncDecl(_, statements),
|
||||
..
|
||||
} = item
|
||||
{
|
||||
self.insert_function_definition(item_id, statements);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
// Imports should have already been processed by the symbol table and are irrelevant
|
||||
// for this representation.
|
||||
ast::StatementKind::Import(..) => (),
|
||||
ast::StatementKind::Flow(..) => {
|
||||
//TODO this should be an error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn function_internal_statement(
|
||||
&mut self,
|
||||
statement: &ast::Statement<ast::StatementKind>,
|
||||
) -> Option<Statement> {
|
||||
let ast::Statement { id: item_id, kind, .. } = statement;
|
||||
match kind {
|
||||
ast::StatementKind::Expression(expr) => Some(Statement::Expression(self.expression(expr))),
|
||||
ast::StatementKind::Declaration(decl) => match decl {
|
||||
ast::Declaration::FuncDecl(_, statements) => {
|
||||
self.insert_function_definition(item_id, statements);
|
||||
None
|
||||
}
|
||||
ast::Declaration::Binding { constant, expr, .. } => {
|
||||
let symbol = self.symbol_table.lookup_symbol(item_id).unwrap();
|
||||
Some(Statement::Binding {
|
||||
id: symbol.def_id(),
|
||||
constant: *constant,
|
||||
expr: self.expression(expr),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
ast::StatementKind::Import(_) => None,
|
||||
ast::StatementKind::Flow(ast::FlowControl::Return(expr)) =>
|
||||
if let Some(expr) = expr {
|
||||
Some(Statement::Return(self.expression(expr)))
|
||||
} else {
|
||||
Some(Statement::Return(Expression::unit()))
|
||||
},
|
||||
ast::StatementKind::Flow(ast::FlowControl::Break) => Some(Statement::Break),
|
||||
ast::StatementKind::Flow(ast::FlowControl::Continue) => Some(Statement::Continue),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_function_definition(&mut self, item_id: &ast::ItemId, statements: &ast::Block) {
|
||||
let symbol = self.symbol_table.lookup_symbol(item_id).unwrap();
|
||||
let function_def = FunctionDefinition { body: self.function_internal_block(statements) };
|
||||
self.functions.insert(symbol.def_id(), function_def);
|
||||
}
|
||||
|
||||
//TODO this needs to be type-aware to work correctly
|
||||
fn lookup_method(&mut self, name: &str) -> Option<DefId> {
|
||||
for (def_id, function) in self.functions.iter() {
|
||||
let symbol = self.symbol_table.lookup_symbol_by_def(def_id)?;
|
||||
println!("Def Id: {} symbol: {:?}", def_id, symbol);
|
||||
if symbol.local_name() == name {
|
||||
return Some(*def_id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn expression(&mut self, expr: &ast::Expression) -> Expression {
|
||||
use crate::ast::ExpressionKind::*;
|
||||
|
||||
match &expr.kind {
|
||||
SelfValue => Expression::Lookup(Lookup::SelfParam),
|
||||
NatLiteral(n) => Expression::Literal(Literal::Nat(*n)),
|
||||
FloatLiteral(f) => Expression::Literal(Literal::Float(*f)),
|
||||
//TODO implement handling string literal prefixes
|
||||
StringLiteral { s, prefix: _ } => Expression::Literal(Literal::StringLit(s.clone())),
|
||||
BoolLiteral(b) => Expression::Literal(Literal::Bool(*b)),
|
||||
BinExp(binop, lhs, rhs) => self.binop(binop, lhs, rhs),
|
||||
PrefixExp(op, arg) => self.prefix(op, arg),
|
||||
Value(qualified_name) => self.value(qualified_name),
|
||||
Call { f, arguments } => {
|
||||
let f = self.expression(f);
|
||||
let args = arguments.iter().map(|arg| self.invocation_argument(arg)).collect();
|
||||
//TODO need to have full type availability at this point to do this method lookup
|
||||
//correctly
|
||||
if let Expression::Access { name, expr } = f {
|
||||
let def_id = self.lookup_method(&name).unwrap();
|
||||
let method = Expression::Lookup(Lookup::Function(def_id));
|
||||
Expression::CallMethod { f: Box::new(method), args, self_expr: expr }
|
||||
} else {
|
||||
Expression::Call { f: Box::new(f), args }
|
||||
}
|
||||
}
|
||||
TupleLiteral(exprs) => Expression::Tuple(exprs.iter().map(|e| self.expression(e)).collect()),
|
||||
IfExpression { discriminator, body } =>
|
||||
self.reduce_if_expression(discriminator.as_ref().map(|x| x.as_ref()), body),
|
||||
Lambda { params, body, .. } => Expression::Callable(Callable::Lambda {
|
||||
arity: params.len() as u8,
|
||||
body: self.function_internal_block(body),
|
||||
}),
|
||||
NamedStruct { name, fields } => {
|
||||
let symbol = match self.symbol_table.lookup_symbol(&name.id) {
|
||||
Some(symbol) => symbol,
|
||||
None => return Expression::ReductionError(format!("No symbol found for {}", name)),
|
||||
};
|
||||
let (tag, type_id) = match symbol.spec() {
|
||||
SymbolSpec::RecordConstructor { tag, type_id } => (tag, type_id),
|
||||
e => return Expression::ReductionError(format!("Bad symbol for NamedStruct: {:?}", e)),
|
||||
};
|
||||
|
||||
let field_order = compute_field_orderings(self.type_context, &type_id, tag).unwrap();
|
||||
|
||||
let mut field_map = HashMap::new();
|
||||
for (name, expr) in fields.iter() {
|
||||
field_map.insert(name.as_ref(), expr);
|
||||
}
|
||||
|
||||
let mut ordered_args = vec![];
|
||||
for field in field_order.iter() {
|
||||
let expr = match field_map.get(&field) {
|
||||
Some(expr) => expr,
|
||||
None =>
|
||||
return Expression::ReductionError(format!(
|
||||
"Field {} not specified for record {}",
|
||||
field, name
|
||||
)),
|
||||
};
|
||||
ordered_args.push(self.expression(expr));
|
||||
}
|
||||
|
||||
let constructor =
|
||||
Expression::Callable(Callable::RecordConstructor { type_id, tag, field_order });
|
||||
Expression::Call { f: Box::new(constructor), args: ordered_args }
|
||||
}
|
||||
Index { indexee, indexers } => self.reduce_index(indexee.as_ref(), indexers.as_slice()),
|
||||
WhileExpression { condition, body } => {
|
||||
let cond = Box::new(if let Some(condition) = condition {
|
||||
self.expression(condition)
|
||||
} else {
|
||||
Expression::Literal(Literal::Bool(true))
|
||||
});
|
||||
let statements = self.function_internal_block(body);
|
||||
Expression::Loop { cond, statements }
|
||||
}
|
||||
ForExpression { .. } => Expression::ReductionError("For expr not implemented".to_string()),
|
||||
ListLiteral(items) => Expression::List(items.iter().map(|item| self.expression(item)).collect()),
|
||||
Access { name, expr } =>
|
||||
Expression::Access { name: name.as_ref().to_string(), expr: Box::new(self.expression(expr)) },
|
||||
}
|
||||
}
|
||||
|
||||
//TODO figure out the semantics of multiple indexers - for now, just ignore them
|
||||
fn reduce_index(&mut self, indexee: &ast::Expression, indexers: &[ast::Expression]) -> Expression {
|
||||
if indexers.len() != 1 {
|
||||
return Expression::ReductionError("Invalid index expression".to_string());
|
||||
}
|
||||
let indexee = self.expression(indexee);
|
||||
let indexer = self.expression(&indexers[0]);
|
||||
Expression::Index { indexee: Box::new(indexee), indexer: Box::new(indexer) }
|
||||
}
|
||||
|
||||
fn reduce_if_expression(
|
||||
&mut self,
|
||||
discriminator: Option<&ast::Expression>,
|
||||
body: &ast::IfExpressionBody,
|
||||
) -> Expression {
|
||||
use ast::IfExpressionBody::*;
|
||||
|
||||
let cond = Box::new(match discriminator {
|
||||
Some(expr) => self.expression(expr),
|
||||
None => return Expression::ReductionError("blank cond if-expr not supported".to_string()),
|
||||
});
|
||||
match body {
|
||||
SimpleConditional { then_case, else_case } => {
|
||||
let then_clause = self.function_internal_block(then_case);
|
||||
let else_clause = match else_case.as_ref() {
|
||||
None => vec![],
|
||||
Some(stmts) => self.function_internal_block(stmts),
|
||||
};
|
||||
Expression::Conditional { cond, then_clause, else_clause }
|
||||
}
|
||||
SimplePatternMatch { pattern, then_case, else_case } => {
|
||||
let alternatives = vec![
|
||||
Alternative {
|
||||
pattern: match pattern.reduce(self.symbol_table) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return Expression::ReductionError(format!("Bad pattern: {:?}", e)),
|
||||
},
|
||||
item: self.function_internal_block(then_case),
|
||||
},
|
||||
Alternative {
|
||||
pattern: Pattern::Ignored,
|
||||
item: match else_case.as_ref() {
|
||||
Some(else_case) => self.function_internal_block(else_case),
|
||||
None => vec![],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
Expression::CaseMatch { cond, alternatives }
|
||||
}
|
||||
CondList(ref condition_arms) => {
|
||||
let mut alternatives = vec![];
|
||||
for arm in condition_arms {
|
||||
match arm.condition {
|
||||
ast::Condition::Pattern(ref pat) => {
|
||||
let alt = Alternative {
|
||||
pattern: match pat.reduce(self.symbol_table) {
|
||||
Ok(p) => p,
|
||||
Err(e) =>
|
||||
return Expression::ReductionError(format!("Bad pattern: {:?}", e)),
|
||||
},
|
||||
item: self.function_internal_block(&arm.body),
|
||||
};
|
||||
alternatives.push(alt);
|
||||
}
|
||||
ast::Condition::TruncatedOp(_, _) =>
|
||||
return Expression::ReductionError("case-expression-trunc-op".to_string()),
|
||||
ast::Condition::Else =>
|
||||
return Expression::ReductionError("case-expression-else".to_string()),
|
||||
}
|
||||
}
|
||||
Expression::CaseMatch { cond, alternatives }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn invocation_argument(&mut self, invoc: &ast::InvocationArgument) -> Expression {
|
||||
use crate::ast::InvocationArgument::*;
|
||||
match invoc {
|
||||
Positional(ex) => self.expression(ex),
|
||||
Keyword { .. } => Expression::ReductionError("Keyword arguments not supported".to_string()),
|
||||
Ignored => Expression::ReductionError("Ignored arguments not supported".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn function_internal_block(&mut self, block: &ast::Block) -> Vec<Statement> {
|
||||
block.statements.iter().filter_map(|stmt| self.function_internal_statement(stmt)).collect()
|
||||
}
|
||||
|
||||
fn prefix(&mut self, prefix: &ast::PrefixOp, arg: &ast::Expression) -> Expression {
|
||||
let builtin: Option<Builtin> = TryFrom::try_from(prefix).ok();
|
||||
match builtin {
|
||||
Some(op) => Expression::Call {
|
||||
f: Box::new(Expression::Callable(Callable::Builtin(op))),
|
||||
args: vec![self.expression(arg)],
|
||||
},
|
||||
None => {
|
||||
//TODO need this for custom prefix ops
|
||||
Expression::ReductionError("User-defined prefix ops not supported".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn binop(&mut self, binop: &ast::BinOp, lhs: &ast::Expression, rhs: &ast::Expression) -> Expression {
|
||||
use Expression::ReductionError;
|
||||
|
||||
let operation = Builtin::from_str(binop.sigil()).ok();
|
||||
match operation {
|
||||
Some(Builtin::Assignment) => {
|
||||
let lval = match &lhs.kind {
|
||||
ast::ExpressionKind::Value(qualified_name) => {
|
||||
if let Some(symbol) = self.symbol_table.lookup_symbol(&qualified_name.id) {
|
||||
symbol.def_id()
|
||||
} else {
|
||||
return ReductionError(format!("Couldn't look up name: {:?}", qualified_name));
|
||||
}
|
||||
}
|
||||
_ => return ReductionError("Trying to assign to a non-name".to_string()),
|
||||
};
|
||||
|
||||
Expression::Assign { lval, rval: Box::new(self.expression(rhs)) }
|
||||
}
|
||||
Some(op) => Expression::Call {
|
||||
f: Box::new(Expression::Callable(Callable::Builtin(op))),
|
||||
args: vec![self.expression(lhs), self.expression(rhs)],
|
||||
},
|
||||
//TODO handle a user-defined operation
|
||||
None => ReductionError("User-defined operations not supported".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn value(&mut self, qualified_name: &ast::QualifiedName) -> Expression {
|
||||
use SymbolSpec::*;
|
||||
|
||||
let symbol = match self.symbol_table.lookup_symbol(&qualified_name.id) {
|
||||
Some(s) => s,
|
||||
None =>
|
||||
return Expression::ReductionError(format!("No symbol found for name: `{}`", qualified_name)),
|
||||
};
|
||||
|
||||
let def_id = symbol.def_id();
|
||||
|
||||
match symbol.spec() {
|
||||
Builtin(b) => Expression::Callable(Callable::Builtin(b)),
|
||||
Func { .. } => Expression::Lookup(Lookup::Function(def_id)),
|
||||
GlobalBinding => Expression::Lookup(Lookup::GlobalVar(def_id)),
|
||||
LocalVariable => Expression::Lookup(Lookup::LocalVar(def_id)),
|
||||
FunctionParam(n) => Expression::Lookup(Lookup::Param(n)),
|
||||
DataConstructor { tag, type_id } =>
|
||||
Expression::Callable(Callable::DataConstructor { type_id, tag }),
|
||||
RecordConstructor { .. } => Expression::ReductionError(format!(
|
||||
"The symbol for value {:?} is unexpectdly a RecordConstructor",
|
||||
qualified_name
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::Pattern {
|
||||
fn reduce(&self, symbol_table: &SymbolTable) -> Result<Pattern, PatternError> {
|
||||
Ok(match self {
|
||||
ast::Pattern::Ignored => Pattern::Ignored,
|
||||
ast::Pattern::TuplePattern(subpatterns) => {
|
||||
let items: Result<Vec<Pattern>, PatternError> =
|
||||
subpatterns.iter().map(|pat| pat.reduce(symbol_table)).into_iter().collect();
|
||||
let items = items?;
|
||||
Pattern::Tuple { tag: None, subpatterns: items }
|
||||
}
|
||||
ast::Pattern::Literal(lit) => Pattern::Literal(match lit {
|
||||
ast::PatternLiteral::NumPattern { neg, num } => match (neg, num) {
|
||||
(false, ast::ExpressionKind::NatLiteral(n)) => Literal::Nat(*n),
|
||||
(false, ast::ExpressionKind::FloatLiteral(f)) => Literal::Float(*f),
|
||||
(true, ast::ExpressionKind::NatLiteral(n)) => Literal::Int(-(*n as i64)),
|
||||
(true, ast::ExpressionKind::FloatLiteral(f)) => Literal::Float(-f),
|
||||
(_, e) =>
|
||||
return Err(format!("Internal error, unexpected pattern literal: {:?}", e).into()),
|
||||
},
|
||||
ast::PatternLiteral::StringPattern(s) => Literal::StringLit(s.clone()),
|
||||
ast::PatternLiteral::BoolPattern(b) => Literal::Bool(*b),
|
||||
}),
|
||||
ast::Pattern::TupleStruct(name, subpatterns) => {
|
||||
let symbol = symbol_table.lookup_symbol(&name.id).unwrap();
|
||||
if let SymbolSpec::DataConstructor { tag, type_id: _ } = symbol.spec() {
|
||||
let items: Result<Vec<Pattern>, PatternError> =
|
||||
subpatterns.iter().map(|pat| pat.reduce(symbol_table)).into_iter().collect();
|
||||
let items = items?;
|
||||
Pattern::Tuple { tag: Some(tag), subpatterns: items }
|
||||
} else {
|
||||
return Err(
|
||||
"Internal error, trying to match something that's not a DataConstructor".into()
|
||||
);
|
||||
}
|
||||
}
|
||||
ast::Pattern::VarOrName(name) => {
|
||||
let symbol = symbol_table.lookup_symbol(&name.id).unwrap();
|
||||
match symbol.spec() {
|
||||
SymbolSpec::DataConstructor { tag, type_id: _ } =>
|
||||
Pattern::Tuple { tag: Some(tag), subpatterns: vec![] },
|
||||
SymbolSpec::LocalVariable => {
|
||||
let def_id = symbol.def_id();
|
||||
Pattern::Binding(def_id)
|
||||
}
|
||||
spec => return Err(format!("Unexpected VarOrName symbol: {:?}", spec).into()),
|
||||
}
|
||||
}
|
||||
ast::Pattern::Record(name, specified_members) => {
|
||||
let symbol = symbol_table.lookup_symbol(&name.id).unwrap();
|
||||
if let SymbolSpec::RecordConstructor { tag, type_id: _ } = symbol.spec() {
|
||||
//TODO do this computation from the type_id
|
||||
/*
|
||||
if specified_members.iter().any(|(member, _)| !members.contains_key(member)) {
|
||||
return Err(format!("Unknown key in record pattern").into());
|
||||
}
|
||||
*/
|
||||
|
||||
let subpatterns: Result<Vec<(Rc<String>, Pattern)>, PatternError> = specified_members
|
||||
.iter()
|
||||
.map(|(name, pat)| {
|
||||
pat.reduce(symbol_table).map(|reduced_pat| (name.clone(), reduced_pat))
|
||||
})
|
||||
.into_iter()
|
||||
.collect();
|
||||
let subpatterns = subpatterns?;
|
||||
Pattern::Record { tag, subpatterns }
|
||||
} else {
|
||||
return Err(format!("Unexpected Record pattern symbol: {:?}", symbol.spec()).into());
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the type context and a variant, compute what order the fields on it were stored.
|
||||
/// This needs to be public until type-checking is fully implemented because the type information
|
||||
/// is only available at runtime.
|
||||
pub fn compute_field_orderings(
|
||||
type_context: &TypeContext,
|
||||
type_id: &TypeId,
|
||||
tag: u32,
|
||||
) -> Option<Vec<String>> {
|
||||
// Eventually, the ReducedIR should decide what field ordering is optimal.
|
||||
// For now, just do it alphabetically.
|
||||
|
||||
let record_members = type_context.lookup_record_members(type_id, tag)?;
|
||||
let mut field_order: Vec<String> =
|
||||
record_members.iter().map(|(field, _type_id)| field).cloned().collect();
|
||||
field_order.sort_unstable();
|
||||
Some(field_order)
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use crate::{symbol_table::SymbolTable, type_inference::TypeContext};
|
||||
|
||||
fn build_ir(input: &str) -> ReducedIR {
|
||||
let ast = crate::util::quick_ast(input);
|
||||
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let mut type_context = TypeContext::new();
|
||||
|
||||
symbol_table.process_ast(&ast, &mut type_context).unwrap();
|
||||
|
||||
let reduced = reduce(&ast, &symbol_table, &type_context);
|
||||
reduced.debug(&symbol_table);
|
||||
reduced
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ir() {
|
||||
let src = r#"
|
||||
|
||||
let global_one = 10 + 20
|
||||
let global_two = "the string hello"
|
||||
|
||||
fn a_function(i, j, k) {
|
||||
fn nested(x) {
|
||||
x + 10
|
||||
}
|
||||
i + j * nested(k)
|
||||
}
|
||||
|
||||
fn another_function(e) {
|
||||
let local_var = 420
|
||||
e * local_var
|
||||
}
|
||||
|
||||
another_function()
|
||||
"#;
|
||||
|
||||
let reduced = build_ir(src);
|
||||
assert_eq!(reduced.functions.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_methods() {
|
||||
let src = r#"
|
||||
type Thing = Thing
|
||||
impl Thing {
|
||||
fn a_method() {
|
||||
20
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let a = Thing
|
||||
4 + a.a_method()
|
||||
"#;
|
||||
let reduced = build_ir(src);
|
||||
assert_eq!(reduced.functions.len(), 1);
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
use std::{collections::HashMap, convert::From, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
builtin::Builtin,
|
||||
symbol_table::{DefId, SymbolTable},
|
||||
type_inference::TypeId,
|
||||
};
|
||||
|
||||
//TODO most of these Clone impls only exist to support function application, because the
|
||||
//tree-walking evaluator moves the reduced IR members.
|
||||
|
||||
/// The reduced intermediate representation consists of a list of function definitions, and a block
|
||||
/// of entrypoint statements. In a repl or script context this can be an arbitrary list of
|
||||
/// statements, in an executable context will likely just be a pointer to the main() function.
|
||||
#[derive(Debug)]
|
||||
pub struct ReducedIR {
|
||||
pub functions: HashMap<DefId, FunctionDefinition>,
|
||||
pub entrypoint: Vec<Statement>,
|
||||
}
|
||||
|
||||
impl ReducedIR {
|
||||
#[allow(dead_code)]
|
||||
pub fn debug(&self, symbol_table: &SymbolTable) {
|
||||
println!("Reduced IR:");
|
||||
println!("Functions:");
|
||||
println!("-----------");
|
||||
for (id, callable) in self.functions.iter() {
|
||||
let name = &symbol_table.lookup_symbol_by_def(id).unwrap().local_name();
|
||||
println!("{}({}) -> {:?}", id, name, callable);
|
||||
}
|
||||
println!();
|
||||
println!("Entrypoint:");
|
||||
println!("-----------");
|
||||
for stmt in self.entrypoint.iter() {
|
||||
println!("{:?}", stmt);
|
||||
}
|
||||
println!("-----------");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Statement {
|
||||
Expression(Expression),
|
||||
Binding { id: DefId, constant: bool, expr: Expression },
|
||||
Return(Expression),
|
||||
Continue,
|
||||
Break,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expression {
|
||||
Literal(Literal),
|
||||
Tuple(Vec<Expression>),
|
||||
List(Vec<Expression>),
|
||||
Lookup(Lookup),
|
||||
Assign { lval: DefId, rval: Box<Expression> },
|
||||
Access { name: String, expr: Box<Expression> },
|
||||
Callable(Callable),
|
||||
Call { f: Box<Expression>, args: Vec<Expression> },
|
||||
CallMethod { f: Box<Expression>, args: Vec<Expression>, self_expr: Box<Expression> },
|
||||
Conditional { cond: Box<Expression>, then_clause: Vec<Statement>, else_clause: Vec<Statement> },
|
||||
CaseMatch { cond: Box<Expression>, alternatives: Vec<Alternative> },
|
||||
Loop { cond: Box<Expression>, statements: Vec<Statement> },
|
||||
Index { indexee: Box<Expression>, indexer: Box<Expression> },
|
||||
ReductionError(String),
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn unit() -> Self {
|
||||
Expression::Tuple(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionDefinition {
|
||||
pub body: Vec<Statement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Callable {
|
||||
Builtin(Builtin),
|
||||
UserDefined(DefId),
|
||||
Lambda { arity: u8, body: Vec<Statement> },
|
||||
DataConstructor { type_id: TypeId, tag: u32 },
|
||||
RecordConstructor { type_id: TypeId, tag: u32, field_order: Vec<String> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Lookup {
|
||||
LocalVar(DefId),
|
||||
GlobalVar(DefId),
|
||||
Function(DefId),
|
||||
Param(u8),
|
||||
SelfParam,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Literal {
|
||||
Nat(u64),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
StringLit(Rc<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Alternative {
|
||||
pub pattern: Pattern,
|
||||
pub item: Vec<Statement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Pattern {
|
||||
Tuple { subpatterns: Vec<Pattern>, tag: Option<u32> },
|
||||
Record { tag: u32, subpatterns: Vec<(Rc<String>, Pattern)> },
|
||||
Literal(Literal),
|
||||
Ignored,
|
||||
Binding(DefId),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct PatternError {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl From<&str> for PatternError {
|
||||
fn from(s: &str) -> Self {
|
||||
Self { msg: s.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for PatternError {
|
||||
fn from(msg: String) -> Self {
|
||||
Self { msg }
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
use schala_repl::{
|
||||
ComputationRequest, ComputationResponse, GlobalOutputStats, LangMetaRequest, LangMetaResponse,
|
||||
ProgrammingLanguageInterface,
|
||||
};
|
||||
use stopwatch::Stopwatch;
|
||||
|
||||
use crate::{error::SchalaError, parsing, reduced_ir, symbol_table, tree_walk_eval, type_inference};
|
||||
|
||||
/// All the state necessary to parse and execute a Schala program are stored in this struct.
|
||||
pub struct Schala<'a> {
|
||||
/// Holds a reference to the original source code, parsed into line and character
|
||||
source_reference: SourceReference,
|
||||
|
||||
//state: eval::State<'static>,
|
||||
/// Keeps track of symbols and scopes
|
||||
symbol_table: symbol_table::SymbolTable,
|
||||
/// Contains information for type-checking
|
||||
type_context: type_inference::TypeContext,
|
||||
/// Schala Parser
|
||||
active_parser: parsing::Parser,
|
||||
|
||||
/// Execution state for AST-walking interpreter
|
||||
eval_state: tree_walk_eval::State<'a>,
|
||||
|
||||
timings: Vec<(&'static str, std::time::Duration)>,
|
||||
}
|
||||
|
||||
/*
|
||||
impl Schala {
|
||||
//TODO implement documentation for language items
|
||||
/*
|
||||
fn handle_docs(&self, source: String) -> LangMetaResponse {
|
||||
LangMetaResponse::Docs {
|
||||
doc_string: format!("Schala item `{}` : <<Schala-lang documentation not yet implemented>>", source)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
*/
|
||||
|
||||
impl<'a> Schala<'a> {
|
||||
/// Creates a new Schala environment *without* any prelude.
|
||||
fn new_blank_env() -> Schala<'a> {
|
||||
Schala {
|
||||
source_reference: SourceReference::new(),
|
||||
symbol_table: symbol_table::SymbolTable::new(),
|
||||
type_context: type_inference::TypeContext::new(),
|
||||
active_parser: parsing::Parser::new(),
|
||||
eval_state: tree_walk_eval::State::new(),
|
||||
timings: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Schala environment with the standard prelude, which is defined as ordinary
|
||||
/// Schala code in the file `prelude.schala`
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Schala<'a> {
|
||||
let prelude = include_str!("../source-files/prelude.schala");
|
||||
let mut env = Schala::new_blank_env();
|
||||
|
||||
let response = env.run_pipeline(prelude, SchalaConfig::default());
|
||||
if let Err(err) = response {
|
||||
panic!("Error in prelude, panicking: {}", err.display());
|
||||
}
|
||||
env
|
||||
}
|
||||
|
||||
/// This is where the actual action of interpreting/compilation happens.
|
||||
/// Note: this should eventually use a query-based system for parallelization, cf.
|
||||
/// https://rustc-dev-guide.rust-lang.org/overview.html
|
||||
fn run_pipeline(&mut self, source: &str, config: SchalaConfig) -> Result<String, SchalaError> {
|
||||
self.timings = vec![];
|
||||
let sw = Stopwatch::start_new();
|
||||
|
||||
self.source_reference.load_new_source(source);
|
||||
let ast = self
|
||||
.active_parser
|
||||
.parse(source)
|
||||
.map_err(|err| SchalaError::from_parse_error(err, &self.source_reference))?;
|
||||
self.timings.push(("parsing", sw.elapsed()));
|
||||
|
||||
let sw = Stopwatch::start_new();
|
||||
//Perform all symbol table work
|
||||
self.symbol_table
|
||||
.process_ast(&ast, &mut self.type_context)
|
||||
.map_err(SchalaError::from_symbol_table)?;
|
||||
|
||||
self.timings.push(("symbol_table", sw.elapsed()));
|
||||
|
||||
// Typechecking
|
||||
let _overall_type = self.type_context.typecheck(&ast).map_err(SchalaError::from_type_error);
|
||||
|
||||
let sw = Stopwatch::start_new();
|
||||
let reduced_ir = reduced_ir::reduce(&ast, &self.symbol_table, &self.type_context);
|
||||
self.timings.push(("reduced_ir", sw.elapsed()));
|
||||
|
||||
let sw = Stopwatch::start_new();
|
||||
let evaluation_outputs = self.eval_state.evaluate(reduced_ir, &self.type_context, config.repl);
|
||||
self.timings.push(("tree-walking-evaluation", sw.elapsed()));
|
||||
let text_output: Result<Vec<String>, String> = evaluation_outputs.into_iter().collect();
|
||||
|
||||
let text_output: Result<Vec<String>, SchalaError> =
|
||||
text_output.map_err(|err| SchalaError::from_string(err, Stage::Evaluation));
|
||||
|
||||
let eval_output: String =
|
||||
text_output.map(|v| Iterator::intersperse(v.into_iter(), "\n".to_owned()).collect())?;
|
||||
|
||||
Ok(eval_output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents lines of source code
|
||||
pub(crate) struct SourceReference {
|
||||
last_source: Option<String>,
|
||||
/// Offsets in *bytes* (not chars) representing a newline character
|
||||
newline_offsets: Vec<usize>,
|
||||
}
|
||||
|
||||
impl SourceReference {
|
||||
pub(crate) fn new() -> SourceReference {
|
||||
SourceReference { last_source: None, newline_offsets: vec![] }
|
||||
}
|
||||
|
||||
pub(crate) fn load_new_source(&mut self, source: &str) {
|
||||
self.newline_offsets = vec![];
|
||||
for (offset, ch) in source.as_bytes().iter().enumerate() {
|
||||
if *ch == b'\n' {
|
||||
self.newline_offsets.push(offset);
|
||||
}
|
||||
}
|
||||
self.last_source = Some(source.to_string());
|
||||
}
|
||||
|
||||
// (line_start, line_num, the string itself)
|
||||
pub fn get_line(&self, line: usize) -> (usize, usize, String) {
|
||||
if self.newline_offsets.is_empty() {
|
||||
return (0, 0, self.last_source.as_ref().cloned().unwrap());
|
||||
}
|
||||
|
||||
//TODO make sure this is utf8-safe
|
||||
let start_idx = match self.newline_offsets.binary_search(&line) {
|
||||
Ok(index) | Err(index) => index,
|
||||
};
|
||||
|
||||
let last_source = self.last_source.as_ref().unwrap();
|
||||
|
||||
let start = self.newline_offsets[start_idx];
|
||||
let end = self.newline_offsets.get(start_idx + 1).cloned().unwrap_or_else(|| last_source.len());
|
||||
|
||||
let slice = &last_source.as_bytes()[start..end];
|
||||
(start, start_idx, std::str::from_utf8(slice).unwrap().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum Stage {
|
||||
Parsing,
|
||||
Symbols,
|
||||
ScopeResolution,
|
||||
Typechecking,
|
||||
AstReduction,
|
||||
Evaluation,
|
||||
}
|
||||
|
||||
fn stage_names() -> Vec<&'static str> {
|
||||
vec!["parsing", "symbol-table", "typechecking", "ast-reduction", "ast-walking-evaluation"]
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct SchalaConfig {
|
||||
pub repl: bool,
|
||||
}
|
||||
|
||||
impl<'a> ProgrammingLanguageInterface for Schala<'a> {
|
||||
//TODO flesh out Config
|
||||
type Config = SchalaConfig;
|
||||
fn language_name() -> String {
|
||||
"Schala".to_owned()
|
||||
}
|
||||
|
||||
fn source_file_suffix() -> String {
|
||||
"schala".to_owned()
|
||||
}
|
||||
|
||||
fn run_computation(&mut self, request: ComputationRequest<Self::Config>) -> ComputationResponse {
|
||||
let ComputationRequest { source, debug_requests: _, config: _ } = request;
|
||||
let sw = Stopwatch::start_new();
|
||||
|
||||
let main_output =
|
||||
self.run_pipeline(source, request.config).map_err(|schala_err| schala_err.display());
|
||||
let total_duration = sw.elapsed();
|
||||
|
||||
let stage_durations: Vec<_> = std::mem::take(&mut self.timings)
|
||||
.into_iter()
|
||||
.map(|(label, duration)| (label.to_string(), duration))
|
||||
.collect();
|
||||
let global_output_stats = GlobalOutputStats { total_duration, stage_durations };
|
||||
|
||||
ComputationResponse { main_output, global_output_stats, debug_responses: vec![] }
|
||||
}
|
||||
|
||||
fn request_meta(&mut self, request: LangMetaRequest) -> LangMetaResponse {
|
||||
match request {
|
||||
LangMetaRequest::StageNames =>
|
||||
LangMetaResponse::StageNames(stage_names().iter().map(|s| s.to_string()).collect()),
|
||||
_ => LangMetaResponse::Custom { kind: "not-implemented".to_string(), value: "".to_string() },
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
use std::{fmt, rc::Rc};
|
||||
|
||||
/// Fully-qualified symbol name
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
pub struct Fqsn {
|
||||
//TODO Fqsn's need to be cheaply cloneable
|
||||
pub scopes: Vec<ScopeSegment>,
|
||||
}
|
||||
|
||||
impl Fqsn {
|
||||
pub fn from_scope_stack(scopes: &[ScopeSegment], new_name: Rc<String>) -> Self {
|
||||
let mut v = Vec::new();
|
||||
for s in scopes {
|
||||
v.push(s.clone());
|
||||
}
|
||||
v.push(ScopeSegment::Name(new_name));
|
||||
Fqsn { scopes: v }
|
||||
}
|
||||
|
||||
pub fn extend(&self, new_item: &str) -> Self {
|
||||
let mut new = self.clone();
|
||||
new.scopes.push(ScopeSegment::Name(Rc::new(new_item.to_string())));
|
||||
new
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_strs(strs: &[&str]) -> Fqsn {
|
||||
let mut scopes = vec![];
|
||||
for s in strs {
|
||||
scopes.push(ScopeSegment::Name(Rc::new(s.to_string())));
|
||||
}
|
||||
Fqsn { scopes }
|
||||
}
|
||||
|
||||
pub fn last_elem(&self) -> Rc<String> {
|
||||
let ScopeSegment::Name(name) = self.scopes.last().unwrap();
|
||||
name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Fqsn {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let delim = "::";
|
||||
let Fqsn { scopes } = self;
|
||||
write!(f, "FQSN<{}", scopes[0])?;
|
||||
for item in scopes[1..].iter() {
|
||||
write!(f, "{}{}", delim, item)?;
|
||||
}
|
||||
write!(f, ">")
|
||||
}
|
||||
}
|
||||
|
||||
//TODO eventually this should use ItemId's to avoid String-cloning
|
||||
/// One segment within a scope.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
pub enum ScopeSegment {
|
||||
Name(Rc<String>),
|
||||
}
|
||||
|
||||
impl fmt::Display for ScopeSegment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let ScopeSegment::Name(name) = self;
|
||||
write!(f, "{}", name)
|
||||
}
|
||||
}
|
@ -1,244 +0,0 @@
|
||||
#![allow(clippy::enum_variant_names)]
|
||||
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
fmt,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ast,
|
||||
ast::ItemId,
|
||||
builtin::Builtin,
|
||||
parsing::Location,
|
||||
type_inference::{TypeContext, TypeId},
|
||||
};
|
||||
|
||||
mod populator;
|
||||
use populator::SymbolTablePopulator;
|
||||
mod fqsn;
|
||||
pub use fqsn::{Fqsn, ScopeSegment};
|
||||
mod resolver;
|
||||
mod symbol_trie;
|
||||
use symbol_trie::SymbolTrie;
|
||||
mod test;
|
||||
use crate::identifier::{define_id_kind, Id, IdStore};
|
||||
|
||||
define_id_kind!(DefItem);
|
||||
pub type DefId = Id<DefItem>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SymbolError {
|
||||
DuplicateName { prev_name: Fqsn, location: Location },
|
||||
DuplicateVariant { type_fqsn: Fqsn, name: String },
|
||||
DuplicateRecord { type_fqsn: Fqsn, location: Location, record: String, member: String },
|
||||
UnknownAnnotation { name: String },
|
||||
BadAnnotation { name: String, msg: String },
|
||||
BadImplBlockEntry,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
struct NameSpec<K> {
|
||||
location: Location,
|
||||
kind: K,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NameKind {
|
||||
Module,
|
||||
Function,
|
||||
Binding,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TypeKind {
|
||||
Function,
|
||||
Constructor,
|
||||
}
|
||||
|
||||
/// Keeps track of what names were used in a given namespace.
|
||||
struct NameTable<K> {
|
||||
table: HashMap<Fqsn, NameSpec<K>>,
|
||||
}
|
||||
|
||||
impl<K> NameTable<K> {
|
||||
fn new() -> Self {
|
||||
Self { table: HashMap::new() }
|
||||
}
|
||||
|
||||
fn register(&mut self, name: Fqsn, spec: NameSpec<K>) -> Result<(), SymbolError> {
|
||||
match self.table.entry(name.clone()) {
|
||||
Entry::Occupied(o) =>
|
||||
Err(SymbolError::DuplicateName { prev_name: name, location: o.get().location }),
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(spec);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//cf. p. 150 or so of Language Implementation Patterns
|
||||
pub struct SymbolTable {
|
||||
def_id_store: IdStore<DefItem>,
|
||||
|
||||
/// Used for import resolution.
|
||||
symbol_trie: SymbolTrie,
|
||||
|
||||
/// These tables are responsible for preventing duplicate names.
|
||||
fq_names: NameTable<NameKind>, //Note that presence of two tables implies that a type and other binding with the same name can co-exist
|
||||
types: NameTable<TypeKind>,
|
||||
|
||||
id_to_def: HashMap<ItemId, DefId>,
|
||||
def_to_symbol: HashMap<DefId, Rc<Symbol>>,
|
||||
}
|
||||
|
||||
impl SymbolTable {
|
||||
/// Create a new, empty SymbolTable
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
def_id_store: IdStore::new(),
|
||||
symbol_trie: SymbolTrie::new(),
|
||||
fq_names: NameTable::new(),
|
||||
types: NameTable::new(),
|
||||
id_to_def: HashMap::new(),
|
||||
def_to_symbol: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The main entry point into the symbol table. This will traverse the AST in several
|
||||
/// different ways and populate subtables with information that will be used further in the
|
||||
/// compilation process.
|
||||
pub fn process_ast(
|
||||
&mut self,
|
||||
ast: &ast::AST,
|
||||
type_context: &mut TypeContext,
|
||||
) -> Result<(), Vec<SymbolError>> {
|
||||
let mut populator = SymbolTablePopulator { type_context, table: self };
|
||||
|
||||
let errs = populator.populate_definition_tables(ast);
|
||||
if !errs.is_empty() {
|
||||
return Err(errs);
|
||||
}
|
||||
|
||||
// Walks the AST, matching the ID of an identifier used in some expression to
|
||||
// the corresponding Symbol.
|
||||
let mut resolver = resolver::ScopeResolver::new(self);
|
||||
resolver.resolve(ast);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lookup_symbol(&self, id: &ItemId) -> Option<&Symbol> {
|
||||
let def = self.id_to_def.get(id)?;
|
||||
self.def_to_symbol.get(def).map(|s| s.as_ref())
|
||||
}
|
||||
|
||||
pub fn lookup_symbol_by_def(&self, def: &DefId) -> Option<&Symbol> {
|
||||
self.def_to_symbol.get(def).map(|s| s.as_ref())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn debug(&self) {
|
||||
println!("Symbol table:");
|
||||
println!("----------------");
|
||||
for (id, def) in self.id_to_def.iter() {
|
||||
if let Some(symbol) = self.def_to_symbol.get(def) {
|
||||
println!("{} => {}: {}", id, def, symbol);
|
||||
} else {
|
||||
println!("{} => {} <NO SYMBOL FOUND>", id, def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a new mapping of a fully-qualified symbol name (e.g. `Option::Some`)
|
||||
/// to a Symbol, a descriptor of what that name refers to.
|
||||
fn add_symbol(&mut self, id: &ItemId, fqsn: Fqsn, spec: SymbolSpec) {
|
||||
let def_id = self.def_id_store.fresh();
|
||||
let local_name = fqsn.last_elem();
|
||||
let symbol = Rc::new(Symbol { fully_qualified_name: fqsn.clone(), local_name, spec, def_id });
|
||||
self.symbol_trie.insert(&fqsn, def_id);
|
||||
self.id_to_def.insert(*id, def_id);
|
||||
self.def_to_symbol.insert(def_id, symbol);
|
||||
}
|
||||
|
||||
fn populate_single_builtin(&mut self, fqsn: Fqsn, builtin: Builtin) {
|
||||
let def_id = self.def_id_store.fresh();
|
||||
let spec = SymbolSpec::Builtin(builtin);
|
||||
let local_name = fqsn.last_elem();
|
||||
let symbol = Rc::new(Symbol { fully_qualified_name: fqsn.clone(), local_name, spec, def_id });
|
||||
|
||||
self.symbol_trie.insert(&fqsn, def_id);
|
||||
self.def_to_symbol.insert(def_id, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Symbol {
|
||||
fully_qualified_name: Fqsn,
|
||||
local_name: Rc<String>,
|
||||
spec: SymbolSpec,
|
||||
def_id: DefId,
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
pub fn local_name(&self) -> &str {
|
||||
self.local_name.as_ref()
|
||||
}
|
||||
|
||||
pub fn def_id(&self) -> DefId {
|
||||
self.def_id
|
||||
}
|
||||
|
||||
pub fn spec(&self) -> SymbolSpec {
|
||||
self.spec.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Symbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "<Local name: {}, {}, Spec: {}>", self.local_name(), self.fully_qualified_name, self.spec)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO - I think I eventually want to draw a distinction between true global items
|
||||
//i.e. global vars, and items whose definitions are scoped. Right now there's a sense
|
||||
//in which Func, DataConstructor, RecordConstructor, and GlobalBinding are "globals",
|
||||
//whereas LocalVarible and FunctionParam have local scope. But right now, they all
|
||||
//get put into a common table, and all get DefId's from a common source.
|
||||
//
|
||||
//It would be good if individual functions could in parallel look up their own
|
||||
//local vars without interfering with other lookups. Also some type definitions
|
||||
//should be scoped in a similar way.
|
||||
//
|
||||
//Also it makes sense that non-globals should not use DefId's, particularly not
|
||||
//function parameters (even though they are currently assigned).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SymbolSpec {
|
||||
Builtin(Builtin),
|
||||
Func { method: Option<crate::ast::TypeSingletonName> },
|
||||
DataConstructor { tag: u32, type_id: TypeId },
|
||||
RecordConstructor { tag: u32, type_id: TypeId },
|
||||
GlobalBinding, //Only for global variables, not for function-local ones or ones within a `let` scope context
|
||||
LocalVariable,
|
||||
FunctionParam(u8),
|
||||
}
|
||||
|
||||
impl fmt::Display for SymbolSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::SymbolSpec::*;
|
||||
match self {
|
||||
Builtin(b) => write!(f, "Builtin: {:?}", b),
|
||||
Func { .. } => write!(f, "Func"),
|
||||
DataConstructor { tag, type_id } => write!(f, "DataConstructor(tag: {}, type: {})", tag, type_id),
|
||||
RecordConstructor { type_id, tag, .. } =>
|
||||
write!(f, "RecordConstructor(tag: {})(<members> -> {})", tag, type_id),
|
||||
GlobalBinding => write!(f, "GlobalBinding"),
|
||||
LocalVariable => write!(f, "Local variable"),
|
||||
FunctionParam(n) => write!(f, "Function param: {}", n),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,352 +0,0 @@
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap, HashSet},
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use super::{Fqsn, NameKind, NameSpec, ScopeSegment, SymbolError, SymbolSpec, SymbolTable, TypeKind};
|
||||
use crate::{
|
||||
ast::{
|
||||
Declaration, Expression, ExpressionKind, ItemId, Statement, StatementKind, TypeBody,
|
||||
TypeSingletonName, Variant, VariantKind, AST,
|
||||
},
|
||||
builtin::Builtin,
|
||||
parsing::Location,
|
||||
type_inference::{self, PendingType, TypeBuilder, TypeContext, VariantBuilder},
|
||||
};
|
||||
|
||||
pub(super) struct SymbolTablePopulator<'a> {
|
||||
pub(super) type_context: &'a mut TypeContext,
|
||||
pub(super) table: &'a mut SymbolTable,
|
||||
}
|
||||
|
||||
impl<'a> SymbolTablePopulator<'a> {
|
||||
/* note: this adds names for *forward reference* but doesn't actually create any types. solve that problem
|
||||
* later */
|
||||
|
||||
fn add_symbol(&mut self, id: &ItemId, fqsn: Fqsn, spec: SymbolSpec) {
|
||||
self.table.add_symbol(id, fqsn, spec)
|
||||
}
|
||||
|
||||
/// This function traverses the AST and adds symbol table entries for
|
||||
/// constants, functions, types, and modules defined within. This simultaneously
|
||||
/// checks for dupicate definitions (and returns errors if discovered), and sets
|
||||
/// up name tables that will be used by further parts of the compiler
|
||||
pub fn populate_definition_tables(&mut self, ast: &AST) -> Vec<SymbolError> {
|
||||
let mut scope_stack = vec![];
|
||||
self.add_from_scope(ast.statements.as_ref(), &mut scope_stack, false)
|
||||
}
|
||||
|
||||
fn add_from_scope(
|
||||
&mut self,
|
||||
statements: &[Statement<StatementKind>],
|
||||
scope_stack: &mut Vec<ScopeSegment>,
|
||||
function_scope: bool,
|
||||
) -> Vec<SymbolError> {
|
||||
let mut errors = vec![];
|
||||
|
||||
for statement in statements {
|
||||
let Statement { id, kind, location } = statement;
|
||||
let location = *location;
|
||||
if let Err(err) = self.add_single_statement(id, kind, location, scope_stack, function_scope) {
|
||||
errors.push(err);
|
||||
} else {
|
||||
let decl = match kind {
|
||||
StatementKind::Declaration(decl) => decl,
|
||||
_ => continue,
|
||||
};
|
||||
// If there's an error with a name, don't recurse into subscopes of that name
|
||||
let recursive_errs = match decl {
|
||||
Declaration::FuncDecl(signature, body) => {
|
||||
let new_scope = ScopeSegment::Name(signature.name.clone());
|
||||
scope_stack.push(new_scope);
|
||||
let output = self.add_from_scope(body.as_ref(), scope_stack, true);
|
||||
scope_stack.pop();
|
||||
output
|
||||
}
|
||||
Declaration::Module { name, items } => {
|
||||
let new_scope = ScopeSegment::Name(name.clone());
|
||||
scope_stack.push(new_scope);
|
||||
let output = self.add_from_scope(items.as_ref(), scope_stack, false);
|
||||
scope_stack.pop();
|
||||
output
|
||||
}
|
||||
Declaration::TypeDecl { name, body, mutable } => {
|
||||
let type_fqsn = Fqsn::from_scope_stack(scope_stack, name.name.clone());
|
||||
self.add_type_members(name, body, mutable, location, type_fqsn)
|
||||
}
|
||||
|
||||
Declaration::Impl { type_name, interface_name: _, block } => {
|
||||
let mut errors = vec![];
|
||||
let new_scope = ScopeSegment::Name(Rc::new(format!("<impl-block>{}", type_name)));
|
||||
scope_stack.push(new_scope);
|
||||
|
||||
for decl_stmt in block.iter() {
|
||||
let Statement { id, kind, location } = decl_stmt;
|
||||
let location = *location;
|
||||
match kind {
|
||||
decl @ Declaration::FuncDecl(signature, body) => {
|
||||
let output =
|
||||
self.add_single_declaration(id, decl, location, scope_stack, true);
|
||||
if let Err(e) = output {
|
||||
errors.push(e);
|
||||
};
|
||||
let new_scope = ScopeSegment::Name(signature.name.clone());
|
||||
scope_stack.push(new_scope);
|
||||
let output = self.add_from_scope(body.as_ref(), scope_stack, true);
|
||||
scope_stack.pop();
|
||||
errors.extend(output.into_iter());
|
||||
}
|
||||
_other => errors.push(SymbolError::BadImplBlockEntry),
|
||||
};
|
||||
}
|
||||
scope_stack.pop();
|
||||
errors
|
||||
}
|
||||
_ => vec![],
|
||||
};
|
||||
errors.extend(recursive_errs.into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
|
||||
fn add_single_statement(
|
||||
&mut self,
|
||||
id: &ItemId,
|
||||
kind: &StatementKind,
|
||||
location: Location,
|
||||
scope_stack: &[ScopeSegment],
|
||||
function_scope: bool,
|
||||
) -> Result<(), SymbolError> {
|
||||
match kind {
|
||||
StatementKind::Declaration(decl) =>
|
||||
self.add_single_declaration(id, decl, location, scope_stack, function_scope),
|
||||
_ => return Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_single_declaration(
|
||||
&mut self,
|
||||
id: &ItemId,
|
||||
decl: &Declaration,
|
||||
location: Location,
|
||||
scope_stack: &[ScopeSegment],
|
||||
function_scope: bool,
|
||||
) -> Result<(), SymbolError> {
|
||||
match decl {
|
||||
Declaration::FuncSig(signature) => {
|
||||
let fq_function = Fqsn::from_scope_stack(scope_stack, signature.name.clone());
|
||||
self.table
|
||||
.fq_names
|
||||
.register(fq_function.clone(), NameSpec { location, kind: NameKind::Function })?;
|
||||
self.table
|
||||
.types
|
||||
.register(fq_function.clone(), NameSpec { location, kind: TypeKind::Function })?;
|
||||
|
||||
self.add_symbol(id, fq_function, SymbolSpec::Func { method: None });
|
||||
}
|
||||
Declaration::FuncDecl(signature, ..) => {
|
||||
let fn_name = &signature.name;
|
||||
let fq_function = Fqsn::from_scope_stack(scope_stack, fn_name.clone());
|
||||
self.table
|
||||
.fq_names
|
||||
.register(fq_function.clone(), NameSpec { location, kind: NameKind::Function })?;
|
||||
self.table
|
||||
.types
|
||||
.register(fq_function.clone(), NameSpec { location, kind: TypeKind::Function })?;
|
||||
|
||||
self.add_symbol(id, fq_function, SymbolSpec::Func { method: None });
|
||||
}
|
||||
Declaration::TypeDecl { name, .. } => {
|
||||
let fq_type = Fqsn::from_scope_stack(scope_stack, name.name.clone());
|
||||
self.table.types.register(fq_type, NameSpec { location, kind: TypeKind::Constructor })?;
|
||||
}
|
||||
//TODO handle type aliases
|
||||
Declaration::TypeAlias { .. } => (),
|
||||
Declaration::Binding { name, .. } => {
|
||||
let fq_binding = Fqsn::from_scope_stack(scope_stack, name.clone());
|
||||
self.table
|
||||
.fq_names
|
||||
.register(fq_binding.clone(), NameSpec { location, kind: NameKind::Binding })?;
|
||||
if !function_scope {
|
||||
self.add_symbol(id, fq_binding, SymbolSpec::GlobalBinding);
|
||||
}
|
||||
}
|
||||
//TODO implement interfaces
|
||||
Declaration::Interface { .. } => (),
|
||||
Declaration::Impl { .. } => (),
|
||||
Declaration::Module { name, .. } => {
|
||||
let fq_module = Fqsn::from_scope_stack(scope_stack, name.clone());
|
||||
self.table.fq_names.register(fq_module, NameSpec { location, kind: NameKind::Module })?;
|
||||
}
|
||||
Declaration::Annotation { name, arguments, inner } => {
|
||||
let inner = inner.as_ref();
|
||||
self.add_single_statement(
|
||||
&inner.id,
|
||||
&inner.kind,
|
||||
inner.location,
|
||||
scope_stack,
|
||||
function_scope,
|
||||
)?;
|
||||
self.process_annotation(name.as_ref(), arguments.as_slice(), scope_stack, inner)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_annotation(
|
||||
&mut self,
|
||||
name: &str,
|
||||
arguments: &[Expression],
|
||||
scope_stack: &[ScopeSegment],
|
||||
inner: &Statement<StatementKind>,
|
||||
) -> Result<(), SymbolError> {
|
||||
if name == "register_builtin" {
|
||||
if let Statement {
|
||||
id: _,
|
||||
location: _,
|
||||
kind: StatementKind::Declaration(Declaration::FuncDecl(sig, _)),
|
||||
} = inner
|
||||
{
|
||||
let fqsn = Fqsn::from_scope_stack(scope_stack, sig.name.clone());
|
||||
let builtin_name = match arguments {
|
||||
[Expression { kind: ExpressionKind::Value(qname), .. }]
|
||||
if qname.components.len() == 1 =>
|
||||
qname.components[0].clone(),
|
||||
_ =>
|
||||
return Err(SymbolError::BadAnnotation {
|
||||
name: name.to_string(),
|
||||
msg: "Bad argument for register_builtin".to_string(),
|
||||
}),
|
||||
};
|
||||
|
||||
let builtin =
|
||||
Builtin::from_str(builtin_name.as_str()).map_err(|_| SymbolError::BadAnnotation {
|
||||
name: name.to_string(),
|
||||
msg: format!("Invalid builtin: {}", builtin_name),
|
||||
})?;
|
||||
|
||||
self.table.populate_single_builtin(fqsn, builtin);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SymbolError::BadAnnotation {
|
||||
name: name.to_string(),
|
||||
msg: "register_builtin not annotating a function".to_string(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Err(SymbolError::UnknownAnnotation { name: name.to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
fn add_type_members(
|
||||
&mut self,
|
||||
type_name: &TypeSingletonName,
|
||||
type_body: &TypeBody,
|
||||
_mutable: &bool,
|
||||
location: Location,
|
||||
type_fqsn: Fqsn,
|
||||
) -> Vec<SymbolError> {
|
||||
let (variants, immediate_variant) = match type_body {
|
||||
TypeBody::Variants(variants) => (variants.clone(), false),
|
||||
TypeBody::ImmediateRecord { id, fields } => (
|
||||
vec![Variant {
|
||||
id: *id,
|
||||
name: type_name.name.clone(),
|
||||
kind: VariantKind::Record(fields.clone()),
|
||||
}],
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
// Check for duplicates before registering any types with the TypeContext
|
||||
let mut seen_variants = HashSet::new();
|
||||
let mut errors = vec![];
|
||||
|
||||
for variant in variants.iter() {
|
||||
if seen_variants.contains(&variant.name) {
|
||||
errors.push(SymbolError::DuplicateVariant {
|
||||
type_fqsn: type_fqsn.clone(),
|
||||
name: variant.name.as_ref().to_string(),
|
||||
})
|
||||
}
|
||||
seen_variants.insert(variant.name.clone());
|
||||
|
||||
if let VariantKind::Record(ref members) = variant.kind {
|
||||
let mut seen_members = HashMap::new();
|
||||
for (member_name, _) in members.iter() {
|
||||
match seen_members.entry(member_name.as_ref()) {
|
||||
Entry::Occupied(o) => {
|
||||
let location = *o.get();
|
||||
errors.push(SymbolError::DuplicateRecord {
|
||||
type_fqsn: type_fqsn.clone(),
|
||||
location,
|
||||
record: variant.name.as_ref().to_string(),
|
||||
member: member_name.as_ref().to_string(),
|
||||
});
|
||||
}
|
||||
//TODO eventually this should track meaningful locations
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
let mut type_builder = TypeBuilder::new(type_name.name.as_ref());
|
||||
|
||||
let mut variant_name_map = HashMap::new();
|
||||
for variant in variants.iter() {
|
||||
let Variant { name, kind, id } = variant;
|
||||
|
||||
variant_name_map.insert(name.clone(), id);
|
||||
|
||||
let mut variant_builder = VariantBuilder::new(name.as_ref());
|
||||
match kind {
|
||||
VariantKind::UnitStruct => (),
|
||||
VariantKind::TupleStruct(items) =>
|
||||
for type_identifier in items {
|
||||
let pending: PendingType = type_identifier.into();
|
||||
variant_builder.add_member(pending);
|
||||
},
|
||||
VariantKind::Record(members) =>
|
||||
for (field_name, type_identifier) in members.iter() {
|
||||
let pending: PendingType = type_identifier.into();
|
||||
variant_builder.add_record_member(field_name.as_ref(), pending);
|
||||
},
|
||||
}
|
||||
type_builder.add_variant(variant_builder);
|
||||
}
|
||||
|
||||
let type_id = self.type_context.register_type(type_builder);
|
||||
let type_definition = self.type_context.lookup_type(&type_id).unwrap();
|
||||
|
||||
// This index is guaranteed to be the correct tag
|
||||
for (index, variant) in type_definition.variants.iter().enumerate() {
|
||||
let id = variant_name_map.get(&variant.name).unwrap();
|
||||
let tag = index as u32;
|
||||
let spec = match &variant.members {
|
||||
type_inference::VariantMembers::Unit => SymbolSpec::DataConstructor { tag, type_id },
|
||||
type_inference::VariantMembers::Tuple(..) => SymbolSpec::DataConstructor { tag, type_id },
|
||||
type_inference::VariantMembers::Record(..) => SymbolSpec::RecordConstructor { tag, type_id },
|
||||
};
|
||||
self.table.add_symbol(id, type_fqsn.extend(&variant.name), spec);
|
||||
}
|
||||
|
||||
if immediate_variant {
|
||||
let variant = &type_definition.variants[0];
|
||||
let id = variant_name_map.get(&variant.name).unwrap();
|
||||
let spec = SymbolSpec::RecordConstructor { tag: 0, type_id };
|
||||
self.table.add_symbol(id, type_fqsn, spec);
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
@ -1,253 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
ast::*,
|
||||
symbol_table::{Fqsn, ScopeSegment, SymbolSpec, SymbolTable},
|
||||
util::ScopeStack,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NameType {
|
||||
//TODO eventually this needs to support closures
|
||||
Param(u8),
|
||||
LocalVariable(ItemId),
|
||||
LocalFunction(ItemId),
|
||||
Import(Fqsn),
|
||||
}
|
||||
|
||||
type LexScope<'a> = ScopeStack<'a, Rc<String>, NameType, ScopeType>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ScopeType {
|
||||
Function { name: Rc<String> },
|
||||
Lambda,
|
||||
PatternMatch,
|
||||
ImplBlock,
|
||||
//TODO add some notion of a let-like scope?
|
||||
}
|
||||
|
||||
pub struct ScopeResolver<'a> {
|
||||
symbol_table: &'a mut super::SymbolTable,
|
||||
lexical_scopes: LexScope<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ScopeResolver<'a> {
|
||||
pub fn new(symbol_table: &'a mut SymbolTable) -> Self {
|
||||
let lexical_scopes = ScopeStack::new(None);
|
||||
Self { symbol_table, lexical_scopes }
|
||||
}
|
||||
|
||||
pub fn resolve(&mut self, ast: &AST) {
|
||||
walk_ast(self, ast);
|
||||
}
|
||||
|
||||
/// This method correctly modifies the id_to_def table (ItemId) to have the appropriate
|
||||
/// mappings.
|
||||
fn lookup_name_in_scope(&mut self, name: &QualifiedName) {
|
||||
//TODO this method badly needs attention
|
||||
let QualifiedName { id, components } = name;
|
||||
|
||||
let local_name = components.first().unwrap().clone();
|
||||
let name_type = self.lexical_scopes.lookup(&local_name);
|
||||
let fqsn = Fqsn { scopes: components.iter().map(|name| ScopeSegment::Name(name.clone())).collect() };
|
||||
let def_id = self.symbol_table.symbol_trie.lookup(&fqsn);
|
||||
|
||||
//TODO handle a "partial" qualified name, and also handle it down in the pattern-matching
|
||||
//section
|
||||
if components.len() == 1 {
|
||||
match name_type {
|
||||
Some(NameType::Import(fqsn)) => {
|
||||
let def_id = self.symbol_table.symbol_trie.lookup(fqsn);
|
||||
|
||||
if let Some(def_id) = def_id {
|
||||
self.symbol_table.id_to_def.insert(*id, def_id);
|
||||
}
|
||||
}
|
||||
Some(NameType::Param(n)) => {
|
||||
let spec = SymbolSpec::FunctionParam(*n);
|
||||
//TODO need to come up with a better solution for local variable FQSNs
|
||||
let lscope = ScopeSegment::Name(Rc::new("<local-param>".to_string()));
|
||||
let fqsn = Fqsn { scopes: vec![lscope, ScopeSegment::Name(local_name.clone())] };
|
||||
self.symbol_table.add_symbol(id, fqsn, spec);
|
||||
}
|
||||
Some(NameType::LocalFunction(item_id)) => {
|
||||
let def_id = self.symbol_table.id_to_def.get(item_id);
|
||||
if let Some(def_id) = def_id {
|
||||
let def_id = *def_id;
|
||||
self.symbol_table.id_to_def.insert(*id, def_id);
|
||||
}
|
||||
}
|
||||
Some(NameType::LocalVariable(item_id)) => {
|
||||
let def_id = self.symbol_table.id_to_def.get(item_id);
|
||||
if let Some(def_id) = def_id {
|
||||
let def_id = *def_id;
|
||||
self.symbol_table.id_to_def.insert(*id, def_id);
|
||||
}
|
||||
}
|
||||
None =>
|
||||
if let Some(def_id) = def_id {
|
||||
self.symbol_table.id_to_def.insert(*id, def_id);
|
||||
},
|
||||
}
|
||||
} else if let Some(def_id) = def_id {
|
||||
self.symbol_table.id_to_def.insert(*id, def_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ASTVisitor for ScopeResolver<'a> {
|
||||
// Import statements bring in a bunch of local names that all map to a specific FQSN.
|
||||
// FQSNs map to a Symbol (or this is an error), Symbols have a DefId. So for every
|
||||
// name we import, we map a local name (a string) to a NameType::ImportedDefinition(DefId).
|
||||
fn import(&mut self, import_spec: &ImportSpecifier) -> Recursion {
|
||||
let ImportSpecifier { ref path_components, ref imported_names, .. } = &import_spec;
|
||||
match imported_names {
|
||||
ImportedNames::All => {
|
||||
let prefix =
|
||||
Fqsn { scopes: path_components.iter().map(|c| ScopeSegment::Name(c.clone())).collect() };
|
||||
let members = self.symbol_table.symbol_trie.get_children(&prefix);
|
||||
for fqsn in members.into_iter() {
|
||||
self.lexical_scopes.insert(fqsn.last_elem(), NameType::Import(fqsn));
|
||||
}
|
||||
}
|
||||
ImportedNames::LastOfPath => {
|
||||
let fqsn =
|
||||
Fqsn { scopes: path_components.iter().map(|c| ScopeSegment::Name(c.clone())).collect() };
|
||||
self.lexical_scopes.insert(fqsn.last_elem(), NameType::Import(fqsn));
|
||||
}
|
||||
ImportedNames::List(ref names) => {
|
||||
let fqsn_prefix: Vec<ScopeSegment> =
|
||||
path_components.iter().map(|c| ScopeSegment::Name(c.clone())).collect();
|
||||
for name in names.iter() {
|
||||
let mut scopes = fqsn_prefix.clone();
|
||||
scopes.push(ScopeSegment::Name(name.clone()));
|
||||
let fqsn = Fqsn { scopes };
|
||||
self.lexical_scopes.insert(fqsn.last_elem(), NameType::Import(fqsn));
|
||||
}
|
||||
}
|
||||
};
|
||||
Recursion::Continue
|
||||
}
|
||||
|
||||
fn declaration(&mut self, declaration: &Declaration, id: &ItemId) -> Recursion {
|
||||
let cur_function_name = match self.lexical_scopes.get_name() {
|
||||
//TODO this needs to be a fqsn
|
||||
Some(ScopeType::Function { name }) => Some(name.clone()),
|
||||
_ => None,
|
||||
};
|
||||
match declaration {
|
||||
Declaration::FuncDecl(signature, block) => {
|
||||
let param_names = signature.params.iter().map(|param| param.name.clone());
|
||||
//TODO I'm 90% sure this is right, until I get to closures
|
||||
//let mut new_scope = self.lexical_scopes.new_scope(Some(ScopeType::Function { name: signature.name.clone() }));
|
||||
//TODO this will recurse unwantedly into scopes; need to pop an outer function
|
||||
//scope off first before going into a non-closure scope
|
||||
let mut new_scope =
|
||||
ScopeStack::new(Some(ScopeType::Function { name: signature.name.clone() }));
|
||||
|
||||
for (n, param) in param_names.enumerate() {
|
||||
new_scope.insert(param, NameType::Param(n as u8));
|
||||
}
|
||||
|
||||
self.lexical_scopes.insert(signature.name.clone(), NameType::LocalFunction(*id));
|
||||
|
||||
let mut new_resolver =
|
||||
ScopeResolver { symbol_table: self.symbol_table, lexical_scopes: new_scope };
|
||||
walk_block(&mut new_resolver, block);
|
||||
Recursion::Stop
|
||||
}
|
||||
Declaration::Binding { name, .. } => {
|
||||
if let Some(fn_name) = cur_function_name {
|
||||
// We are within a function scope
|
||||
let fqsn =
|
||||
Fqsn { scopes: vec![ScopeSegment::Name(fn_name), ScopeSegment::Name(name.clone())] };
|
||||
self.symbol_table.add_symbol(id, fqsn, SymbolSpec::LocalVariable);
|
||||
self.lexical_scopes.insert(name.clone(), NameType::LocalVariable(*id));
|
||||
}
|
||||
Recursion::Continue
|
||||
}
|
||||
Declaration::Impl { block, .. } => {
|
||||
let new_scope = ScopeStack::new(Some(ScopeType::ImplBlock));
|
||||
let mut new_resolver =
|
||||
ScopeResolver { symbol_table: self.symbol_table, lexical_scopes: new_scope };
|
||||
for stmt in block.iter() {
|
||||
walk_declaration(&mut new_resolver, &stmt.kind, &stmt.id);
|
||||
}
|
||||
Recursion::Stop
|
||||
}
|
||||
_ => Recursion::Continue,
|
||||
}
|
||||
}
|
||||
|
||||
fn expression(&mut self, expression: &Expression) -> Recursion {
|
||||
use ExpressionKind::*;
|
||||
match &expression.kind {
|
||||
Value(name) => {
|
||||
self.lookup_name_in_scope(name);
|
||||
}
|
||||
NamedStruct { name, fields: _ } => {
|
||||
self.lookup_name_in_scope(name);
|
||||
}
|
||||
Lambda { params, body, .. } => {
|
||||
let param_names = params.iter().map(|param| param.name.clone());
|
||||
//TODO need to properly handle closure scope, this is currently broken
|
||||
//let mut new_scope = self.lexical_scopes.new_scope(Some(ScopeType::Function { name: signature.name.clone() }));
|
||||
let mut new_scope = ScopeStack::new(Some(ScopeType::Lambda));
|
||||
|
||||
for (n, param) in param_names.enumerate() {
|
||||
new_scope.insert(param, NameType::Param(n as u8));
|
||||
}
|
||||
|
||||
let mut new_resolver =
|
||||
ScopeResolver { symbol_table: self.symbol_table, lexical_scopes: new_scope };
|
||||
walk_block(&mut new_resolver, body);
|
||||
return Recursion::Stop;
|
||||
}
|
||||
IfExpression { discriminator, body } => {
|
||||
if let Some(d) = discriminator.as_ref() {
|
||||
walk_expression(self, d);
|
||||
}
|
||||
let mut resolver = ScopeResolver {
|
||||
lexical_scopes: self.lexical_scopes.new_scope(Some(ScopeType::PatternMatch)),
|
||||
symbol_table: self.symbol_table,
|
||||
};
|
||||
walk_if_expr_body(&mut resolver, body);
|
||||
return Recursion::Stop;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Recursion::Continue
|
||||
}
|
||||
|
||||
fn pattern(&mut self, pat: &Pattern) -> Recursion {
|
||||
use Pattern::*;
|
||||
|
||||
match pat {
|
||||
Literal(..) | Ignored | TuplePattern(..) => (),
|
||||
TupleStruct(name, _) | Record(name, _) => {
|
||||
self.lookup_name_in_scope(name);
|
||||
}
|
||||
//TODO this isn't really the right syntax for a VarOrName
|
||||
VarOrName(QualifiedName { id, components }) => {
|
||||
if components.len() == 1 {
|
||||
//TODO need a better way to construct a FQSN from a QualifiedName
|
||||
let local_name: Rc<String> = components[0].clone();
|
||||
let lscope = ScopeSegment::Name(Rc::new("<local-case-match>".to_string()));
|
||||
let fqsn = Fqsn { scopes: vec![lscope, ScopeSegment::Name(local_name.clone())] };
|
||||
self.symbol_table.add_symbol(id, fqsn, SymbolSpec::LocalVariable);
|
||||
self.lexical_scopes.insert(local_name, NameType::LocalVariable(*id));
|
||||
} else {
|
||||
let fqsn = Fqsn {
|
||||
scopes: components.iter().map(|name| ScopeSegment::Name(name.clone())).collect(),
|
||||
};
|
||||
let def_id = self.symbol_table.symbol_trie.lookup(&fqsn);
|
||||
|
||||
if let Some(def_id) = def_id {
|
||||
self.symbol_table.id_to_def.insert(*id, def_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Recursion::Continue
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
use radix_trie::{Trie, TrieCommon, TrieKey};
|
||||
|
||||
use super::{DefId, Fqsn, ScopeSegment};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SymbolTrie(Trie<Fqsn, DefId>);
|
||||
|
||||
impl TrieKey for Fqsn {
|
||||
fn encode_bytes(&self) -> Vec<u8> {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
let mut output = vec![];
|
||||
for segment in self.scopes.iter() {
|
||||
let ScopeSegment::Name(s) = segment;
|
||||
s.as_bytes().hash(&mut hasher);
|
||||
output.extend_from_slice(&hasher.finish().to_be_bytes());
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolTrie {
|
||||
pub fn new() -> SymbolTrie {
|
||||
SymbolTrie(Trie::new())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, fqsn: &Fqsn, def_id: DefId) {
|
||||
self.0.insert(fqsn.clone(), def_id);
|
||||
}
|
||||
|
||||
pub fn lookup(&self, fqsn: &Fqsn) -> Option<DefId> {
|
||||
self.0.get(fqsn).cloned()
|
||||
}
|
||||
|
||||
pub fn get_children(&self, fqsn: &Fqsn) -> Vec<Fqsn> {
|
||||
let subtrie = match self.0.subtrie(fqsn) {
|
||||
Some(s) => s,
|
||||
None => return vec![],
|
||||
};
|
||||
let output: Vec<Fqsn> = subtrie.keys().filter(|cur_key| **cur_key != *fqsn).cloned().collect();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::symbol_table::Fqsn;
|
||||
|
||||
fn make_fqsn(strs: &[&str]) -> Fqsn {
|
||||
Fqsn::from_strs(strs)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trie_insertion() {
|
||||
let id = DefId::default();
|
||||
let mut trie = SymbolTrie::new();
|
||||
|
||||
trie.insert(&make_fqsn(&["unrelated", "thing"]), id);
|
||||
trie.insert(&make_fqsn(&["outer", "inner"]), id);
|
||||
trie.insert(&make_fqsn(&["outer", "inner", "still_inner"]), id);
|
||||
|
||||
let children = trie.get_children(&make_fqsn(&["outer", "inner"]));
|
||||
assert_eq!(children.len(), 1);
|
||||
}
|
||||
}
|
@ -1,314 +0,0 @@
|
||||
#![cfg(test)]
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
use super::*;
|
||||
use crate::util::quick_ast;
|
||||
|
||||
fn add_symbols(src: &str) -> (SymbolTable, Result<(), Vec<SymbolError>>) {
|
||||
let ast = quick_ast(src);
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let mut type_context = crate::type_inference::TypeContext::new();
|
||||
let result = symbol_table.process_ast(&ast, &mut type_context);
|
||||
(symbol_table, result)
|
||||
}
|
||||
|
||||
fn make_fqsn(strs: &[&str]) -> Fqsn {
|
||||
Fqsn::from_strs(strs)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_symbol_table() {
|
||||
let src = "let a = 10; fn b() { 20 }";
|
||||
let (symbols, _) = add_symbols(src);
|
||||
|
||||
fn make_fqsn(strs: &[&str]) -> Fqsn {
|
||||
Fqsn::from_strs(strs)
|
||||
}
|
||||
|
||||
symbols.fq_names.table.get(&make_fqsn(&["b"])).unwrap();
|
||||
|
||||
let src = "type Option<T> = Some(T) | None";
|
||||
let (symbols, _) = add_symbols(src);
|
||||
|
||||
symbols.types.table.get(&make_fqsn(&["Option"])).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_function_definition_duplicates() {
|
||||
let source = r#"
|
||||
fn a() { 1 }
|
||||
fn b() { 2 }
|
||||
fn a() { 3 }
|
||||
"#;
|
||||
let (_, output) = add_symbols(source);
|
||||
let errs = output.unwrap_err();
|
||||
assert_matches!(&errs[..], [
|
||||
SymbolError::DuplicateName { prev_name, ..}
|
||||
] if prev_name == &Fqsn::from_strs(&["a"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_variable_definition_duplicates() {
|
||||
let source = r#"
|
||||
let x = 9
|
||||
let a = 20
|
||||
let q = 39
|
||||
let a = 30
|
||||
let x = 34
|
||||
"#;
|
||||
let (_, output) = add_symbols(source);
|
||||
let errs = output.unwrap_err();
|
||||
|
||||
assert_matches!(&errs[..], [
|
||||
SymbolError::DuplicateName { prev_name: pn1, ..},
|
||||
SymbolError::DuplicateName { prev_name: pn2, ..}
|
||||
] if pn1 == &Fqsn::from_strs(&["a"]) && pn2 == &Fqsn::from_strs(&["x"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_type_definition_duplicates() {
|
||||
let source = r#"
|
||||
let x = 9
|
||||
type Food = Japchae | Burrito | Other
|
||||
type Food = GoodJapchae | Breadfruit
|
||||
"#;
|
||||
let (_, output) = add_symbols(source);
|
||||
let errs = output.unwrap_err();
|
||||
let err = &errs[0];
|
||||
|
||||
match err {
|
||||
SymbolError::DuplicateName { location: _, prev_name } => {
|
||||
assert_eq!(prev_name, &Fqsn::from_strs(&["Food"]));
|
||||
|
||||
//TODO restore this Location test
|
||||
//assert_eq!(location, &Location { line_num: 2, char_num: 2 });
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_variant_duplicates() {
|
||||
let source = r#"
|
||||
type Panda = FoolsGold | Kappa(i32) | Remix | Kappa | Thursday | Remix
|
||||
"#;
|
||||
let (_, output) = add_symbols(source);
|
||||
let errs = output.unwrap_err();
|
||||
assert_eq!(errs.len(), 2);
|
||||
assert_matches!(&errs[0], SymbolError::DuplicateVariant {
|
||||
type_fqsn, name } if *type_fqsn == Fqsn::from_strs(&["Panda"]) &&
|
||||
name == "Kappa");
|
||||
|
||||
assert_matches!(&errs[1], SymbolError::DuplicateVariant {
|
||||
type_fqsn, name } if *type_fqsn == Fqsn::from_strs(&["Panda"]) &&
|
||||
name == "Remix");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_variable_definition_duplicates_in_function() {
|
||||
let source = r#"
|
||||
fn a() {
|
||||
let a = 20
|
||||
let b = 40
|
||||
a + b
|
||||
}
|
||||
|
||||
fn q() {
|
||||
let a = 29
|
||||
let x = 30
|
||||
let x = 33
|
||||
}
|
||||
"#;
|
||||
let (_, output) = add_symbols(source);
|
||||
let errs = output.unwrap_err();
|
||||
assert_matches!(&errs[..], [
|
||||
SymbolError::DuplicateName { prev_name: pn1, ..},
|
||||
] if pn1 == &Fqsn::from_strs(&["q", "x"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_falsely_detect_duplicates() {
|
||||
let source = r#"
|
||||
let a = 20;
|
||||
fn some_func() {
|
||||
let a = 40;
|
||||
77
|
||||
}
|
||||
let q = 39
|
||||
"#;
|
||||
let (symbols, _) = add_symbols(source);
|
||||
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["a"])).is_some());
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["some_func", "a"])).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enclosing_scopes() {
|
||||
let source = r#"
|
||||
fn outer_func(x) {
|
||||
fn inner_func(arg) {
|
||||
arg
|
||||
}
|
||||
x + inner_func(x)
|
||||
}"#;
|
||||
let (symbols, _) = add_symbols(source);
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["outer_func"])).is_some());
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["outer_func", "inner_func"])).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enclosing_scopes_2() {
|
||||
let source = r#"
|
||||
fn outer_func(x) {
|
||||
fn inner_func(arg) {
|
||||
arg
|
||||
}
|
||||
|
||||
fn second_inner_func() {
|
||||
fn another_inner_func() {
|
||||
}
|
||||
}
|
||||
|
||||
inner_func(x)
|
||||
}
|
||||
"#;
|
||||
let (symbols, _) = add_symbols(source);
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["outer_func"])).is_some());
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["outer_func", "inner_func"])).is_some());
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["outer_func", "second_inner_func"])).is_some());
|
||||
assert!(symbols
|
||||
.fq_names
|
||||
.table
|
||||
.get(&make_fqsn(&["outer_func", "second_inner_func", "another_inner_func"]))
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enclosing_scopes_3() {
|
||||
let source = r#"
|
||||
fn outer_func(x) {
|
||||
|
||||
fn inner_func(arg) {
|
||||
arg
|
||||
}
|
||||
|
||||
fn second_inner_func() {
|
||||
fn another_inner_func() {
|
||||
}
|
||||
fn another_inner_func() {
|
||||
}
|
||||
}
|
||||
|
||||
inner_func(x)
|
||||
}"#;
|
||||
let (_, output) = add_symbols(source);
|
||||
let _err = output.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modules() {
|
||||
let source = r#"
|
||||
module stuff {
|
||||
fn item() {
|
||||
}
|
||||
}
|
||||
|
||||
fn item()
|
||||
"#;
|
||||
|
||||
let (symbols, _) = add_symbols(source);
|
||||
symbols.fq_names.table.get(&make_fqsn(&["stuff"])).unwrap();
|
||||
symbols.fq_names.table.get(&make_fqsn(&["item"])).unwrap();
|
||||
symbols.fq_names.table.get(&make_fqsn(&["stuff", "item"])).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_modules() {
|
||||
let source = r#"
|
||||
module q {
|
||||
fn foo() { 4 }
|
||||
}
|
||||
|
||||
module a {
|
||||
fn foo() { 334 }
|
||||
}
|
||||
|
||||
module a {
|
||||
fn sarat() { 39 }
|
||||
fn foo() { 256.1 }
|
||||
}
|
||||
"#;
|
||||
let (_, output) = add_symbols(source);
|
||||
let errs = output.unwrap_err();
|
||||
|
||||
assert_matches!(&errs[..], [
|
||||
SymbolError::DuplicateName { prev_name: pn1, ..},
|
||||
] if pn1 == &Fqsn::from_strs(&["a"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_struct_members() {
|
||||
let source = r#"
|
||||
type Tarak = Tarak {
|
||||
loujet: i32
|
||||
,
|
||||
mets: i32,
|
||||
mets: i32
|
||||
,
|
||||
}
|
||||
"#;
|
||||
|
||||
let (_, output) = add_symbols(source);
|
||||
let errs = dbg!(output.unwrap_err());
|
||||
assert_matches!(&errs[..], [
|
||||
SymbolError::DuplicateRecord {
|
||||
type_fqsn, member, record, ..},
|
||||
] if type_fqsn == &Fqsn::from_strs(&["Tarak"]) && member == "mets" && record == "Tarak"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_definition_added_to_symbol_table() {
|
||||
let source = r#"
|
||||
|
||||
type Foo = { x: Int, y: Int }
|
||||
|
||||
impl Foo {
|
||||
fn hella() {
|
||||
let a = 50
|
||||
self.x + a
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let (symbols, _) = add_symbols(source);
|
||||
symbols.debug();
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["<impl-block>Foo", "hella"])).is_some());
|
||||
assert!(symbols.fq_names.table.get(&make_fqsn(&["<impl-block>Foo", "hella", "a"])).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_method_definitions_detected() {
|
||||
let source = r#"
|
||||
|
||||
type Foo = { x: Int, y: Int }
|
||||
|
||||
impl Foo {
|
||||
fn hella() {
|
||||
self.x + 50
|
||||
}
|
||||
|
||||
fn hella() {
|
||||
self.x + 40
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let (_symbols, output) = add_symbols(source);
|
||||
let errs = output.unwrap_err();
|
||||
assert_matches!(&errs[..], [
|
||||
SymbolError::DuplicateName { prev_name: pn1, ..},
|
||||
] if pn1 == &Fqsn::from_strs(&["<impl-block>Foo", "hella"]));
|
||||
}
|
@ -1,513 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{EvalResult, Memory, MemoryValue, Primitive, State};
|
||||
use crate::{
|
||||
builtin::Builtin,
|
||||
reduced_ir::{
|
||||
Alternative, Callable, Expression, FunctionDefinition, Literal, Lookup, Pattern, ReducedIR, Statement,
|
||||
},
|
||||
type_inference::TypeContext,
|
||||
util::ScopeStack,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum StatementOutput {
|
||||
Primitive(Primitive),
|
||||
Nothing,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum LoopControlFlow {
|
||||
Break,
|
||||
Continue,
|
||||
}
|
||||
|
||||
pub struct Evaluator<'a, 'b> {
|
||||
type_context: &'b TypeContext,
|
||||
state: &'b mut State<'a>,
|
||||
early_returning: bool,
|
||||
loop_control: Option<LoopControlFlow>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Evaluator<'a, 'b> {
|
||||
pub(crate) fn new(state: &'b mut State<'a>, type_context: &'b TypeContext) -> Self {
|
||||
Self { state, type_context, early_returning: false, loop_control: None }
|
||||
}
|
||||
|
||||
pub fn evaluate(&mut self, reduced: ReducedIR, repl: bool) -> Vec<Result<String, String>> {
|
||||
let mut acc = vec![];
|
||||
for (def_id, function) in reduced.functions.into_iter() {
|
||||
let mem = (&def_id).into();
|
||||
self.state.memory.insert(mem, MemoryValue::Function(function));
|
||||
}
|
||||
|
||||
for statement in reduced.entrypoint.into_iter() {
|
||||
match self.statement(statement) {
|
||||
Ok(StatementOutput::Primitive(output)) if repl =>
|
||||
acc.push(Ok(output.to_repl(self.type_context))),
|
||||
Ok(_) => (),
|
||||
Err(error) => {
|
||||
acc.push(Err(error.msg));
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
fn block(&mut self, statements: Vec<Statement>) -> EvalResult<Primitive> {
|
||||
let mut retval = None;
|
||||
for stmt in statements.into_iter() {
|
||||
match self.statement(stmt)? {
|
||||
StatementOutput::Nothing => (),
|
||||
StatementOutput::Primitive(prim) => {
|
||||
retval = Some(prim);
|
||||
}
|
||||
};
|
||||
if self.early_returning {
|
||||
break;
|
||||
}
|
||||
if self.loop_control.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(if let Some(ret) = retval { ret } else { self.expression(Expression::unit())? })
|
||||
}
|
||||
|
||||
fn statement(&mut self, stmt: Statement) -> EvalResult<StatementOutput> {
|
||||
match stmt {
|
||||
Statement::Binding { ref id, expr, constant: _ } => {
|
||||
let evaluated = self.expression(expr)?;
|
||||
self.state.memory.insert(id.into(), evaluated.into());
|
||||
Ok(StatementOutput::Nothing)
|
||||
}
|
||||
Statement::Expression(expr) => {
|
||||
let evaluated = self.expression(expr)?;
|
||||
Ok(StatementOutput::Primitive(evaluated))
|
||||
}
|
||||
Statement::Return(expr) => {
|
||||
let evaluated = self.expression(expr)?;
|
||||
self.early_returning = true;
|
||||
Ok(StatementOutput::Primitive(evaluated))
|
||||
}
|
||||
Statement::Break => {
|
||||
self.loop_control = Some(LoopControlFlow::Break);
|
||||
Ok(StatementOutput::Nothing)
|
||||
}
|
||||
Statement::Continue => {
|
||||
self.loop_control = Some(LoopControlFlow::Continue);
|
||||
Ok(StatementOutput::Nothing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expression(&mut self, expression: Expression) -> EvalResult<Primitive> {
|
||||
Ok(match expression {
|
||||
Expression::Literal(lit) => Primitive::Literal(lit),
|
||||
Expression::Tuple(items) => Primitive::Tuple(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|expr| self.expression(expr))
|
||||
.collect::<EvalResult<Vec<Primitive>>>()?,
|
||||
),
|
||||
Expression::List(items) => Primitive::List(
|
||||
items
|
||||
.into_iter()
|
||||
.map(|expr| self.expression(expr))
|
||||
.collect::<EvalResult<Vec<Primitive>>>()?,
|
||||
),
|
||||
Expression::Lookup(kind) => match kind {
|
||||
Lookup::Function(ref id) => {
|
||||
let mem = id.into();
|
||||
match self.state.memory.lookup(&mem) {
|
||||
// This just checks that the function exists in "memory" by ID, we don't
|
||||
// actually retrieve it until `apply_function()`
|
||||
Some(MemoryValue::Function(_)) => Primitive::Callable(Callable::UserDefined(*id)),
|
||||
x => return Err(format!("Function not found for id: {} : {:?}", id, x).into()),
|
||||
}
|
||||
}
|
||||
Lookup::Param(n) => {
|
||||
let mem = n.into();
|
||||
match self.state.memory.lookup(&mem) {
|
||||
Some(MemoryValue::Primitive(prim)) => prim.clone(),
|
||||
e => return Err(format!("Param lookup error, got {:?}", e).into()),
|
||||
}
|
||||
}
|
||||
Lookup::SelfParam => {
|
||||
let mem = Memory::self_param();
|
||||
match self.state.memory.lookup(&mem) {
|
||||
Some(MemoryValue::Primitive(prim)) => prim.clone(),
|
||||
e => return Err(format!("SelfParam lookup error, got {:?}", e).into()),
|
||||
}
|
||||
}
|
||||
Lookup::LocalVar(ref id) | Lookup::GlobalVar(ref id) => {
|
||||
let mem = id.into();
|
||||
match self.state.memory.lookup(&mem) {
|
||||
Some(MemoryValue::Primitive(expr)) => expr.clone(),
|
||||
_ =>
|
||||
return Err(
|
||||
format!("Nothing found for local/gloval variable lookup {}", id).into()
|
||||
),
|
||||
}
|
||||
}
|
||||
},
|
||||
Expression::Assign { ref lval, box rval } => {
|
||||
let mem = lval.into();
|
||||
let evaluated = self.expression(rval)?;
|
||||
println!("Inserting {:?} into {:?}", evaluated, mem);
|
||||
self.state.memory.insert(mem, MemoryValue::Primitive(evaluated));
|
||||
Primitive::unit()
|
||||
}
|
||||
Expression::Call { box f, args } => self.call_expression(f, args, None)?,
|
||||
Expression::CallMethod { box f, args, box self_expr } =>
|
||||
self.call_expression(f, args, Some(self_expr))?,
|
||||
Expression::Callable(Callable::DataConstructor { type_id, tag }) => {
|
||||
let arity = self.type_context.lookup_variant_arity(&type_id, tag).unwrap();
|
||||
if arity == 0 {
|
||||
Primitive::Object { type_id, tag, items: vec![], ordered_fields: None }
|
||||
} else {
|
||||
Primitive::Callable(Callable::DataConstructor { type_id, tag })
|
||||
}
|
||||
}
|
||||
Expression::Callable(func) => Primitive::Callable(func),
|
||||
Expression::Conditional { box cond, then_clause, else_clause } => {
|
||||
let cond = self.expression(cond)?;
|
||||
match cond {
|
||||
Primitive::Literal(Literal::Bool(true)) => self.block(then_clause)?,
|
||||
Primitive::Literal(Literal::Bool(false)) => self.block(else_clause)?,
|
||||
v => return Err(format!("Non-boolean value {:?} in if-statement", v).into()),
|
||||
}
|
||||
}
|
||||
Expression::CaseMatch { box cond, alternatives } =>
|
||||
self.case_match_expression(cond, alternatives)?,
|
||||
Expression::Index { box indexee, box indexer } => {
|
||||
let indexee = self.expression(indexee)?;
|
||||
let indexer = self.expression(indexer)?;
|
||||
match (indexee, indexer) {
|
||||
(Primitive::List(items), Primitive::Literal(Literal::Nat(n))) =>
|
||||
match items.get(n as usize) {
|
||||
Some(item) => item.clone(),
|
||||
None => return Err(format!("Invalid index {} for this value", n).into()),
|
||||
},
|
||||
_ => return Err("Invalid index type".to_string().into()),
|
||||
}
|
||||
}
|
||||
Expression::Loop { box cond, statements } => self.loop_expression(cond, statements)?,
|
||||
Expression::ReductionError(e) => return Err(e.into()),
|
||||
Expression::Access { name, box expr } => {
|
||||
let expr = self.expression(expr)?;
|
||||
match expr {
|
||||
Primitive::Object { items, ordered_fields: Some(ordered_fields), .. } => {
|
||||
let idx = match ordered_fields.iter().position(|s| s == &name) {
|
||||
Some(idx) => idx,
|
||||
None => return Err(format!("Field `{}` not found", name).into()),
|
||||
};
|
||||
|
||||
let item = match items.get(idx) {
|
||||
Some(item) => item,
|
||||
None => return Err(format!("Field lookup `{}` failed", name).into()),
|
||||
};
|
||||
|
||||
item.clone()
|
||||
}
|
||||
e =>
|
||||
return Err(
|
||||
format!("Trying to do a field lookup on a non-object value: {:?}", e).into()
|
||||
),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn loop_expression(&mut self, cond: Expression, statements: Vec<Statement>) -> EvalResult<Primitive> {
|
||||
let existing = self.loop_control;
|
||||
let output = self.loop_expression_inner(cond, statements);
|
||||
self.loop_control = existing;
|
||||
output
|
||||
}
|
||||
|
||||
fn loop_expression_inner(
|
||||
&mut self,
|
||||
cond: Expression,
|
||||
statements: Vec<Statement>,
|
||||
) -> EvalResult<Primitive> {
|
||||
loop {
|
||||
let cond = self.expression(cond.clone())?;
|
||||
println!("COND: {:?}", cond);
|
||||
match cond {
|
||||
Primitive::Literal(Literal::Bool(true)) => (),
|
||||
Primitive::Literal(Literal::Bool(false)) => break,
|
||||
e => return Err(format!("Loop condition evaluates to non-boolean: {:?}", e).into()),
|
||||
};
|
||||
//TODO eventually loops shoudl be able to return something
|
||||
let _output = self.block(statements.clone())?;
|
||||
match self.loop_control {
|
||||
None => (),
|
||||
Some(LoopControlFlow::Continue) => {
|
||||
self.loop_control = None;
|
||||
}
|
||||
Some(LoopControlFlow::Break) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Primitive::unit())
|
||||
}
|
||||
|
||||
fn case_match_expression(
|
||||
&mut self,
|
||||
cond: Expression,
|
||||
alternatives: Vec<Alternative>,
|
||||
) -> EvalResult<Primitive> {
|
||||
fn matches(scrut: &Primitive, pat: &Pattern, scope: &mut ScopeStack<Memory, MemoryValue>) -> bool {
|
||||
match pat {
|
||||
Pattern::Ignored => true,
|
||||
Pattern::Binding(ref def_id) => {
|
||||
let mem = def_id.into();
|
||||
scope.insert(mem, MemoryValue::Primitive(scrut.clone())); //TODO make sure this doesn't cause problems with nesting
|
||||
true
|
||||
}
|
||||
Pattern::Literal(pat_literal) =>
|
||||
if let Primitive::Literal(scrut_literal) = scrut {
|
||||
pat_literal == scrut_literal
|
||||
} else {
|
||||
false
|
||||
},
|
||||
Pattern::Tuple { subpatterns, tag } => match tag {
|
||||
None => match scrut {
|
||||
Primitive::Tuple(items) if items.len() == subpatterns.len() => items
|
||||
.iter()
|
||||
.zip(subpatterns.iter())
|
||||
.all(|(item, subpat)| matches(item, subpat, scope)),
|
||||
_ => false, //TODO should be a type error
|
||||
},
|
||||
Some(pattern_tag) => match scrut {
|
||||
//TODO should test type_ids for runtime type checking, once those work
|
||||
Primitive::Object { tag, items, .. }
|
||||
if tag == pattern_tag && items.len() == subpatterns.len() =>
|
||||
items
|
||||
.iter()
|
||||
.zip(subpatterns.iter())
|
||||
.all(|(item, subpat)| matches(item, subpat, scope)),
|
||||
_ => false,
|
||||
},
|
||||
},
|
||||
Pattern::Record { tag: pattern_tag, subpatterns } => match scrut {
|
||||
//TODO several types of possible error here
|
||||
Primitive::Object { tag, items, ordered_fields: Some(ordered_fields), .. }
|
||||
if tag == pattern_tag =>
|
||||
subpatterns.iter().all(|(field_name, subpat)| {
|
||||
let idx = ordered_fields
|
||||
.iter()
|
||||
.position(|field| field.as_str() == field_name.as_ref())
|
||||
.unwrap();
|
||||
let item = &items[idx];
|
||||
matches(item, subpat, scope)
|
||||
}),
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
let cond = self.expression(cond)?;
|
||||
|
||||
for alt in alternatives.into_iter() {
|
||||
let mut new_scope = self.state.memory.new_scope(None);
|
||||
if matches(&cond, &alt.pattern, &mut new_scope) {
|
||||
let mut new_state = State { memory: new_scope };
|
||||
let mut evaluator = Evaluator::new(&mut new_state, self.type_context);
|
||||
let output = evaluator.block(alt.item);
|
||||
self.early_returning = evaluator.early_returning;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
Err("No valid match in match expression".into())
|
||||
}
|
||||
|
||||
//TODO need to do something with self_expr to make method invocations actually work
|
||||
fn call_expression(
|
||||
&mut self,
|
||||
f: Expression,
|
||||
args: Vec<Expression>,
|
||||
self_expr: Option<Expression>,
|
||||
) -> EvalResult<Primitive> {
|
||||
let func = match self.expression(f)? {
|
||||
Primitive::Callable(func) => func,
|
||||
other => return Err(format!("Trying to call non-function value: {:?}", other).into()),
|
||||
};
|
||||
match func {
|
||||
Callable::Builtin(builtin) => self.apply_builtin(builtin, args),
|
||||
Callable::UserDefined(def_id) => {
|
||||
let mem = (&def_id).into();
|
||||
match self.state.memory.lookup(&mem) {
|
||||
Some(MemoryValue::Function(FunctionDefinition { body })) => {
|
||||
let body = body.clone(); //TODO ideally this clone would not happen
|
||||
self.apply_function(body, args, self_expr)
|
||||
}
|
||||
e => Err(format!("Error looking up function with id {}: {:?}", def_id, e).into()),
|
||||
}
|
||||
}
|
||||
Callable::Lambda { arity, body } => {
|
||||
if arity as usize != args.len() {
|
||||
return Err(format!(
|
||||
"Lambda expression requries {} arguments, only {} provided",
|
||||
arity,
|
||||
args.len()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
self.apply_function(body, args, None)
|
||||
}
|
||||
Callable::DataConstructor { type_id, tag } => {
|
||||
let arity = self.type_context.lookup_variant_arity(&type_id, tag).unwrap();
|
||||
if arity as usize != args.len() {
|
||||
return Err(format!(
|
||||
"Constructor expression requries {} arguments, only {} provided",
|
||||
arity,
|
||||
args.len()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut items: Vec<Primitive> = vec![];
|
||||
for arg in args.into_iter() {
|
||||
items.push(self.expression(arg)?);
|
||||
}
|
||||
Ok(Primitive::Object { type_id, tag, items, ordered_fields: None })
|
||||
}
|
||||
Callable::RecordConstructor { type_id, tag, field_order } => {
|
||||
//TODO maybe I'll want to do a runtime check of the evaluated fields
|
||||
/*
|
||||
let record_members = self.type_context.lookup_record_members(type_id, tag)
|
||||
.ok_or(format!("Runtime record lookup for: {} {} not found", type_id, tag).into())?;
|
||||
*/
|
||||
|
||||
let mut items: Vec<Primitive> = vec![];
|
||||
for arg in args.into_iter() {
|
||||
items.push(self.expression(arg)?);
|
||||
}
|
||||
Ok(Primitive::Object { type_id, tag, items, ordered_fields: Some(field_order) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_builtin(&mut self, builtin: Builtin, args: Vec<Expression>) -> EvalResult<Primitive> {
|
||||
use Builtin::*;
|
||||
use Literal::*;
|
||||
use Primitive::Literal as Lit;
|
||||
|
||||
let evaled_args: EvalResult<Vec<Primitive>> =
|
||||
args.into_iter().map(|arg| self.expression(arg)).collect();
|
||||
let evaled_args = evaled_args?;
|
||||
|
||||
Ok(match (builtin, evaled_args.as_slice()) {
|
||||
/* builtin functions */
|
||||
(IOPrint, &[ref anything]) => {
|
||||
print!("{}", anything.to_repl(self.type_context));
|
||||
Primitive::Tuple(vec![])
|
||||
}
|
||||
(IOPrintLn, &[ref anything]) => {
|
||||
println!("{}", anything.to_repl(self.type_context));
|
||||
Primitive::Tuple(vec![])
|
||||
}
|
||||
(IOGetLine, &[]) => {
|
||||
let mut buf = String::new();
|
||||
std::io::stdin().read_line(&mut buf).expect("Error readling line in 'getline'");
|
||||
StringLit(Rc::new(buf.trim().to_string())).into()
|
||||
}
|
||||
/* Binops */
|
||||
(binop, &[ref lhs, ref rhs]) => match (binop, lhs, rhs) {
|
||||
// TODO need a better way of handling these literals
|
||||
(Add, Lit(Nat(l)), Lit(Nat(r))) => Nat(l + r).into(),
|
||||
(Add, Lit(Int(l)), Lit(Int(r))) => Int(l + r).into(),
|
||||
(Add, Lit(Nat(l)), Lit(Int(r))) => Int((*l as i64) + (*r as i64)).into(),
|
||||
(Add, Lit(Int(l)), Lit(Nat(r))) => Int((*l as i64) + (*r as i64)).into(),
|
||||
(Concatenate, Lit(StringLit(ref s1)), Lit(StringLit(ref s2))) =>
|
||||
StringLit(Rc::new(format!("{}{}", s1, s2))).into(),
|
||||
(Subtract, Lit(Nat(l)), Lit(Nat(r))) => Nat(l - r).into(),
|
||||
(Multiply, Lit(Nat(l)), Lit(Nat(r))) => Nat(l * r).into(),
|
||||
(Divide, Lit(Nat(l)), Lit(Nat(r))) => Float((*l as f64) / (*r as f64)).into(),
|
||||
(Quotient, Lit(Nat(l)), Lit(Nat(r))) =>
|
||||
if *r == 0 {
|
||||
return Err("Divide-by-zero error".into());
|
||||
} else {
|
||||
Nat(l / r).into()
|
||||
},
|
||||
(Modulo, Lit(Nat(l)), Lit(Nat(r))) => Nat(l % r).into(),
|
||||
(Exponentiation, Lit(Nat(l)), Lit(Nat(r))) => Nat(l ^ r).into(),
|
||||
(BitwiseAnd, Lit(Nat(l)), Lit(Nat(r))) => Nat(l & r).into(),
|
||||
(BitwiseOr, Lit(Nat(l)), Lit(Nat(r))) => Nat(l | r).into(),
|
||||
|
||||
/* comparisons */
|
||||
(Equality, Lit(Nat(l)), Lit(Nat(r))) => Bool(l == r).into(),
|
||||
(Equality, Lit(Int(l)), Lit(Int(r))) => Bool(l == r).into(),
|
||||
(Equality, Lit(Float(l)), Lit(Float(r))) => Bool(l == r).into(),
|
||||
(Equality, Lit(Bool(l)), Lit(Bool(r))) => Bool(l == r).into(),
|
||||
(Equality, Lit(StringLit(ref l)), Lit(StringLit(ref r))) => Bool(l == r).into(),
|
||||
|
||||
(NotEqual, Lit(Nat(l)), Lit(Nat(r))) => Bool(l != r).into(),
|
||||
(NotEqual, Lit(Int(l)), Lit(Int(r))) => Bool(l != r).into(),
|
||||
(NotEqual, Lit(Float(l)), Lit(Float(r))) => Bool(l != r).into(),
|
||||
(NotEqual, Lit(Bool(l)), Lit(Bool(r))) => Bool(l != r).into(),
|
||||
(NotEqual, Lit(StringLit(ref l)), Lit(StringLit(ref r))) => Bool(l != r).into(),
|
||||
|
||||
(LessThan, Lit(Nat(l)), Lit(Nat(r))) => Bool(l < r).into(),
|
||||
(LessThan, Lit(Int(l)), Lit(Int(r))) => Bool(l < r).into(),
|
||||
(LessThan, Lit(Float(l)), Lit(Float(r))) => Bool(l < r).into(),
|
||||
|
||||
(LessThanOrEqual, Lit(Nat(l)), Lit(Nat(r))) => Bool(l <= r).into(),
|
||||
(LessThanOrEqual, Lit(Int(l)), Lit(Int(r))) => Bool(l <= r).into(),
|
||||
(LessThanOrEqual, Lit(Float(l)), Lit(Float(r))) => Bool(l <= r).into(),
|
||||
|
||||
(GreaterThan, Lit(Nat(l)), Lit(Nat(r))) => Bool(l > r).into(),
|
||||
(GreaterThan, Lit(Int(l)), Lit(Int(r))) => Bool(l > r).into(),
|
||||
(GreaterThan, Lit(Float(l)), Lit(Float(r))) => Bool(l > r).into(),
|
||||
|
||||
(GreaterThanOrEqual, Lit(Nat(l)), Lit(Nat(r))) => Bool(l >= r).into(),
|
||||
(GreaterThanOrEqual, Lit(Int(l)), Lit(Int(r))) => Bool(l >= r).into(),
|
||||
(GreaterThanOrEqual, Lit(Float(l)), Lit(Float(r))) => Bool(l >= r).into(),
|
||||
|
||||
(binop, lhs, rhs) =>
|
||||
return Err(format!("Invalid binop expression {:?} {:?} {:?}", lhs, binop, rhs).into()),
|
||||
},
|
||||
(prefix, &[ref arg]) => match (prefix, arg) {
|
||||
(BooleanNot, Lit(Bool(true))) => Bool(false),
|
||||
(BooleanNot, Lit(Bool(false))) => Bool(true),
|
||||
(Negate, Lit(Nat(n))) => Int(-(*n as i64)),
|
||||
(Negate, Lit(Int(n))) => Int(-(*n as i64)),
|
||||
(Negate, Lit(Float(f))) => Float(-(*f as f64)),
|
||||
(Increment, Lit(Int(n))) => Int(*n),
|
||||
(Increment, Lit(Nat(n))) => Nat(*n),
|
||||
_ => return Err("No valid prefix op".into()),
|
||||
}
|
||||
.into(),
|
||||
(x, args) => return Err(format!("bad or unimplemented builtin {:?} | {:?}", x, args).into()),
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_function(
|
||||
&mut self,
|
||||
body: Vec<Statement>,
|
||||
args: Vec<Expression>,
|
||||
self_expr: Option<Expression>,
|
||||
) -> EvalResult<Primitive> {
|
||||
let self_expr = if let Some(expr) = self_expr { Some(self.expression(expr)?) } else { None };
|
||||
let mut evaluated_args: Vec<Primitive> = vec![];
|
||||
for arg in args.into_iter() {
|
||||
evaluated_args.push(self.expression(arg)?);
|
||||
}
|
||||
|
||||
let mut frame_state = State { memory: self.state.memory.new_scope(None) };
|
||||
let mut evaluator = Evaluator::new(&mut frame_state, self.type_context);
|
||||
|
||||
if let Some(evaled) = self_expr {
|
||||
let mem = Memory::self_param();
|
||||
evaluator.state.memory.insert(mem, MemoryValue::Primitive(evaled));
|
||||
}
|
||||
for (n, evaled) in evaluated_args.into_iter().enumerate() {
|
||||
let n = n as u8;
|
||||
let mem = n.into();
|
||||
evaluator.state.memory.insert(mem, MemoryValue::Primitive(evaled));
|
||||
}
|
||||
evaluator.block(body)
|
||||
}
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
use std::{convert::From, fmt::Write};
|
||||
|
||||
use crate::{
|
||||
reduced_ir::{Callable, Expression, FunctionDefinition, Literal, ReducedIR},
|
||||
symbol_table::DefId,
|
||||
type_inference::{TypeContext, TypeId},
|
||||
util::{delim_wrapped, ScopeStack},
|
||||
};
|
||||
|
||||
mod evaluator;
|
||||
mod test;
|
||||
|
||||
type EvalResult<T> = Result<T, RuntimeError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct State<'a> {
|
||||
memory: ScopeStack<'a, Memory, MemoryValue>,
|
||||
}
|
||||
|
||||
//TODO - eh, I dunno, maybe it doesn't matter exactly how memory works in the tree-walking
|
||||
//evaluator
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
enum Memory {
|
||||
Index(u32),
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
fn self_param() -> Self {
|
||||
Memory::Index(3_999_999)
|
||||
}
|
||||
}
|
||||
|
||||
// This is for function param lookups, and is a hack
|
||||
impl From<u8> for Memory {
|
||||
fn from(n: u8) -> Self {
|
||||
Memory::Index(4_000_000 + (n as u32))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DefId> for Memory {
|
||||
fn from(id: &DefId) -> Self {
|
||||
Self::Index(id.as_u32())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RuntimeError {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl From<String> for RuntimeError {
|
||||
fn from(msg: String) -> Self {
|
||||
Self { msg }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for RuntimeError {
|
||||
fn from(msg: &str) -> Self {
|
||||
Self { msg: msg.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeError {
|
||||
#[allow(dead_code)]
|
||||
fn get_msg(&self) -> String {
|
||||
format!("Runtime error: {}", self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Anything that can be stored in memory; that is, a function definition, or a fully-evaluated
|
||||
/// program value.
|
||||
#[derive(Debug)]
|
||||
enum MemoryValue {
|
||||
Function(FunctionDefinition),
|
||||
Primitive(Primitive),
|
||||
}
|
||||
|
||||
impl From<Primitive> for MemoryValue {
|
||||
fn from(prim: Primitive) -> Self {
|
||||
Self::Primitive(prim)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum RuntimeValue {
|
||||
Expression(Expression),
|
||||
Evaluated(Primitive),
|
||||
}
|
||||
|
||||
impl From<Expression> for RuntimeValue {
|
||||
fn from(expr: Expression) -> Self {
|
||||
Self::Expression(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Primitive> for RuntimeValue {
|
||||
fn from(prim: Primitive) -> Self {
|
||||
Self::Evaluated(prim)
|
||||
}
|
||||
}
|
||||
|
||||
/// A fully-reduced value
|
||||
#[derive(Debug, Clone)]
|
||||
enum Primitive {
|
||||
Tuple(Vec<Primitive>),
|
||||
List(Vec<Primitive>),
|
||||
Literal(Literal),
|
||||
Callable(Callable),
|
||||
Object { type_id: TypeId, tag: u32, ordered_fields: Option<Vec<String>>, items: Vec<Primitive> },
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
fn to_repl(&self, type_context: &TypeContext) -> String {
|
||||
match self {
|
||||
Primitive::Object { type_id, items, tag, ordered_fields: _ } if items.is_empty() =>
|
||||
type_context.variant_local_name(type_id, *tag).unwrap().to_string(),
|
||||
Primitive::Object { type_id, items, tag, ordered_fields: None } => {
|
||||
format!(
|
||||
"{}{}",
|
||||
type_context.variant_local_name(type_id, *tag).unwrap(),
|
||||
delim_wrapped('(', ')', items.iter().map(|item| item.to_repl(type_context)))
|
||||
)
|
||||
}
|
||||
Primitive::Object { type_id, items, tag, ordered_fields: Some(fields) } => {
|
||||
let mut buf = format!("{} {{ ", type_context.variant_local_name(type_id, *tag).unwrap());
|
||||
for item in fields.iter().zip(items.iter()).map(Some).intersperse(None) {
|
||||
match item {
|
||||
Some((name, val)) => write!(buf, "{}: {}", name, val.to_repl(type_context)).unwrap(),
|
||||
None => write!(buf, ", ").unwrap(),
|
||||
}
|
||||
}
|
||||
write!(buf, " }}").unwrap();
|
||||
buf
|
||||
}
|
||||
Primitive::Literal(lit) => match lit {
|
||||
Literal::Nat(n) => format!("{}", n),
|
||||
Literal::Int(i) => format!("{}", i),
|
||||
Literal::Float(f) => format!("{}", f),
|
||||
Literal::Bool(b) => format!("{}", b),
|
||||
Literal::StringLit(s) => format!("\"{}\"", s),
|
||||
},
|
||||
Primitive::Tuple(terms) => delim_wrapped('(', ')', terms.iter().map(|x| x.to_repl(type_context))),
|
||||
Primitive::List(terms) => delim_wrapped('[', ']', terms.iter().map(|x| x.to_repl(type_context))),
|
||||
Primitive::Callable(..) => "<some-callable>".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unit() -> Self {
|
||||
Primitive::Tuple(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Literal> for Primitive {
|
||||
fn from(lit: Literal) -> Self {
|
||||
Primitive::Literal(lit)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self { memory: ScopeStack::new(Some("global".to_string())) }
|
||||
}
|
||||
|
||||
pub fn evaluate(
|
||||
&mut self,
|
||||
reduced: ReducedIR,
|
||||
type_context: &TypeContext,
|
||||
repl: bool,
|
||||
) -> Vec<Result<String, String>> {
|
||||
let mut evaluator = evaluator::Evaluator::new(self, type_context);
|
||||
evaluator.evaluate(reduced, repl)
|
||||
}
|
||||
}
|
@ -1,564 +0,0 @@
|
||||
#![cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::{
|
||||
symbol_table::SymbolTable,
|
||||
tree_walk_eval::{evaluator::Evaluator, State},
|
||||
type_inference::TypeContext,
|
||||
};
|
||||
|
||||
fn evaluate_input(input: &str) -> Result<String, String> {
|
||||
let ast = crate::util::quick_ast(input);
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let mut type_context = TypeContext::new();
|
||||
|
||||
symbol_table.process_ast(&ast, &mut type_context).unwrap();
|
||||
|
||||
let reduced_ir = crate::reduced_ir::reduce(&ast, &symbol_table, &type_context);
|
||||
reduced_ir.debug(&symbol_table);
|
||||
println!("========");
|
||||
symbol_table.debug();
|
||||
|
||||
let mut state = State::new();
|
||||
let mut evaluator = Evaluator::new(&mut state, &type_context);
|
||||
let mut outputs = evaluator.evaluate(reduced_ir, true);
|
||||
outputs.pop().unwrap()
|
||||
}
|
||||
|
||||
fn eval_assert(input: &str, expected: &str) {
|
||||
assert_eq!(evaluate_input(input), Ok(expected.to_string()));
|
||||
}
|
||||
|
||||
fn eval_assert_failure(input: &str, expected: &str) {
|
||||
assert_eq!(evaluate_input(input), Err(expected.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_eval() {
|
||||
eval_assert("1 + 2", "3");
|
||||
eval_assert("let mut a = 1; a = 2", "()");
|
||||
eval_assert("let mut a = 1; a = a + 2; a", "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn op_eval() {
|
||||
eval_assert("-13", "-13");
|
||||
eval_assert("10 - 2", "8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_eval() {
|
||||
eval_assert("fn oi(x) { x + 1 }; oi(4)", "5");
|
||||
eval_assert("fn oi(x) { x + 1 }; oi(1+2)", "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopes() {
|
||||
let scope_ok = r#"
|
||||
let a = 20
|
||||
fn haha() {
|
||||
let something = 38
|
||||
let a = 10
|
||||
a
|
||||
}
|
||||
haha()
|
||||
"#;
|
||||
|
||||
eval_assert(scope_ok, "10");
|
||||
|
||||
let scope_ok = r#"
|
||||
let a = 20
|
||||
fn queque() {
|
||||
let a = 10
|
||||
a
|
||||
}
|
||||
a
|
||||
"#;
|
||||
eval_assert(scope_ok, "20");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_scopes_2() {
|
||||
eval_assert(
|
||||
r#"
|
||||
fn trad() {
|
||||
let a = 10
|
||||
fn jinner() {
|
||||
let b = 20
|
||||
b
|
||||
}
|
||||
|
||||
a + jinner()
|
||||
}
|
||||
trad()"#,
|
||||
"30",
|
||||
);
|
||||
|
||||
let err = "No symbol found for name: `a`";
|
||||
|
||||
eval_assert_failure(
|
||||
r#"
|
||||
fn trad() {
|
||||
let a = 10
|
||||
fn inner() {
|
||||
let b = 20
|
||||
a + b
|
||||
}
|
||||
|
||||
inner()
|
||||
}
|
||||
|
||||
trad()
|
||||
"#,
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adt_output_1() {
|
||||
let source = r#"
|
||||
|
||||
type Option<T> = Some(T) | None
|
||||
let a = Option::None
|
||||
let b = Option::Some(10)
|
||||
(b, a)
|
||||
"#;
|
||||
eval_assert(source, "(Some(10), None)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adt_output_2() {
|
||||
let source = r#"
|
||||
type Gobble = Unknown | Rufus { a: Int, torrid: Nat }
|
||||
let b = Gobble::Rufus { a: 3, torrid: 99 }
|
||||
b
|
||||
"#;
|
||||
eval_assert(source, "Rufus { a: 3, torrid: 99 }");
|
||||
|
||||
let source = r#"
|
||||
type Gobble = Unknown | Rufus { a: Int, torrid: Nat }
|
||||
let b = Gobble::Rufus { torrid: 3, a: 84 }
|
||||
b
|
||||
"#;
|
||||
eval_assert(source, "Rufus { a: 84, torrid: 3 }");
|
||||
|
||||
let source = r#"
|
||||
type Gobble = Unknown | Rufus { a: Int, torrid: Nat }
|
||||
let b = Gobble::Rufus { a: 84 }
|
||||
b
|
||||
"#;
|
||||
eval_assert_failure(source, "Field torrid not specified for record Gobble::Rufus");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_if_statement() {
|
||||
let source = r#"
|
||||
let a = 10
|
||||
let b = 10
|
||||
if a == b then { 69 } else { 420 }
|
||||
"#;
|
||||
eval_assert(source, "69");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_patterns_1() {
|
||||
let source = r#"
|
||||
let x = 10
|
||||
let a = if x is 10 then { 255 } else { 256 }
|
||||
let b = if 23 is 99 then { 255 } else { 256 }
|
||||
let c = if true is false then { 9 } else { 10 }
|
||||
let d = if "xxx" is "yyy" then { 20 } else { 30 }
|
||||
(a, b, c, d)
|
||||
"#;
|
||||
eval_assert(source, "(255, 256, 10, 30)");
|
||||
}
|
||||
|
||||
#[test_case("sanchez", "1")]
|
||||
#[test_case("mouri", "2")]
|
||||
#[test_case("hella", "3")]
|
||||
#[test_case("cyrus", "4")]
|
||||
fn basic_patterns_2(input: &str, expected: &str) {
|
||||
let mut source = format!(r#"let x = "{}""#, input);
|
||||
source.push_str(
|
||||
r#"
|
||||
if x {
|
||||
is "sanchez" then 1
|
||||
is "mouri" then 2
|
||||
is "hella" then 3
|
||||
is _ then 4
|
||||
}
|
||||
"#,
|
||||
);
|
||||
eval_assert(&source, expected);
|
||||
}
|
||||
|
||||
#[test_case(r#"(45, "panda", false, 2.2)"#, r#""yes""#)]
|
||||
#[test_case(r#"(99, "panda", false, -2.45)"#, r#""maybe""#)]
|
||||
fn tuple_patterns(input: &str, expected: &str) {
|
||||
let mut source = format!("let x = {}", input);
|
||||
source.push_str(
|
||||
r#"
|
||||
if x {
|
||||
is (45, "pablo", _, 28.4) then "no"
|
||||
is (_, "panda", _, 2.2) then "yes"
|
||||
is _ then "maybe"
|
||||
}"#,
|
||||
);
|
||||
|
||||
eval_assert(&source, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_patterns_1() {
|
||||
let source = r#"
|
||||
type Ara = Kueh { a: Int, b: String } | Morbuk
|
||||
|
||||
let alpha = Ara::Kueh { a: 10, b: "sanchez" }
|
||||
if alpha {
|
||||
is Ara::Kueh { a, b } then (b, a)
|
||||
is _ then ("nooo", 8888)
|
||||
}"#;
|
||||
eval_assert(source, r#"("sanchez", 10)"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_patterns_2() {
|
||||
let source = r#"
|
||||
type Ara = Kueh { a: Int, b: String } | Morbuk
|
||||
|
||||
let alpha = Ara::Kueh { a: 10, b: "sanchez" }
|
||||
if alpha {
|
||||
is Ara::Kueh { a, b: le_value } then (le_value, (a*2))
|
||||
is _ then ("nooo", 8888)
|
||||
}"#;
|
||||
eval_assert(source, r#"("sanchez", 20)"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_patterns_3() {
|
||||
let source = r#"
|
||||
type Vstsavlobs = { tkveni: Int, b: Ia }
|
||||
type Ia = { sitqva: Int, ghmerts: String }
|
||||
let b = Vstsavlobs { tkveni: 3, b: Ia::Ia { sitqva: 5, ghmerts: "ooo" } }
|
||||
if b {
|
||||
is Vstsavlobs::Vstsavlobs { tkveni: _, b: Ia::Ia { sitqva, ghmerts } } then sitqva
|
||||
is _ then 5000
|
||||
}"#;
|
||||
eval_assert(source, "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_is_patterns() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let q = "a string"
|
||||
let x = Option::Some(9); if x is Option::Some(q) then { q } else { 0 }"#;
|
||||
|
||||
eval_assert(source, "9");
|
||||
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let q = "a string"
|
||||
let outer = 2
|
||||
let x = Option::None; if x is Option::Some(q) then { q } else { -2 + outer }"#;
|
||||
eval_assert(source, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_if_matching() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let a = Option::None
|
||||
if a { is Option::None then 4; is Option::Some(x) then x }
|
||||
"#;
|
||||
eval_assert(source, "4");
|
||||
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let sara = Option::Some(99)
|
||||
if sara { is Option::None then 1 + 3; is Option::Some(x) then x }
|
||||
"#;
|
||||
eval_assert(source, "99");
|
||||
|
||||
let source = r#"
|
||||
let a = 10
|
||||
if a { is 10 then "x"; is 4 then "y" }
|
||||
"#;
|
||||
eval_assert(source, "\"x\"");
|
||||
|
||||
let source = r#"
|
||||
let a = 10
|
||||
if a { is 15 then "x"; is 10 then "y" }
|
||||
"#;
|
||||
eval_assert(source, "\"y\"");
|
||||
}
|
||||
|
||||
//TODO - I can probably cut down some of these
|
||||
#[test]
|
||||
fn string_pattern() {
|
||||
let source = r#"
|
||||
let a = "foo"
|
||||
if a { is "foo" then "x"; is _ then "y" }
|
||||
"#;
|
||||
eval_assert(source, "\"x\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_pattern() {
|
||||
let source = r#"
|
||||
let a = true
|
||||
if a {
|
||||
is true then "x"
|
||||
is false then "y"
|
||||
}
|
||||
"#;
|
||||
eval_assert(source, "\"x\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_pattern_2() {
|
||||
let source = r#"
|
||||
let a = false
|
||||
if a { is true then "x"; is false then "y" }
|
||||
"#;
|
||||
eval_assert(source, "\"y\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_pattern() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
if Option::Some(10) {
|
||||
is _ then "hella"
|
||||
}
|
||||
"#;
|
||||
eval_assert(source, "\"hella\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern() {
|
||||
let source = r#"
|
||||
if (1, 2) {
|
||||
is (1, x) then x;
|
||||
is _ then 99
|
||||
}
|
||||
"#;
|
||||
eval_assert(source, "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_2() {
|
||||
let source = r#"
|
||||
if (1, 2) {
|
||||
is (10, x) then x
|
||||
is (y, x) then x + y
|
||||
}
|
||||
"#;
|
||||
eval_assert(source, "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_3() {
|
||||
let source = r#"
|
||||
if (1, 5) {
|
||||
is (10, x) then x
|
||||
is (1, x) then x
|
||||
}
|
||||
"#;
|
||||
eval_assert(source, "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_4() {
|
||||
let source = r#"
|
||||
if (1, 5) {
|
||||
is (10, x) then x
|
||||
is (1, x) then x
|
||||
}
|
||||
"#;
|
||||
eval_assert(source, "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prim_obj_pattern() {
|
||||
let source = r#"
|
||||
type Stuff = Mulch(Nat) | Jugs(Nat, String) | Mardok
|
||||
let a = Stuff::Mulch(20)
|
||||
let b = Stuff::Jugs(1, "haha")
|
||||
let c = Stuff::Mardok
|
||||
|
||||
let x = if a {
|
||||
is Stuff::Mulch(20) then "x"
|
||||
is _ then "ERR"
|
||||
}
|
||||
|
||||
let y = if b {
|
||||
is Stuff::Mulch(n) then "ERR"
|
||||
is Stuff::Jugs(2, _) then "ERR"
|
||||
is Stuff::Jugs(1, s) then s
|
||||
is _ then "ERR"
|
||||
}
|
||||
|
||||
let z = if c {
|
||||
is Stuff::Jugs(_, _) then "ERR"
|
||||
is Stuff::Mardok then "NIGH"
|
||||
is _ then "ERR"
|
||||
}
|
||||
|
||||
(x, y, z)
|
||||
"#;
|
||||
eval_assert(source, r#"("x", "haha", "NIGH")"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_lambda_evaluation_1() {
|
||||
let source = r#"
|
||||
let q = \(x, y) { x * y }
|
||||
let x = q(5, 2)
|
||||
let y = \(m, n, o) { m + n + o }(1,2,3)
|
||||
(x, y)
|
||||
"#;
|
||||
|
||||
eval_assert(source, r"(10, 6)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_lambda_evaluation_2() {
|
||||
let source = r#"
|
||||
fn milta() {
|
||||
\(x) { x + 33 }
|
||||
}
|
||||
milta()(10)
|
||||
"#;
|
||||
|
||||
eval_assert(source, "43");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_all() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
import Option::*
|
||||
let x = Some(9); if x is Some(q) then { q } else { 0 }"#;
|
||||
eval_assert(source, "9");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accessors() {
|
||||
let source = r#"
|
||||
type Klewos = { a: Int, b: String }
|
||||
let value = Klewos::Klewos { a: 50, b: "nah" }
|
||||
(value.a, value.b)
|
||||
"#;
|
||||
|
||||
eval_assert(source, r#"(50, "nah")"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn early_return() {
|
||||
let source = r#"
|
||||
fn chnurmek(a: Int): Int {
|
||||
if a == 5 then {
|
||||
return 9999;
|
||||
}
|
||||
return (a + 2);
|
||||
}
|
||||
|
||||
(chnurmek(5), chnurmek(0))
|
||||
"#;
|
||||
eval_assert(source, r#"(9999, 2)"#);
|
||||
|
||||
let source = r#"
|
||||
fn marbuk(a: Int, b: Int): (Int, Int) {
|
||||
if a == 5 then {
|
||||
if b == 6 then {
|
||||
return (50, 50);
|
||||
}
|
||||
|
||||
return (a, b + 1)
|
||||
}
|
||||
(a * 100, b * 100)
|
||||
}
|
||||
|
||||
let x = marbuk(1, 1)
|
||||
let y = marbuk(5, 1)
|
||||
let z = marbuk(5, 6)
|
||||
|
||||
(x, y, z)
|
||||
"#;
|
||||
eval_assert(source, "((100, 100), (5, 2), (50, 50))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loops() {
|
||||
let source = r#"
|
||||
let mut a = 0
|
||||
let mut count = 0
|
||||
while a != 5 {
|
||||
a = a + 1
|
||||
count = count + 100
|
||||
}
|
||||
|
||||
count
|
||||
"#;
|
||||
eval_assert(source, "500");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loops_2() {
|
||||
let source = r#"
|
||||
let mut a = 0
|
||||
let mut acc = 0
|
||||
while a < 10 {
|
||||
acc = acc + 1
|
||||
a = a + 1
|
||||
|
||||
// Without this continue, the output would be 20
|
||||
if a == 5 then {
|
||||
continue
|
||||
}
|
||||
|
||||
acc = acc + 1
|
||||
}
|
||||
|
||||
acc"#;
|
||||
eval_assert(source, "19");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_literals() {
|
||||
eval_assert(
|
||||
r#"
|
||||
let a = [7, 8, 9]
|
||||
a
|
||||
"#,
|
||||
"[7, 8, 9]",
|
||||
);
|
||||
|
||||
eval_assert(
|
||||
r#"
|
||||
let a = [7, 8, 9]
|
||||
fn foo() { return 2 }
|
||||
(a[0], a[foo()])
|
||||
"#,
|
||||
"(7, 9)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_method() {
|
||||
let src = r#"
|
||||
type Thing = Thing
|
||||
impl Thing {
|
||||
fn a_method() {
|
||||
20
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let a = Thing::Thing
|
||||
4 + a.a_method()
|
||||
"#;
|
||||
eval_assert(src, "24");
|
||||
}
|
@ -1,445 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
||||
use parsing::{AST, Statement, Declaration, Signature, Expression, ExpressionType, Operation, Variant, TypeName, TypeSingletonName};
|
||||
|
||||
// from Niko's talk
|
||||
/* fn type_check(expression, expected_ty) -> Ty {
|
||||
let ty = bare_type_check(expression, expected_type);
|
||||
if ty icompatible with expected_ty {
|
||||
try_coerce(expression, ty, expected_ty)
|
||||
} else {
|
||||
ty
|
||||
}
|
||||
}
|
||||
|
||||
fn bare_type_check(exprssion, expected_type) -> Ty { ... }
|
||||
*/
|
||||
|
||||
/* H-M ALGO NOTES
|
||||
from https://www.youtube.com/watch?v=il3gD7XMdmA
|
||||
(also check out http://dev.stephendiehl.com/fun/006_hindley_milner.html)
|
||||
|
||||
typeInfer :: Expr a -> Matching (Type a)
|
||||
unify :: Type a -> Type b -> Matching (Type c)
|
||||
|
||||
(Matching a) is a monad in which unification is done
|
||||
|
||||
ex:
|
||||
|
||||
typeInfer (If e1 e2 e3) = do
|
||||
t1 <- typeInfer e1
|
||||
t2 <- typeInfer e2
|
||||
t3 <- typeInfer e3
|
||||
_ <- unify t1 BoolType
|
||||
unify t2 t3 -- b/c t2 and t3 have to be the same type
|
||||
|
||||
typeInfer (Const (ConstInt _)) = IntType -- same for other literals
|
||||
|
||||
--function application
|
||||
typeInfer (Apply f x) = do
|
||||
tf <- typeInfer f
|
||||
tx <- typeInfer x
|
||||
case tf of
|
||||
FunctionType t1 t2 -> do
|
||||
_ <- unify t1 tx
|
||||
return t2
|
||||
_ -> fail "Not a function"
|
||||
|
||||
--type annotation
|
||||
typeInfer (Typed x t) = do
|
||||
tx <- typeInfer x
|
||||
unify tx t
|
||||
|
||||
--variable and let expressions - need to pass around a map of variable names to types here
|
||||
typeInfer :: [ (Var, Type Var) ] -> Expr Var -> Matching (Type Var)
|
||||
|
||||
typeInfer ctx (Var x) = case (lookup x ctx) of
|
||||
Just t -> return t
|
||||
Nothing -> fail "Unknown variable"
|
||||
|
||||
--let x = e1 in e2
|
||||
typeInfer ctx (Let x e1 e2) = do
|
||||
t1 <- typeInfer ctx e1
|
||||
typeInfer ((x, t1) :: ctx) e2
|
||||
|
||||
--lambdas are complicated (this represents ʎx.e)
|
||||
typeInfer ctx (Lambda x e) = do
|
||||
t1 <- allocExistentialVariable
|
||||
t2 <- typeInfer ((x, t1) :: ctx) e
|
||||
return $ FunctionType t1 t2 -- ie. t1 -> t2
|
||||
|
||||
|
||||
--to solve the problem of map :: (a -> b) -> [a] -> [b]
|
||||
when we use a variable whose type has universal tvars, convert those universal
|
||||
tvars to existential ones
|
||||
-and each distinct universal tvar needs to map to the same existential type
|
||||
|
||||
-so we change typeinfer:
|
||||
typeInfer ctx (Var x) = do
|
||||
case (lookup x ctx) of
|
||||
Nothing -> ...
|
||||
Just t -> do
|
||||
let uvars = nub (toList t) -- nub removes duplicates, so this gets unique universally quantified variables
|
||||
evars <- mapM (const allocExistentialVariable) uvars
|
||||
let varMap = zip uvars evars
|
||||
let vixVar varMap v = fromJust $ lookup v varMap
|
||||
return (fmap (fixVar varMap) t)
|
||||
|
||||
--how do we define unify??
|
||||
|
||||
-recall, type signature is:
|
||||
unify :: Type a -> Type b -> Matching (Type c)
|
||||
unify BoolType BoolType = BoolType --easy, same for all constants
|
||||
unify (FunctionType t1 t2) (FunctionType t3 t4) = do
|
||||
t5 <- unify t1 t3
|
||||
t6 <- unify t2 t4
|
||||
return $ FunctionType t5 t6
|
||||
unify (TVar a) (TVar b) = if a == b then TVar a else fail
|
||||
--existential types can be assigned another type at most once
|
||||
--some complicated stuff about hanlding existential types
|
||||
--everything else is a type error
|
||||
unify a b = fail
|
||||
|
||||
|
||||
SKOLEMIZATION - how you prevent an unassigned existential type variable from leaking!
|
||||
-before a type gets to global scope, replace all unassigned existential vars w/ new unique universal
|
||||
type variables
|
||||
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Type {
|
||||
TVar(TypeVar),
|
||||
TConst(TypeConst),
|
||||
TFunc(Box<Type>, Box<Type>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TypeVar {
|
||||
Univ(Rc<String>),
|
||||
Exist(u64),
|
||||
}
|
||||
impl TypeVar {
|
||||
fn univ(label: &str) -> TypeVar {
|
||||
TypeVar::Univ(Rc::new(label.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TypeConst {
|
||||
UserT(Rc<String>),
|
||||
Integer,
|
||||
Float,
|
||||
StringT,
|
||||
Boolean,
|
||||
Unit,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
type TypeCheckResult = Result<Type, String>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
struct PathSpecifier(Rc<String>);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct TypeContextEntry {
|
||||
ty: Type,
|
||||
constant: bool
|
||||
}
|
||||
|
||||
pub struct TypeContext {
|
||||
symbol_table: HashMap<PathSpecifier, TypeContextEntry>,
|
||||
evar_table: HashMap<u64, Type>,
|
||||
existential_type_label_count: u64
|
||||
}
|
||||
|
||||
impl TypeContext {
|
||||
pub fn new() -> TypeContext {
|
||||
TypeContext {
|
||||
symbol_table: HashMap::new(),
|
||||
evar_table: HashMap::new(),
|
||||
existential_type_label_count: 0,
|
||||
}
|
||||
}
|
||||
pub fn add_symbols(&mut self, ast: &AST) {
|
||||
use self::Declaration::*;
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
for statement in ast.0.iter() {
|
||||
match *statement {
|
||||
Statement::ExpressionStatement(_) => (),
|
||||
Statement::Declaration(ref decl) => match *decl {
|
||||
FuncSig(_) => (),
|
||||
Impl { .. } => (),
|
||||
TypeDecl(ref type_constructor, ref body) => {
|
||||
for variant in body.0.iter() {
|
||||
let (spec, ty) = match variant {
|
||||
&Variant::UnitStruct(ref data_constructor) => {
|
||||
let spec = PathSpecifier(data_constructor.clone());
|
||||
let ty = TConst(UserT(type_constructor.name.clone()));
|
||||
(spec, ty)
|
||||
},
|
||||
&Variant::TupleStruct(ref data_construcor, ref args) => {
|
||||
//TODO fix
|
||||
let arg = args.get(0).unwrap();
|
||||
let type_arg = self.from_anno(arg);
|
||||
let spec = PathSpecifier(data_construcor.clone());
|
||||
let ty = TFunc(Box::new(type_arg), Box::new(TConst(UserT(type_constructor.name.clone()))));
|
||||
(spec, ty)
|
||||
},
|
||||
&Variant::Record(_, _) => unimplemented!(),
|
||||
};
|
||||
let entry = TypeContextEntry { ty, constant: true };
|
||||
self.symbol_table.insert(spec, entry);
|
||||
}
|
||||
},
|
||||
TypeAlias { .. } => (),
|
||||
Binding {ref name, ref constant, ref expr} => {
|
||||
let spec = PathSpecifier(name.clone());
|
||||
let ty = expr.1.as_ref()
|
||||
.map(|ty| self.from_anno(ty))
|
||||
.unwrap_or_else(|| { self.alloc_existential_type() }); // this call to alloc_existential is OK b/c a binding only ever has one type, so if the annotation is absent, it's fine to just make one de novo
|
||||
let entry = TypeContextEntry { ty, constant: *constant };
|
||||
self.symbol_table.insert(spec, entry);
|
||||
},
|
||||
FuncDecl(ref signature, _) => {
|
||||
let spec = PathSpecifier(signature.name.clone());
|
||||
let ty = self.from_signature(signature);
|
||||
let entry = TypeContextEntry { ty, constant: true };
|
||||
self.symbol_table.insert(spec, entry);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn lookup(&mut self, binding: &Rc<String>) -> Option<TypeContextEntry> {
|
||||
let key = PathSpecifier(binding.clone());
|
||||
self.symbol_table.get(&key).map(|entry| entry.clone())
|
||||
}
|
||||
pub fn debug_symbol_table(&self) -> String {
|
||||
format!("Symbol table:\n {:?}\nEvar table:\n{:?}", self.symbol_table, self.evar_table)
|
||||
}
|
||||
fn alloc_existential_type(&mut self) -> Type {
|
||||
let ret = Type::TVar(TypeVar::Exist(self.existential_type_label_count));
|
||||
self.existential_type_label_count += 1;
|
||||
ret
|
||||
}
|
||||
|
||||
fn from_anno(&mut self, anno: &TypeName) -> Type {
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
match anno {
|
||||
&TypeName::Singleton(TypeSingletonName { ref name, .. }) => {
|
||||
match name.as_ref().as_ref() {
|
||||
"Int" => TConst(Integer),
|
||||
"Float" => TConst(Float),
|
||||
"Bool" => TConst(Boolean),
|
||||
"String" => TConst(StringT),
|
||||
s => TVar(TypeVar::Univ(Rc::new(format!("{}",s)))),
|
||||
}
|
||||
},
|
||||
&TypeName::Tuple(ref items) => {
|
||||
if items.len() == 1 {
|
||||
TConst(Unit)
|
||||
} else {
|
||||
TConst(Bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_signature(&mut self, sig: &Signature) -> Type {
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
//TODO this won't work properly until you make sure that all (universal) type vars in the function have the same existential type var
|
||||
// actually this should never even put existential types into the symbol table at all
|
||||
|
||||
//this will crash if more than 5 arg function is used
|
||||
let names = vec!["a", "b", "c", "d", "e", "f"];
|
||||
let mut idx = 0;
|
||||
|
||||
let mut get_type = || { let q = TVar(TypeVar::Univ(Rc::new(format!("{}", names.get(idx).unwrap())))); idx += 1; q };
|
||||
|
||||
let return_type = sig.type_anno.as_ref().map(|anno| self.from_anno(&anno)).unwrap_or_else(|| { get_type() });
|
||||
if sig.params.len() == 0 {
|
||||
TFunc(Box::new(TConst(Unit)), Box::new(return_type))
|
||||
} else {
|
||||
let mut output_type = return_type;
|
||||
for p in sig.params.iter() {
|
||||
let p_type = p.1.as_ref().map(|anno| self.from_anno(anno)).unwrap_or_else(|| { get_type() });
|
||||
output_type = TFunc(Box::new(p_type), Box::new(output_type));
|
||||
}
|
||||
output_type
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_check(&mut self, ast: &AST) -> TypeCheckResult {
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
let mut last = TConst(Unit);
|
||||
|
||||
for statement in ast.0.iter() {
|
||||
match statement {
|
||||
&Statement::Declaration(ref _decl) => {
|
||||
//return Err(format!("Declarations not supported"));
|
||||
},
|
||||
&Statement::ExpressionStatement(ref expr) => {
|
||||
last = self.infer(expr)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(last)
|
||||
}
|
||||
fn infer(&mut self, expr: &Expression) -> TypeCheckResult {
|
||||
match (&expr.0, &expr.1) {
|
||||
(exprtype, &Some(ref anno)) => {
|
||||
let tx = self.infer_no_anno(exprtype)?;
|
||||
let ty = self.from_anno(anno);
|
||||
self.unify(tx, ty)
|
||||
},
|
||||
(exprtype, &None) => self.infer_no_anno(exprtype),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_no_anno(&mut self, ex: &ExpressionType) -> TypeCheckResult {
|
||||
use self::ExpressionType::*;
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
Ok(match ex {
|
||||
&IntLiteral(_) => TConst(Integer),
|
||||
&FloatLiteral(_) => TConst(Float),
|
||||
&StringLiteral(_) => TConst(StringT),
|
||||
&BoolLiteral(_) => TConst(Boolean),
|
||||
&Value(ref name, _) => {
|
||||
self.lookup(name)
|
||||
.map(|entry| entry.ty)
|
||||
.ok_or(format!("Couldn't find {}", name))?
|
||||
},
|
||||
&BinExp(ref op, ref lhs, ref rhs) => {
|
||||
let t_lhs = self.infer(lhs)?;
|
||||
match self.infer_op(op)? {
|
||||
TFunc(t1, t2) => {
|
||||
let _ = self.unify(t_lhs, *t1)?;
|
||||
let t_rhs = self.infer(rhs)?;
|
||||
let x = *t2;
|
||||
match x {
|
||||
TFunc(t3, t4) => {
|
||||
let _ = self.unify(t_rhs, *t3)?;
|
||||
*t4
|
||||
},
|
||||
_ => return Err(format!("Not a function type either")),
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Op {:?} is not a function type", op)),
|
||||
}
|
||||
},
|
||||
&Call { ref f, ref arguments } => {
|
||||
let tf = self.infer(f)?;
|
||||
let targ = self.infer(arguments.get(0).unwrap())?;
|
||||
match tf {
|
||||
TFunc(box t1, box t2) => {
|
||||
let _ = self.unify(t1, targ)?;
|
||||
t2
|
||||
},
|
||||
_ => return Err(format!("Not a function!")),
|
||||
}
|
||||
},
|
||||
_ => TConst(Bottom),
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_op(&mut self, op: &Operation) -> TypeCheckResult {
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
macro_rules! binoptype {
|
||||
($lhs:expr, $rhs:expr, $out:expr) => { TFunc(Box::new($lhs), Box::new(TFunc(Box::new($rhs), Box::new($out)))) };
|
||||
}
|
||||
|
||||
Ok(match (*op.0).as_ref() {
|
||||
"+" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
"++" => binoptype!(TConst(StringT), TConst(StringT), TConst(StringT)),
|
||||
"-" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
"*" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
"/" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
"%" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
_ => TConst(Bottom)
|
||||
})
|
||||
}
|
||||
|
||||
fn unify(&mut self, t1: Type, t2: Type) -> TypeCheckResult {
|
||||
use self::Type::*;
|
||||
use self::TypeVar::*;
|
||||
|
||||
println!("Calling unify with `{:?}` and `{:?}`", t1, t2);
|
||||
|
||||
match (&t1, &t2) {
|
||||
(&TConst(ref c1), &TConst(ref c2)) if c1 == c2 => Ok(TConst(c1.clone())),
|
||||
(&TFunc(ref t1, ref t2), &TFunc(ref t3, ref t4)) => {
|
||||
let t5 = self.unify(*t1.clone().clone(), *t3.clone().clone())?;
|
||||
let t6 = self.unify(*t2.clone().clone(), *t4.clone().clone())?;
|
||||
Ok(TFunc(Box::new(t5), Box::new(t6)))
|
||||
},
|
||||
(&TVar(Univ(ref a)), &TVar(Univ(ref b))) => {
|
||||
if a == b {
|
||||
Ok(TVar(Univ(a.clone())))
|
||||
} else {
|
||||
Err(format!("Couldn't unify universal types {} and {}", a, b))
|
||||
}
|
||||
},
|
||||
//the interesting case!!
|
||||
(&TVar(Exist(ref a)), ref t2) => {
|
||||
let x = self.evar_table.get(a).map(|x| x.clone());
|
||||
match x {
|
||||
Some(ref t1) => self.unify(t1.clone().clone(), t2.clone().clone()),
|
||||
None => {
|
||||
self.evar_table.insert(*a, t2.clone().clone());
|
||||
Ok(t2.clone().clone())
|
||||
}
|
||||
}
|
||||
},
|
||||
(ref t1, &TVar(Exist(ref a))) => {
|
||||
let x = self.evar_table.get(a).map(|x| x.clone());
|
||||
match x {
|
||||
Some(ref t2) => self.unify(t2.clone().clone(), t1.clone().clone()),
|
||||
None => {
|
||||
self.evar_table.insert(*a, t1.clone().clone());
|
||||
Ok(t1.clone().clone())
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => Err(format!("Types {:?} and {:?} don't unify", t1, t2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Type, TypeVar, TypeConst, TypeContext};
|
||||
use super::Type::*;
|
||||
use super::TypeConst::*;
|
||||
use schala_lang::parsing::{parse, tokenize};
|
||||
|
||||
macro_rules! type_test {
|
||||
($input:expr, $correct:expr) => {
|
||||
{
|
||||
let mut tc = TypeContext::new();
|
||||
let ast = parse(tokenize($input)).0.unwrap() ;
|
||||
tc.add_symbols(&ast);
|
||||
assert_eq!($correct, tc.type_check(&ast).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_inference() {
|
||||
type_test!("30", TConst(Integer));
|
||||
type_test!("fn x(a: Int): Bool {}; x(1)", TConst(Boolean));
|
||||
}
|
||||
}
|
@ -1,227 +0,0 @@
|
||||
use std::{collections::HashMap, convert::From};
|
||||
|
||||
use crate::{
|
||||
ast::{TypeIdentifier, AST},
|
||||
identifier::{define_id_kind, Id, IdStore},
|
||||
};
|
||||
|
||||
define_id_kind!(TypeItem);
|
||||
pub type TypeId = Id<TypeItem>;
|
||||
|
||||
pub struct TypeContext {
|
||||
defined_types: HashMap<TypeId, DefinedType>,
|
||||
type_id_store: IdStore<TypeItem>,
|
||||
}
|
||||
|
||||
impl TypeContext {
|
||||
pub fn new() -> Self {
|
||||
Self { defined_types: HashMap::new(), type_id_store: IdStore::new() }
|
||||
}
|
||||
|
||||
pub fn register_type(&mut self, builder: TypeBuilder) -> TypeId {
|
||||
let type_id = self.type_id_store.fresh();
|
||||
|
||||
let mut pending_variants = vec![];
|
||||
for variant_builder in builder.variants.into_iter() {
|
||||
let members = variant_builder.members;
|
||||
if members.is_empty() {
|
||||
pending_variants.push(Variant { name: variant_builder.name, members: VariantMembers::Unit });
|
||||
continue;
|
||||
}
|
||||
let record_variant = matches!(members.get(0).unwrap(), VariantMemberBuilder::KeyVal(..));
|
||||
|
||||
if record_variant {
|
||||
let pending_members = members.into_iter().map(|var| match var {
|
||||
VariantMemberBuilder::KeyVal(name, ty) => (name, ty),
|
||||
_ => panic!("Compiler internal error: variant mismatch"),
|
||||
});
|
||||
|
||||
//TODO make this mapping meaningful
|
||||
let type_ids = pending_members
|
||||
.into_iter()
|
||||
.map(|(name, _ty_id)| (name, self.type_id_store.fresh()))
|
||||
.collect();
|
||||
pending_variants
|
||||
.push(Variant { name: variant_builder.name, members: VariantMembers::Record(type_ids) });
|
||||
} else {
|
||||
let pending_members = members.into_iter().map(|var| match var {
|
||||
VariantMemberBuilder::Pending(pending_type) => pending_type,
|
||||
_ => panic!("Compiler internal error: variant mismatch"),
|
||||
});
|
||||
|
||||
//TODO make this mapping meaningful
|
||||
let type_ids = pending_members.into_iter().map(|_ty_id| self.type_id_store.fresh()).collect();
|
||||
|
||||
pending_variants
|
||||
.push(Variant { name: variant_builder.name, members: VariantMembers::Tuple(type_ids) });
|
||||
}
|
||||
}
|
||||
|
||||
// Eventually, I will want to have a better way of determining which numeric tag goes with
|
||||
// which variant. For now, just sort them alphabetically.
|
||||
pending_variants.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
let defined = DefinedType { name: builder.name, variants: pending_variants };
|
||||
|
||||
self.defined_types.insert(type_id, defined);
|
||||
type_id
|
||||
}
|
||||
|
||||
pub fn variant_local_name(&self, type_id: &TypeId, tag: u32) -> Option<&str> {
|
||||
self.defined_types
|
||||
.get(type_id)
|
||||
.and_then(|defined| defined.variants.get(tag as usize))
|
||||
.map(|variant| variant.name.as_ref())
|
||||
}
|
||||
|
||||
pub fn lookup_variant_arity(&self, type_id: &TypeId, tag: u32) -> Option<u32> {
|
||||
self.defined_types.get(type_id).and_then(|defined| defined.variants.get(tag as usize)).map(
|
||||
|variant| match &variant.members {
|
||||
VariantMembers::Unit => 0,
|
||||
VariantMembers::Tuple(items) => items.len() as u32,
|
||||
VariantMembers::Record(items) => items.len() as u32,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn lookup_record_members(&self, type_id: &TypeId, tag: u32) -> Option<&[(String, TypeId)]> {
|
||||
self.defined_types.get(type_id).and_then(|defined| defined.variants.get(tag as usize)).and_then(
|
||||
|variant| match &variant.members {
|
||||
VariantMembers::Record(items) => Some(items.as_ref()),
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn lookup_type(&self, type_id: &TypeId) -> Option<&DefinedType> {
|
||||
self.defined_types.get(type_id)
|
||||
}
|
||||
|
||||
//TODO return some kind of overall type later?
|
||||
pub fn typecheck(&mut self, ast: &AST) -> Result<(), TypeError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A type defined in program source code, as opposed to a builtin.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct DefinedType {
|
||||
pub name: String,
|
||||
|
||||
// the variants are in this list according to tag order
|
||||
pub variants: Vec<Variant>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Variant {
|
||||
pub name: String,
|
||||
pub members: VariantMembers,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VariantMembers {
|
||||
Unit,
|
||||
// Should be non-empty
|
||||
Tuple(Vec<TypeId>),
|
||||
Record(Vec<(String, TypeId)>),
|
||||
}
|
||||
|
||||
/// Represents a type mentioned as a member of another type during the type registration process.
|
||||
/// It may not have been registered itself in the relevant context.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct PendingType {
|
||||
inner: TypeIdentifier,
|
||||
}
|
||||
|
||||
impl From<&TypeIdentifier> for PendingType {
|
||||
fn from(type_identifier: &TypeIdentifier) -> Self {
|
||||
Self { inner: type_identifier.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TypeBuilder {
|
||||
name: String,
|
||||
variants: Vec<VariantBuilder>,
|
||||
}
|
||||
|
||||
impl TypeBuilder {
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self { name: name.to_string(), variants: vec![] }
|
||||
}
|
||||
|
||||
pub fn add_variant(&mut self, vb: VariantBuilder) {
|
||||
self.variants.push(vb);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VariantBuilder {
|
||||
name: String,
|
||||
members: Vec<VariantMemberBuilder>,
|
||||
}
|
||||
|
||||
impl VariantBuilder {
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self { name: name.to_string(), members: vec![] }
|
||||
}
|
||||
|
||||
pub fn add_member(&mut self, member_ty: PendingType) {
|
||||
self.members.push(VariantMemberBuilder::Pending(member_ty));
|
||||
}
|
||||
|
||||
// You can't call this and `add_member` on the same fn, there should be a runtime error when
|
||||
// that's detected.
|
||||
pub fn add_record_member(&mut self, name: &str, ty: PendingType) {
|
||||
self.members.push(VariantMemberBuilder::KeyVal(name.to_string(), ty));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum VariantMemberBuilder {
|
||||
Pending(PendingType),
|
||||
KeyVal(String, PendingType),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeError {
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TypeConst {
|
||||
Unit,
|
||||
Nat,
|
||||
Int,
|
||||
Float,
|
||||
StringT,
|
||||
Bool,
|
||||
Ordering,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Type {
|
||||
Const(TypeConst),
|
||||
//Var(TypeVar),
|
||||
Arrow { params: Vec<Type>, ret: Box<Type> },
|
||||
Compound { ty_name: String, args: Vec<Type> },
|
||||
}
|
||||
|
||||
macro_rules! ty {
|
||||
($type_name:ident) => {
|
||||
Type::Const(crate::type_inference::TypeConst::$type_name)
|
||||
};
|
||||
($t1:ident -> $t2:ident) => {
|
||||
Type::Arrow { params: vec![ty!($t1)], ret: Box::new(ty!($t2)) }
|
||||
};
|
||||
($t1:ident -> $t2:ident -> $t3:ident) => {
|
||||
Type::Arrow { params: vec![ty!($t1), ty!($t2)], ret: Box::new(ty!($t3)) }
|
||||
};
|
||||
($type_list:ident, $ret_type:ident) => {
|
||||
Type::Arrow { params: $type_list, ret: Box::new($ret_type) }
|
||||
};
|
||||
}
|
@ -1,522 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
use ena::unify::{UnifyKey, InPlaceUnificationTable, UnificationTable, EqUnifyValue};
|
||||
|
||||
use crate::builtin::Builtin;
|
||||
use crate::ast::*;
|
||||
use crate::util::ScopeStack;
|
||||
use crate::util::deref_optional_box;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TypeData {
|
||||
ty: Option<Type>
|
||||
}
|
||||
|
||||
impl TypeData {
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> TypeData {
|
||||
TypeData { ty: None }
|
||||
}
|
||||
}
|
||||
|
||||
//TODO need to hook this into the actual typechecking system somehow
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeId {
|
||||
local_name: Rc<String>
|
||||
}
|
||||
|
||||
impl TypeId {
|
||||
//TODO this is definitely incomplete
|
||||
pub fn lookup_name(name: &str) -> TypeId {
|
||||
TypeId {
|
||||
local_name: Rc::new(name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn local_name(&self) -> &str {
|
||||
self.local_name.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TypeId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TypeId:{}", self.local_name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct TypeContext<'a> {
|
||||
variable_map: ScopeStack<'a, Rc<String>, Type>,
|
||||
unification_table: InPlaceUnificationTable<TypeVar>,
|
||||
}
|
||||
|
||||
/// `InferResult` is the monad in which type inference takes place.
|
||||
type InferResult<T> = Result<T, TypeError>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeError { pub msg: String }
|
||||
|
||||
impl TypeError {
|
||||
fn new<A, T>(msg: T) -> InferResult<A> where T: Into<String> {
|
||||
Err(TypeError { msg: msg.into() })
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // avoids warning from Compound
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Type {
|
||||
Const(TypeConst),
|
||||
Var(TypeVar),
|
||||
Arrow {
|
||||
params: Vec<Type>,
|
||||
ret: Box<Type>
|
||||
},
|
||||
Compound {
|
||||
ty_name: String,
|
||||
args:Vec<Type>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct TypeVar(usize);
|
||||
|
||||
impl UnifyKey for TypeVar {
|
||||
type Value = Option<TypeConst>;
|
||||
fn index(&self) -> u32 { self.0 as u32 }
|
||||
fn from_index(u: u32) -> TypeVar { TypeVar(u as usize) }
|
||||
fn tag() -> &'static str { "TypeVar" }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TypeConst {
|
||||
Unit,
|
||||
Nat,
|
||||
Int,
|
||||
Float,
|
||||
StringT,
|
||||
Bool,
|
||||
Ordering,
|
||||
//UserDefined
|
||||
}
|
||||
|
||||
impl TypeConst {
|
||||
/*
|
||||
#[allow(dead_code)]
|
||||
pub fn to_string(&self) -> String {
|
||||
use self::TypeConst::*;
|
||||
match self {
|
||||
Unit => "()".to_string(),
|
||||
Nat => "Nat".to_string(),
|
||||
Int => "Int".to_string(),
|
||||
Float => "Float".to_string(),
|
||||
StringT => "String".to_string(),
|
||||
Bool => "Bool".to_string(),
|
||||
Ordering => "Ordering".to_string(),
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl EqUnifyValue for TypeConst { }
|
||||
|
||||
macro_rules! ty {
|
||||
($type_name:ident) => { Type::Const(TypeConst::$type_name) };
|
||||
($t1:ident -> $t2:ident) => { Type::Arrow { params: vec![ty!($t1)], ret: box ty!($t2) } };
|
||||
($t1:ident -> $t2:ident -> $t3:ident) => { Type::Arrow { params: vec![ty!($t1), ty!($t2)], ret: box ty!($t3) } };
|
||||
($type_list:ident, $ret_type:ident) => {
|
||||
Type::Arrow {
|
||||
params: $type_list,
|
||||
ret: box $ret_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO find a better way to capture the to/from string logic
|
||||
impl Type {
|
||||
/*
|
||||
#[allow(dead_code)]
|
||||
pub fn to_string(&self) -> String {
|
||||
use self::Type::*;
|
||||
match self {
|
||||
Const(c) => c.to_string(),
|
||||
Var(v) => format!("t_{}", v.0),
|
||||
Arrow { params, box ref ret } => {
|
||||
if params.is_empty() {
|
||||
format!("-> {}", ret.to_string())
|
||||
} else {
|
||||
let mut buf = String::new();
|
||||
for p in params.iter() {
|
||||
write!(buf, "{} -> ", p.to_string()).unwrap();
|
||||
}
|
||||
write!(buf, "{}", ret.to_string()).unwrap();
|
||||
buf
|
||||
}
|
||||
},
|
||||
Compound { .. } => "<some compound type>".to_string()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
fn from_string(string: &str) -> Option<Type> {
|
||||
Some(match string {
|
||||
"()" | "Unit" => ty!(Unit),
|
||||
"Nat" => ty!(Nat),
|
||||
"Int" => ty!(Int),
|
||||
"Float" => ty!(Float),
|
||||
"String" => ty!(StringT),
|
||||
"Bool" => ty!(Bool),
|
||||
"Ordering" => ty!(Ordering),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// `Type` is parameterized by whether the type variables can be just universal, or universal or
|
||||
/// existential.
|
||||
#[derive(Debug, Clone)]
|
||||
enum Type<A> {
|
||||
Var(A),
|
||||
Const(TConst),
|
||||
Arrow(Box<Type<A>>, Box<Type<A>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum TVar {
|
||||
Univ(UVar),
|
||||
Exist(ExistentialVar)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct UVar(Rc<String>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExistentialVar(u32);
|
||||
|
||||
impl Type<UVar> {
|
||||
fn to_tvar(&self) -> Type<TVar> {
|
||||
match self {
|
||||
Type::Var(UVar(name)) => Type::Var(TVar::Univ(UVar(name.clone()))),
|
||||
Type::Const(ref c) => Type::Const(c.clone()),
|
||||
Type::Arrow(a, b) => Type::Arrow(
|
||||
Box::new(a.to_tvar()),
|
||||
Box::new(b.to_tvar())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Type<TVar> {
|
||||
fn skolemize(&self) -> Type<UVar> {
|
||||
match self {
|
||||
Type::Var(TVar::Univ(uvar)) => Type::Var(uvar.clone()),
|
||||
Type::Var(TVar::Exist(_)) => Type::Var(UVar(Rc::new(format!("sk")))),
|
||||
Type::Const(ref c) => Type::Const(c.clone()),
|
||||
Type::Arrow(a, b) => Type::Arrow(
|
||||
Box::new(a.skolemize()),
|
||||
Box::new(b.skolemize())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeIdentifier {
|
||||
fn to_monotype(&self) -> Type<UVar> {
|
||||
match self {
|
||||
TypeIdentifier::Tuple(_) => Type::Const(TConst::Nat),
|
||||
TypeIdentifier::Singleton(TypeSingletonName { name, .. }) => {
|
||||
match &name[..] {
|
||||
"Nat" => Type::Const(TConst::Nat),
|
||||
"Int" => Type::Const(TConst::Int),
|
||||
"Float" => Type::Const(TConst::Float),
|
||||
"Bool" => Type::Const(TConst::Bool),
|
||||
"String" => Type::Const(TConst::StringT),
|
||||
_ => Type::Const(TConst::Nat),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum TConst {
|
||||
User(Rc<String>),
|
||||
Unit,
|
||||
Nat,
|
||||
Int,
|
||||
Float,
|
||||
StringT,
|
||||
Bool,
|
||||
}
|
||||
|
||||
impl TConst {
|
||||
fn user(name: &str) -> TConst {
|
||||
TConst::User(Rc::new(name.to_string()))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl<'a> TypeContext<'a> {
|
||||
pub fn new() -> TypeContext<'a> {
|
||||
TypeContext {
|
||||
variable_map: ScopeStack::new(None),
|
||||
unification_table: UnificationTable::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn new_env(&'a self, new_var: Rc<String>, ty: Type) -> TypeContext<'a> {
|
||||
let mut new_context = TypeContext {
|
||||
variable_map: self.variable_map.new_scope(None),
|
||||
unification_table: UnificationTable::new(), //???? not sure if i want this
|
||||
};
|
||||
|
||||
new_context.variable_map.insert(new_var, ty);
|
||||
new_context
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
fn get_type_from_name(&self, name: &TypeIdentifier) -> InferResult<Type> {
|
||||
use self::TypeIdentifier::*;
|
||||
Ok(match name {
|
||||
Singleton(TypeSingletonName { name,.. }) => {
|
||||
match Type::from_string(name) {
|
||||
Some(ty) => ty,
|
||||
None => return TypeError::new(format!("Unknown type name: {}", name))
|
||||
}
|
||||
},
|
||||
Tuple(_) => return TypeError::new("tuples aren't ready yet"),
|
||||
})
|
||||
}
|
||||
|
||||
/// `typecheck` is the entry into the type-inference system, accepting an AST as an argument
|
||||
/// Following the example of GHC, the compiler deliberately does typechecking before de-sugaring
|
||||
/// the AST to ReducedAST
|
||||
pub fn typecheck(&mut self, ast: &AST) -> Result<Type, TypeError> {
|
||||
let mut returned_type = Type::Const(TypeConst::Unit);
|
||||
for statement in ast.statements.statements.iter() {
|
||||
returned_type = self.statement(statement)?;
|
||||
}
|
||||
Ok(returned_type)
|
||||
}
|
||||
|
||||
fn statement(&mut self, statement: &Statement) -> InferResult<Type> {
|
||||
match &statement.kind {
|
||||
StatementKind::Expression(e) => self.expr(e),
|
||||
StatementKind::Declaration(decl) => self.decl(decl),
|
||||
StatementKind::Import(_) => Ok(ty!(Unit)),
|
||||
StatementKind::Module(_) => Ok(ty!(Unit)),
|
||||
}
|
||||
}
|
||||
|
||||
fn decl(&mut self, decl: &Declaration) -> InferResult<Type> {
|
||||
use self::Declaration::*;
|
||||
if let Binding { name, expr, .. } = decl {
|
||||
let ty = self.expr(expr)?;
|
||||
self.variable_map.insert(name.clone(), ty);
|
||||
}
|
||||
Ok(ty!(Unit))
|
||||
}
|
||||
|
||||
fn invoc(&mut self, invoc: &InvocationArgument) -> InferResult<Type> {
|
||||
use InvocationArgument::*;
|
||||
match invoc {
|
||||
Positional(expr) => self.expr(expr),
|
||||
_ => Ok(ty!(Nat)) //TODO this is wrong
|
||||
}
|
||||
}
|
||||
|
||||
fn expr(&mut self, expr: &Expression) -> InferResult<Type> {
|
||||
match expr {
|
||||
Expression { kind, type_anno: Some(anno), .. } => {
|
||||
let t1 = self.expr_type(kind)?;
|
||||
let t2 = self.get_type_from_name(anno)?;
|
||||
self.unify(t2, t1)
|
||||
},
|
||||
Expression { kind, type_anno: None, .. } => self.expr_type(kind)
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_type(&mut self, expr: &ExpressionKind) -> InferResult<Type> {
|
||||
use self::ExpressionKind::*;
|
||||
Ok(match expr {
|
||||
NatLiteral(_) => ty!(Nat),
|
||||
BoolLiteral(_) => ty!(Bool),
|
||||
FloatLiteral(_) => ty!(Float),
|
||||
StringLiteral(_) => ty!(StringT),
|
||||
PrefixExp(op, expr) => self.prefix(op, expr)?,
|
||||
BinExp(op, lhs, rhs) => self.binexp(op, lhs, rhs)?,
|
||||
IfExpression { discriminator, body } => self.if_expr(deref_optional_box(discriminator), &**body)?,
|
||||
Value(val) => self.handle_value(val)?,
|
||||
Call { box ref f, arguments } => self.call(f, arguments)?,
|
||||
Lambda { params, type_anno, body } => self.lambda(params, type_anno, body)?,
|
||||
_ => ty!(Unit),
|
||||
})
|
||||
}
|
||||
|
||||
fn prefix(&mut self, op: &PrefixOp, expr: &Expression) -> InferResult<Type> {
|
||||
let builtin: Option<Builtin> = TryFrom::try_from(op).ok();
|
||||
let tf = match builtin.map(|b| b.get_type()) {
|
||||
Some(ty) => ty,
|
||||
None => return TypeError::new("no type found")
|
||||
};
|
||||
|
||||
let tx = self.expr(expr)?;
|
||||
self.handle_apply(tf, vec![tx])
|
||||
}
|
||||
|
||||
fn binexp(&mut self, op: &BinOp, lhs: &Expression, rhs: &Expression) -> InferResult<Type> {
|
||||
let builtin: Option<Builtin> = TryFrom::try_from(op).ok();
|
||||
let tf = match builtin.map(|b| b.get_type()) {
|
||||
Some(ty) => ty,
|
||||
None => return TypeError::new("no type found"),
|
||||
};
|
||||
|
||||
let t_lhs = self.expr(lhs)?;
|
||||
let t_rhs = self.expr(rhs)?; //TODO is this order a problem? not sure
|
||||
|
||||
self.handle_apply(tf, vec![t_lhs, t_rhs])
|
||||
}
|
||||
|
||||
fn if_expr(&mut self, discriminator: Option<&Expression>, body: &IfExpressionBody) -> InferResult<Type> {
|
||||
use self::IfExpressionBody::*;
|
||||
match (discriminator, body) {
|
||||
(Some(expr), SimpleConditional{ then_case, else_case }) => self.handle_simple_if(expr, then_case, else_case),
|
||||
_ => TypeError::new("Complex conditionals not supported".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn handle_simple_if(&mut self, expr: &Expression, then_clause: &Block, else_clause: &Option<Block>) -> InferResult<Type> {
|
||||
let t1 = self.expr(expr)?;
|
||||
let t2 = self.block(then_clause)?;
|
||||
let t3 = match else_clause {
|
||||
Some(block) => self.block(block)?,
|
||||
None => ty!(Unit)
|
||||
};
|
||||
|
||||
let _ = self.unify(ty!(Bool), t1)?;
|
||||
self.unify(t2, t3)
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn lambda(&mut self, params: &Vec<FormalParam>, type_anno: &Option<TypeIdentifier>, _body: &Block) -> InferResult<Type> {
|
||||
let argument_types: InferResult<Vec<Type>> = params.iter().map(|param: &FormalParam| {
|
||||
if let FormalParam { anno: Some(type_identifier), .. } = param {
|
||||
self.get_type_from_name(type_identifier)
|
||||
} else {
|
||||
Ok(Type::Var(self.fresh_type_variable()))
|
||||
}
|
||||
}).collect();
|
||||
let argument_types = argument_types?;
|
||||
let ret_type = match type_anno.as_ref() {
|
||||
Some(anno) => self.get_type_from_name(anno)?,
|
||||
None => Type::Var(self.fresh_type_variable())
|
||||
};
|
||||
|
||||
Ok(ty!(argument_types, ret_type))
|
||||
}
|
||||
|
||||
fn call(&mut self, f: &Expression, args: &[ InvocationArgument ]) -> InferResult<Type> {
|
||||
let tf = self.expr(f)?;
|
||||
let arg_types: InferResult<Vec<Type>> = args.iter().map(|ex| self.invoc(ex)).collect();
|
||||
let arg_types = arg_types?;
|
||||
self.handle_apply(tf, arg_types)
|
||||
}
|
||||
|
||||
fn handle_apply(&mut self, tf: Type, args: Vec<Type>) -> InferResult<Type> {
|
||||
Ok(match tf {
|
||||
Type::Arrow { ref params, ret: box ref t_ret } if params.len() == args.len() => {
|
||||
for (t_param, t_arg) in params.iter().zip(args.iter()) {
|
||||
let _ = self.unify(t_param.clone(), t_arg.clone())?; //TODO I think this needs to reference a sub-scope
|
||||
}
|
||||
t_ret.clone()
|
||||
},
|
||||
Type::Arrow { .. } => return TypeError::new("Wrong length"),
|
||||
_ => return TypeError::new("Not a function".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn block(&mut self, block: &Block) -> InferResult<Type> {
|
||||
let mut output = ty!(Unit);
|
||||
for statement in block.statements.iter() {
|
||||
output = self.statement(statement)?;
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn handle_value(&mut self, val: &QualifiedName) -> InferResult<Type> {
|
||||
let QualifiedName { components: vec, .. } = val;
|
||||
let var = &vec[0];
|
||||
match self.variable_map.lookup(var) {
|
||||
Some(ty) => Ok(ty.clone()),
|
||||
None => TypeError::new(format!("Couldn't find variable: {}", &var)),
|
||||
}
|
||||
}
|
||||
|
||||
fn unify(&mut self, t1: Type, t2: Type) -> InferResult<Type> {
|
||||
use self::Type::*;
|
||||
|
||||
match (t1, t2) {
|
||||
(Const(ref c1), Const(ref c2)) if c1 == c2 => Ok(Const(c1.clone())), //choice of c1 is arbitrary I *think*
|
||||
(a @ Var(_), b @ Const(_)) => self.unify(b, a),
|
||||
(Const(ref c1), Var(ref v2)) => {
|
||||
self.unification_table.unify_var_value(*v2, Some(c1.clone()))
|
||||
.or_else(|_| TypeError::new(format!("Couldn't unify {:?} and {:?}", Const(c1.clone()), Var(*v2))))?;
|
||||
Ok(Const(c1.clone()))
|
||||
},
|
||||
(Var(v1), Var(v2)) => {
|
||||
//TODO add occurs check
|
||||
self.unification_table.unify_var_var(v1, v2)
|
||||
.or_else(|e| {
|
||||
println!("Unify error: {:?}", e);
|
||||
TypeError::new(format!("Two type variables {:?} and {:?} couldn't unify", v1, v2))
|
||||
})?;
|
||||
Ok(Var(v1)) //arbitrary decision I think
|
||||
},
|
||||
(a, b) => TypeError::new(format!("{:?} and {:?} do not unify", a, b)),
|
||||
}
|
||||
}
|
||||
|
||||
fn fresh_type_variable(&mut self) -> TypeVar {
|
||||
|
||||
self.unification_table.new_key(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod typechecking_tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! assert_type_in_fresh_context {
|
||||
($string:expr, $type:expr) => {
|
||||
let mut tc = TypeContext::new();
|
||||
let ast = &crate::util::quick_ast($string);
|
||||
let ty = tc.typecheck(ast).unwrap();
|
||||
assert_eq!(ty, $type)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_test() {
|
||||
assert_type_in_fresh_context!("1", ty!(Nat));
|
||||
assert_type_in_fresh_context!(r#""drugs""#, ty!(StringT));
|
||||
assert_type_in_fresh_context!("true", ty!(Bool));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operators() {
|
||||
//TODO fix these with new operator regime
|
||||
/*
|
||||
assert_type_in_fresh_context!("-1", ty!(Int));
|
||||
assert_type_in_fresh_context!("1 + 2", ty!(Nat));
|
||||
assert_type_in_fresh_context!("-2", ty!(Int));
|
||||
assert_type_in_fresh_context!("!true", ty!(Bool));
|
||||
*/
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
use std::{cmp::Eq, collections::HashMap, fmt::Write, hash::Hash};
|
||||
|
||||
/// Utility function for printing a comma-delimited list of things
|
||||
pub(crate) fn delim_wrapped(lhs: char, rhs: char, terms: impl Iterator<Item = String>) -> String {
|
||||
let mut buf = String::new();
|
||||
write!(buf, "{}", lhs).unwrap();
|
||||
for term in terms.map(Some).intersperse(None) {
|
||||
match term {
|
||||
Some(e) => write!(buf, "{}", e).unwrap(),
|
||||
None => write!(buf, ", ").unwrap(),
|
||||
};
|
||||
}
|
||||
write!(buf, "{}", rhs).unwrap();
|
||||
buf
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ScopeStack<'a, T: 'a, V: 'a, N = String>
|
||||
where T: Hash + Eq
|
||||
{
|
||||
parent: Option<&'a ScopeStack<'a, T, V, N>>,
|
||||
values: HashMap<T, V>,
|
||||
scope_name: Option<N>,
|
||||
}
|
||||
|
||||
impl<'a, T, V, N> ScopeStack<'a, T, V, N>
|
||||
where T: Hash + Eq
|
||||
{
|
||||
pub fn new(scope_name: Option<N>) -> Self
|
||||
where T: Hash + Eq {
|
||||
ScopeStack { parent: None, values: HashMap::new(), scope_name }
|
||||
}
|
||||
pub fn insert(&mut self, key: T, value: V)
|
||||
where T: Hash + Eq {
|
||||
self.values.insert(key, value);
|
||||
}
|
||||
pub fn lookup(&self, key: &T) -> Option<&V>
|
||||
where T: Hash + Eq {
|
||||
match (self.values.get(key), self.parent) {
|
||||
(None, None) => None,
|
||||
(None, Some(parent)) => parent.lookup(key),
|
||||
(Some(value), _) => Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_scope(&'a self, scope_name: Option<N>) -> Self
|
||||
where T: Hash + Eq {
|
||||
ScopeStack { parent: Some(self), values: HashMap::default(), scope_name }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn lookup_with_scope(&self, key: &T) -> Option<(&V, Option<&N>)>
|
||||
where T: Hash + Eq {
|
||||
match (self.values.get(key), self.parent) {
|
||||
(None, None) => None,
|
||||
(None, Some(parent)) => parent.lookup_with_scope(key),
|
||||
(Some(value), _) => Some((value, self.scope_name.as_ref())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> Option<&N> {
|
||||
self.scope_name.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Quickly create an AST from a string, with no error checking. For test use only
|
||||
#[cfg(test)]
|
||||
pub fn quick_ast(input: &str) -> crate::ast::AST {
|
||||
let mut parser = crate::parsing::Parser::new();
|
||||
let output = parser.parse(input);
|
||||
match output {
|
||||
Ok(output) => output,
|
||||
Err(err) => {
|
||||
println!("Parse error: {}", err.msg);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! rc {
|
||||
($string:tt) => {
|
||||
Rc::new(stringify!($string).to_string())
|
||||
};
|
||||
}
|
BIN
schala-logo.ico
Normal file
BIN
schala-logo.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
8
schala-main/Cargo.toml
Normal file
8
schala-main/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "schala-main"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
3
schala-main/src/main.rs
Normal file
3
schala-main/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Schala");
|
||||
}
|
8
schala-parser/Cargo.toml
Normal file
8
schala-parser/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "schala-parser"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
3
schala-parser/src/main.rs
Normal file
3
schala-parser/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "schala-repl"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
llvm-sys = "70.0.2"
|
||||
take_mut = "0.2.2"
|
||||
itertools = "0.10"
|
||||
lazy_static = "0.2.8"
|
||||
maplit = "*"
|
||||
colored = "1.8"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
phf = "0.7.12"
|
||||
includedir = "0.2.0"
|
||||
linefeed = "0.6.0"
|
||||
regex = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
includedir_codegen = "0.2.0"
|
@ -1,7 +0,0 @@
|
||||
extern crate includedir_codegen;
|
||||
|
||||
use includedir_codegen::Compression;
|
||||
|
||||
fn main() {
|
||||
includedir_codegen::start("WEBFILES").dir("../static", Compression::Gzip).build("static.rs").unwrap();
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
use colored::*;
|
||||
|
||||
use crate::{
|
||||
directive_actions::DirectiveAction, language::ProgrammingLanguageInterface, InterpreterDirectiveOutput,
|
||||
Repl,
|
||||
};
|
||||
|
||||
/// A CommandTree is either a `Terminal` or a `NonTerminal`. When command parsing reaches the first
|
||||
/// Terminal, it will use the `DirectiveAction` found there to find an appropriate function to execute,
|
||||
/// and then execute it with any remaining arguments
|
||||
#[derive(Clone)]
|
||||
pub enum CommandTree {
|
||||
Terminal {
|
||||
name: String,
|
||||
children: Vec<CommandTree>,
|
||||
help_msg: Option<String>,
|
||||
action: DirectiveAction,
|
||||
},
|
||||
NonTerminal {
|
||||
name: String,
|
||||
children: Vec<CommandTree>,
|
||||
help_msg: Option<String>,
|
||||
action: DirectiveAction,
|
||||
},
|
||||
Top(Vec<CommandTree>),
|
||||
}
|
||||
|
||||
impl CommandTree {
|
||||
pub fn nonterm_no_further_tab_completions(s: &str, help: Option<&str>) -> CommandTree {
|
||||
CommandTree::NonTerminal {
|
||||
name: s.to_string(),
|
||||
help_msg: help.map(|x| x.to_string()),
|
||||
children: vec![],
|
||||
action: DirectiveAction::Null,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terminal(
|
||||
s: &str,
|
||||
help: Option<&str>,
|
||||
children: Vec<CommandTree>,
|
||||
action: DirectiveAction,
|
||||
) -> CommandTree {
|
||||
CommandTree::Terminal { name: s.to_string(), help_msg: help.map(|x| x.to_string()), children, action }
|
||||
}
|
||||
|
||||
pub fn nonterm(s: &str, help: Option<&str>, children: Vec<CommandTree>) -> CommandTree {
|
||||
CommandTree::NonTerminal {
|
||||
name: s.to_string(),
|
||||
help_msg: help.map(|x| x.to_string()),
|
||||
children,
|
||||
action: DirectiveAction::Null,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cmd(&self) -> &str {
|
||||
match self {
|
||||
CommandTree::Terminal { name, .. } => name.as_str(),
|
||||
CommandTree::NonTerminal { name, .. } => name.as_str(),
|
||||
CommandTree::Top(_) => "",
|
||||
}
|
||||
}
|
||||
pub fn get_help(&self) -> &str {
|
||||
match self {
|
||||
CommandTree::Terminal { help_msg, .. } =>
|
||||
help_msg.as_ref().map(|s| s.as_str()).unwrap_or("<no help text provided>"),
|
||||
CommandTree::NonTerminal { help_msg, .. } =>
|
||||
help_msg.as_ref().map(|s| s.as_str()).unwrap_or("<no help text provided>"),
|
||||
CommandTree::Top(_) => "",
|
||||
}
|
||||
}
|
||||
pub fn get_children(&self) -> &Vec<CommandTree> {
|
||||
use CommandTree::*;
|
||||
match self {
|
||||
Terminal { children, .. } | NonTerminal { children, .. } | Top(children) => children,
|
||||
}
|
||||
}
|
||||
pub fn get_subcommands(&self) -> Vec<&str> {
|
||||
self.get_children().iter().map(|x| x.get_cmd()).collect()
|
||||
}
|
||||
|
||||
pub fn perform<L: ProgrammingLanguageInterface>(
|
||||
&self,
|
||||
repl: &mut Repl<L>,
|
||||
arguments: &[&str],
|
||||
) -> InterpreterDirectiveOutput {
|
||||
let mut dir_pointer: &CommandTree = self;
|
||||
let mut idx = 0;
|
||||
|
||||
let res: Result<(DirectiveAction, usize), String> = loop {
|
||||
match dir_pointer {
|
||||
CommandTree::Top(subcommands) | CommandTree::NonTerminal { children: subcommands, .. } => {
|
||||
let next_command = match arguments.get(idx) {
|
||||
Some(cmd) => cmd,
|
||||
None => break Err("Command requires arguments".to_owned()),
|
||||
};
|
||||
idx += 1;
|
||||
match subcommands.iter().find(|sc| sc.get_cmd() == *next_command) {
|
||||
Some(command_tree) => {
|
||||
dir_pointer = command_tree;
|
||||
}
|
||||
None => break Err(format!("Command {} not found", next_command)),
|
||||
};
|
||||
}
|
||||
CommandTree::Terminal { action, .. } => {
|
||||
break Ok((action.clone(), idx));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok((action, idx)) => action.perform(repl, &arguments[idx..]),
|
||||
Err(err) => Some(err.red().to_string()),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
use std::fmt::Write as FmtWrite;
|
||||
|
||||
use crate::{
|
||||
help::help,
|
||||
language::{LangMetaRequest, LangMetaResponse, ProgrammingLanguageInterface},
|
||||
InterpreterDirectiveOutput, Repl,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DirectiveAction {
|
||||
Null,
|
||||
Help,
|
||||
QuitProgram,
|
||||
ListPasses,
|
||||
TotalTime(bool),
|
||||
StageTime(bool),
|
||||
Doc,
|
||||
}
|
||||
|
||||
impl DirectiveAction {
|
||||
pub fn perform<L: ProgrammingLanguageInterface>(
|
||||
&self,
|
||||
repl: &mut Repl<L>,
|
||||
arguments: &[&str],
|
||||
) -> InterpreterDirectiveOutput {
|
||||
use DirectiveAction::*;
|
||||
match self {
|
||||
Null => None,
|
||||
Help => help(repl, arguments),
|
||||
QuitProgram => {
|
||||
repl.save_before_exit();
|
||||
::std::process::exit(0)
|
||||
}
|
||||
ListPasses => {
|
||||
let pass_names = match repl.language_state.request_meta(LangMetaRequest::StageNames) {
|
||||
LangMetaResponse::StageNames(names) => names,
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
for pass in pass_names.iter().map(Some).intersperse(None) {
|
||||
match pass {
|
||||
Some(pass) => write!(buf, "{}", pass).unwrap(),
|
||||
None => write!(buf, " -> ").unwrap(),
|
||||
}
|
||||
}
|
||||
Some(buf)
|
||||
}
|
||||
TotalTime(value) => {
|
||||
repl.options.show_total_time = *value;
|
||||
None
|
||||
}
|
||||
StageTime(value) => {
|
||||
repl.options.show_stage_times = *value;
|
||||
None
|
||||
}
|
||||
Doc => doc(repl, arguments),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn doc<L: ProgrammingLanguageInterface>(
|
||||
repl: &mut Repl<L>,
|
||||
arguments: &[&str],
|
||||
) -> InterpreterDirectiveOutput {
|
||||
arguments
|
||||
.get(0)
|
||||
.map(|cmd| {
|
||||
let source = cmd.to_string();
|
||||
let meta = LangMetaRequest::Docs { source };
|
||||
match repl.language_state.request_meta(meta) {
|
||||
LangMetaResponse::Docs { doc_string } => Some(doc_string),
|
||||
_ => Some("Invalid doc response".to_owned()),
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| Some(":docs needs an argument".to_owned()))
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
use crate::{command_tree::CommandTree, directive_actions::DirectiveAction};
|
||||
|
||||
pub fn directives_from_pass_names(pass_names: &[String]) -> CommandTree {
|
||||
let passes_directives: Vec<CommandTree> = pass_names
|
||||
.iter()
|
||||
.map(|pass_name| {
|
||||
if pass_name == "parsing" {
|
||||
CommandTree::nonterm(
|
||||
pass_name,
|
||||
None,
|
||||
vec![
|
||||
CommandTree::nonterm_no_further_tab_completions("compact", None),
|
||||
CommandTree::nonterm_no_further_tab_completions("expanded", None),
|
||||
CommandTree::nonterm_no_further_tab_completions("trace", None),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
CommandTree::nonterm_no_further_tab_completions(pass_name, None)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
CommandTree::Top(get_list(&passes_directives, true))
|
||||
}
|
||||
|
||||
fn get_list(passes_directives: &[CommandTree], include_help: bool) -> Vec<CommandTree> {
|
||||
use DirectiveAction::*;
|
||||
|
||||
vec![
|
||||
CommandTree::terminal("exit", Some("exit the REPL"), vec![], QuitProgram),
|
||||
//TODO there should be an alias for this
|
||||
CommandTree::terminal("quit", Some("exit the REPL"), vec![], QuitProgram),
|
||||
CommandTree::terminal(
|
||||
"help",
|
||||
Some("Print this help message"),
|
||||
if include_help { get_list(passes_directives, false) } else { vec![] },
|
||||
Help,
|
||||
),
|
||||
CommandTree::nonterm(
|
||||
"debug",
|
||||
Some("Configure debug information"),
|
||||
vec![
|
||||
CommandTree::terminal(
|
||||
"list-passes",
|
||||
Some("List all registered compiler passes"),
|
||||
vec![],
|
||||
ListPasses,
|
||||
),
|
||||
CommandTree::nonterm(
|
||||
"total-time",
|
||||
None,
|
||||
vec![
|
||||
CommandTree::terminal("on", None, vec![], TotalTime(true)),
|
||||
CommandTree::terminal("off", None, vec![], TotalTime(false)),
|
||||
],
|
||||
),
|
||||
CommandTree::nonterm(
|
||||
"stage-times",
|
||||
Some("Computation time per-stage"),
|
||||
vec![
|
||||
CommandTree::terminal("on", None, vec![], StageTime(true)),
|
||||
CommandTree::terminal("off", None, vec![], StageTime(false)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
CommandTree::terminal("doc", Some("Get language-specific help for an item"), vec![], Doc),
|
||||
]
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
use std::fmt::Write as FmtWrite;
|
||||
|
||||
use colored::*;
|
||||
|
||||
use crate::{
|
||||
command_tree::CommandTree, language::ProgrammingLanguageInterface, InterpreterDirectiveOutput, Repl,
|
||||
};
|
||||
|
||||
pub fn help<L: ProgrammingLanguageInterface>(
|
||||
repl: &mut Repl<L>,
|
||||
arguments: &[&str],
|
||||
) -> InterpreterDirectiveOutput {
|
||||
match arguments {
|
||||
[] => global_help(repl),
|
||||
commands => {
|
||||
let dirs = repl.get_directives();
|
||||
Some(match get_directive_from_commands(commands, &dirs) {
|
||||
None => format!("Directive `{}` not found", commands.last().unwrap()),
|
||||
Some(dir) => {
|
||||
let mut buf = String::new();
|
||||
let cmd = dir.get_cmd();
|
||||
let children = dir.get_children();
|
||||
writeln!(buf, "`{}` - {}", cmd, dir.get_help()).unwrap();
|
||||
for sub in children.iter() {
|
||||
writeln!(buf, "\t`{} {}` - {}", cmd, sub.get_cmd(), sub.get_help()).unwrap();
|
||||
}
|
||||
buf
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_directive_from_commands<'a>(commands: &[&str], dirs: &'a CommandTree) -> Option<&'a CommandTree> {
|
||||
let mut directive_list = dirs.get_children();
|
||||
let mut matched_directive = None;
|
||||
for cmd in commands {
|
||||
let found = directive_list.iter().find(|directive| directive.get_cmd() == *cmd);
|
||||
if let Some(dir) = found {
|
||||
directive_list = dir.get_children();
|
||||
}
|
||||
|
||||
matched_directive = found;
|
||||
}
|
||||
matched_directive
|
||||
}
|
||||
|
||||
fn global_help<L: ProgrammingLanguageInterface>(repl: &mut Repl<L>) -> InterpreterDirectiveOutput {
|
||||
let mut buf = String::new();
|
||||
|
||||
writeln!(buf, "{} version {}", "Schala REPL".bright_red().bold(), crate::VERSION_STRING).unwrap();
|
||||
writeln!(buf, "-----------------------").unwrap();
|
||||
|
||||
for directive in repl.get_directives().get_children() {
|
||||
writeln!(buf, "{}{} - {}", repl.sigil, directive.get_cmd(), directive.get_help()).unwrap();
|
||||
}
|
||||
|
||||
writeln!(buf).unwrap();
|
||||
writeln!(buf, "Language-specific help for {}", <L as ProgrammingLanguageInterface>::language_name())
|
||||
.unwrap();
|
||||
writeln!(buf, "-----------------------").unwrap();
|
||||
Some(buf)
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
use std::{collections::HashSet, time};
|
||||
|
||||
pub trait ProgrammingLanguageInterface {
|
||||
type Config: Default + Clone;
|
||||
fn language_name() -> String;
|
||||
fn source_file_suffix() -> String;
|
||||
|
||||
fn run_computation(&mut self, _request: ComputationRequest<Self::Config>) -> ComputationResponse;
|
||||
|
||||
fn request_meta(&mut self, _request: LangMetaRequest) -> LangMetaResponse {
|
||||
LangMetaResponse::Custom { kind: "not-implemented".to_owned(), value: format!("") }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ComputationRequest<'a, T> {
|
||||
pub source: &'a str,
|
||||
pub config: T,
|
||||
pub debug_requests: HashSet<DebugAsk>,
|
||||
}
|
||||
|
||||
pub struct ComputationResponse {
|
||||
pub main_output: Result<String, String>,
|
||||
pub global_output_stats: GlobalOutputStats,
|
||||
pub debug_responses: Vec<DebugResponse>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct GlobalOutputStats {
|
||||
pub total_duration: time::Duration,
|
||||
pub stage_durations: Vec<(String, time::Duration)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub enum DebugAsk {
|
||||
Timing,
|
||||
ByStage { stage_name: String, token: Option<String> },
|
||||
}
|
||||
|
||||
pub struct DebugResponse {
|
||||
pub ask: DebugAsk,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
pub enum LangMetaRequest {
|
||||
StageNames,
|
||||
Docs { source: String },
|
||||
Custom { kind: String, value: String },
|
||||
ImmediateDebug(DebugAsk),
|
||||
}
|
||||
|
||||
pub enum LangMetaResponse {
|
||||
StageNames(Vec<String>),
|
||||
Docs { doc_string: String },
|
||||
Custom { kind: String, value: String },
|
||||
ImmediateDebug(DebugResponse),
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
#![feature(box_patterns, proc_macro_hygiene, decl_macro, iter_intersperse)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate includedir;
|
||||
extern crate phf;
|
||||
extern crate serde_json;
|
||||
|
||||
mod command_tree;
|
||||
mod language;
|
||||
use self::command_tree::CommandTree;
|
||||
mod repl_options;
|
||||
use repl_options::ReplOptions;
|
||||
mod directive_actions;
|
||||
mod directives;
|
||||
use directives::directives_from_pass_names;
|
||||
mod help;
|
||||
mod response;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use colored::*;
|
||||
pub use language::{
|
||||
ComputationRequest, ComputationResponse, DebugAsk, DebugResponse, GlobalOutputStats, LangMetaRequest,
|
||||
LangMetaResponse, ProgrammingLanguageInterface,
|
||||
};
|
||||
use response::ReplResponse;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/static.rs"));
|
||||
const VERSION_STRING: &str = "0.1.0";
|
||||
|
||||
const HISTORY_SAVE_FILE: &str = ".schala_history";
|
||||
const OPTIONS_SAVE_FILE: &str = ".schala_repl";
|
||||
|
||||
type InterpreterDirectiveOutput = Option<String>;
|
||||
|
||||
pub struct Repl<L: ProgrammingLanguageInterface> {
|
||||
/// If this is the first character typed by a user into the repl, the following
|
||||
/// will be interpreted as a directive to the REPL rather than a command in the
|
||||
/// running programming language.
|
||||
sigil: char,
|
||||
line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>,
|
||||
language_state: L,
|
||||
options: ReplOptions,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum PromptStyle {
|
||||
Normal,
|
||||
Multiline,
|
||||
}
|
||||
|
||||
impl<L: ProgrammingLanguageInterface> Repl<L> {
|
||||
pub fn new(initial_state: L) -> Self {
|
||||
use linefeed::Interface;
|
||||
let line_reader = Interface::new("schala-repl").unwrap();
|
||||
let sigil = ':';
|
||||
|
||||
Repl { sigil, line_reader, language_state: initial_state, options: ReplOptions::new() }
|
||||
}
|
||||
|
||||
pub fn run_repl(&mut self, config: L::Config) {
|
||||
println!("Schala meta-interpeter version {}", VERSION_STRING);
|
||||
println!("Type {} for help with the REPL", format!("{}help", self.sigil).bright_green().bold());
|
||||
self.load_options();
|
||||
self.handle_repl_loop(config);
|
||||
self.save_before_exit();
|
||||
println!("Exiting...");
|
||||
}
|
||||
|
||||
fn load_options(&mut self) {
|
||||
self.line_reader.load_history(HISTORY_SAVE_FILE).unwrap_or(());
|
||||
match ReplOptions::load_from_file(OPTIONS_SAVE_FILE) {
|
||||
Ok(options) => {
|
||||
self.options = options;
|
||||
}
|
||||
Err(e) => eprintln!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_repl_loop(&mut self, config: L::Config) {
|
||||
use linefeed::ReadResult::*;
|
||||
|
||||
'main: loop {
|
||||
macro_rules! match_or_break {
|
||||
($line:expr) => {
|
||||
match $line {
|
||||
Err(e) => {
|
||||
println!("readline IO Error: {}", e);
|
||||
break 'main;
|
||||
}
|
||||
Ok(Eof) | Ok(Signal(_)) => break 'main,
|
||||
Ok(Input(ref input)) => input,
|
||||
}
|
||||
};
|
||||
}
|
||||
self.update_line_reader();
|
||||
let line = self.line_reader.read_line();
|
||||
let input: &str = match_or_break!(line);
|
||||
|
||||
self.line_reader.add_history_unique(input.to_string());
|
||||
let mut chars = input.chars().peekable();
|
||||
let repl_responses = match chars.next() {
|
||||
Some(ch) if ch == self.sigil =>
|
||||
if chars.peek() == Some(&'{') {
|
||||
let mut buf = String::new();
|
||||
buf.push_str(input.get(2..).unwrap());
|
||||
'multiline: loop {
|
||||
self.set_prompt(PromptStyle::Multiline);
|
||||
let new_line = self.line_reader.read_line();
|
||||
let new_input = match_or_break!(new_line);
|
||||
if new_input.starts_with(":}") {
|
||||
break 'multiline;
|
||||
} else {
|
||||
buf.push_str(new_input);
|
||||
buf.push('\n');
|
||||
}
|
||||
}
|
||||
self.handle_input(&buf, &config)
|
||||
} else {
|
||||
if let Some(output) = self.handle_interpreter_directive(input.get(1..).unwrap()) {
|
||||
println!("{}", output);
|
||||
}
|
||||
continue;
|
||||
},
|
||||
_ => self.handle_input(input, &config),
|
||||
};
|
||||
|
||||
for repl_response in repl_responses.iter() {
|
||||
println!("{}", repl_response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_line_reader(&mut self) {
|
||||
let tab_complete_handler = TabCompleteHandler::new(self.sigil, self.get_directives());
|
||||
self.line_reader.set_completer(Arc::new(tab_complete_handler)); //TODO fix this here
|
||||
self.set_prompt(PromptStyle::Normal);
|
||||
}
|
||||
|
||||
fn set_prompt(&mut self, prompt_style: PromptStyle) {
|
||||
let prompt_str = match prompt_style {
|
||||
PromptStyle::Normal => ">> ",
|
||||
PromptStyle::Multiline => ">| ",
|
||||
};
|
||||
|
||||
self.line_reader.set_prompt(prompt_str).unwrap();
|
||||
}
|
||||
|
||||
fn save_before_exit(&self) {
|
||||
self.line_reader.save_history(HISTORY_SAVE_FILE).unwrap_or(());
|
||||
self.options.save_to_file(OPTIONS_SAVE_FILE);
|
||||
}
|
||||
|
||||
fn handle_interpreter_directive(&mut self, input: &str) -> InterpreterDirectiveOutput {
|
||||
let arguments: Vec<&str> = input.split_whitespace().collect();
|
||||
|
||||
if arguments.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let directives = self.get_directives();
|
||||
directives.perform(self, &arguments)
|
||||
}
|
||||
|
||||
fn handle_input(&mut self, input: &str, config: &L::Config) -> Vec<ReplResponse> {
|
||||
let mut debug_requests = HashSet::new();
|
||||
for ask in self.options.debug_asks.iter() {
|
||||
debug_requests.insert(ask.clone());
|
||||
}
|
||||
|
||||
let request = ComputationRequest { source: input, config: config.clone(), debug_requests };
|
||||
let response = self.language_state.run_computation(request);
|
||||
response::handle_computation_response(response, &self.options)
|
||||
}
|
||||
|
||||
fn get_directives(&mut self) -> CommandTree {
|
||||
let pass_names = match self.language_state.request_meta(LangMetaRequest::StageNames) {
|
||||
LangMetaResponse::StageNames(names) => names,
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
directives_from_pass_names(&pass_names)
|
||||
}
|
||||
}
|
||||
|
||||
struct TabCompleteHandler {
|
||||
sigil: char,
|
||||
top_level_commands: CommandTree,
|
||||
}
|
||||
|
||||
use linefeed::{
|
||||
complete::{Completer, Completion},
|
||||
terminal::Terminal,
|
||||
};
|
||||
|
||||
impl TabCompleteHandler {
|
||||
fn new(sigil: char, top_level_commands: CommandTree) -> TabCompleteHandler {
|
||||
TabCompleteHandler { top_level_commands, sigil }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Terminal> Completer<T> for TabCompleteHandler {
|
||||
fn complete(
|
||||
&self,
|
||||
word: &str,
|
||||
prompter: &::linefeed::prompter::Prompter<T>,
|
||||
start: usize,
|
||||
_end: usize,
|
||||
) -> Option<Vec<Completion>> {
|
||||
let line = prompter.buffer();
|
||||
|
||||
if !line.starts_with(self.sigil) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut words = line[1..(if start == 0 { 1 } else { start })].split_whitespace();
|
||||
let mut completions = Vec::new();
|
||||
let mut command_tree: Option<&CommandTree> = Some(&self.top_level_commands);
|
||||
|
||||
loop {
|
||||
match words.next() {
|
||||
None => {
|
||||
let top = matches!(command_tree, Some(CommandTree::Top(_)));
|
||||
let word = if top { word.get(1..).unwrap() } else { word };
|
||||
for cmd in command_tree.map(|x| x.get_subcommands()).unwrap_or_default().into_iter() {
|
||||
if cmd.starts_with(word) {
|
||||
completions.push(Completion {
|
||||
completion: format!("{}{}", if top { ":" } else { "" }, cmd),
|
||||
display: Some(cmd.to_string()),
|
||||
suffix: ::linefeed::complete::Suffix::Some(' '),
|
||||
})
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
Some(s) => {
|
||||
let new_ptr: Option<&CommandTree> = command_tree.and_then(|cm| match cm {
|
||||
CommandTree::Top(children) => children.iter().find(|c| c.get_cmd() == s),
|
||||
CommandTree::NonTerminal { children, .. } =>
|
||||
children.iter().find(|c| c.get_cmd() == s),
|
||||
CommandTree::Terminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
|
||||
});
|
||||
command_tree = new_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(completions)
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
use crate::language::DebugAsk;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ReplOptions {
|
||||
pub debug_asks: HashSet<DebugAsk>,
|
||||
pub show_total_time: bool,
|
||||
pub show_stage_times: bool,
|
||||
}
|
||||
|
||||
impl ReplOptions {
|
||||
pub fn new() -> ReplOptions {
|
||||
ReplOptions { debug_asks: HashSet::new(), show_total_time: true, show_stage_times: false }
|
||||
}
|
||||
|
||||
pub fn save_to_file(&self, filename: &str) {
|
||||
let res = File::create(filename).and_then(|mut file| {
|
||||
let buf = crate::serde_json::to_string(self).unwrap();
|
||||
file.write_all(buf.as_bytes())
|
||||
});
|
||||
if let Err(err) = res {
|
||||
eprintln!("Error saving {} file {}", filename, err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_from_file(filename: &str) -> Result<ReplOptions, io::Error> {
|
||||
File::open(filename)
|
||||
.and_then(|mut file| {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
})
|
||||
.and_then(|contents| {
|
||||
let output: ReplOptions = crate::serde_json::from_str(&contents)?;
|
||||
Ok(output)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
use std::{fmt, fmt::Write};
|
||||
|
||||
use colored::*;
|
||||
|
||||
use crate::{
|
||||
language::{ComputationResponse, DebugAsk},
|
||||
ReplOptions,
|
||||
};
|
||||
|
||||
pub struct ReplResponse {
|
||||
label: Option<String>,
|
||||
text: String,
|
||||
color: Option<Color>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ReplResponse {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut buf = String::new();
|
||||
if let Some(ref label) = self.label {
|
||||
write!(buf, "({})", label).unwrap();
|
||||
}
|
||||
write!(buf, "=> {}", self.text).unwrap();
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self.color {
|
||||
Some(c) => buf.color(c),
|
||||
None => buf.normal(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_computation_response(
|
||||
response: ComputationResponse,
|
||||
options: &ReplOptions,
|
||||
) -> Vec<ReplResponse> {
|
||||
let mut responses = vec![];
|
||||
|
||||
if options.show_total_time {
|
||||
responses.push(ReplResponse {
|
||||
label: Some("Total time".to_string()),
|
||||
text: format!("{:?}", response.global_output_stats.total_duration),
|
||||
color: None,
|
||||
});
|
||||
}
|
||||
|
||||
if options.show_stage_times {
|
||||
responses.push(ReplResponse {
|
||||
label: Some("Stage times".to_string()),
|
||||
text: format!("{:?}", response.global_output_stats.stage_durations),
|
||||
color: None,
|
||||
});
|
||||
}
|
||||
|
||||
for debug_resp in response.debug_responses {
|
||||
let stage_name = match debug_resp.ask {
|
||||
DebugAsk::ByStage { stage_name, .. } => stage_name,
|
||||
_ => continue,
|
||||
};
|
||||
responses.push(ReplResponse {
|
||||
label: Some(stage_name.to_string()),
|
||||
text: debug_resp.value,
|
||||
color: Some(Color::Red),
|
||||
});
|
||||
}
|
||||
|
||||
responses.push(match response.main_output {
|
||||
Ok(s) => ReplResponse { label: None, text: s, color: None },
|
||||
Err(e) => ReplResponse { label: Some("Error".to_string()), text: e, color: Some(Color::Red) },
|
||||
});
|
||||
|
||||
responses
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
|
||||
|
||||
fn outer() {
|
||||
fn inner(a) {
|
||||
a + 10
|
||||
}
|
||||
|
||||
inner(20) + 8.3
|
||||
}
|
||||
|
||||
outer()
|
@ -1,21 +0,0 @@
|
||||
|
||||
fn hella(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
fn paha(x, y, z) {
|
||||
x * y * z
|
||||
}
|
||||
|
||||
a = 1
|
||||
|
||||
c = if a {
|
||||
10
|
||||
} else {
|
||||
20
|
||||
}
|
||||
|
||||
q = 4
|
||||
q = q + 2
|
||||
q + 1 + c
|
||||
|
@ -1,8 +0,0 @@
|
||||
if 20 {
|
||||
a = 20
|
||||
b = 30
|
||||
c = 40
|
||||
a + b + c
|
||||
} else {
|
||||
Null
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
|
||||
(fn(q) { q * 2 }(25))
|
||||
|
||||
a = fn(x) { x + 5 }
|
||||
a(2)
|
@ -1,17 +0,0 @@
|
||||
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
fn subtract(a, b) {
|
||||
a - b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
first_value = add(20, 20)
|
||||
second_value = subtract(700, 650)
|
||||
first_value + second_value
|
||||
}
|
||||
|
||||
main()
|
||||
|
@ -1,24 +0,0 @@
|
||||
|
||||
|
||||
fn hella(x) {
|
||||
print("hey")
|
||||
if x == 3 {
|
||||
Null
|
||||
} else {
|
||||
hella(x + 1)
|
||||
}
|
||||
}
|
||||
|
||||
hella(0)
|
||||
|
||||
|
||||
|
||||
fn fib(x) {
|
||||
if x < 3 {
|
||||
1
|
||||
} else {
|
||||
fib(x - 1) + fib(x - 2)
|
||||
}
|
||||
}
|
||||
|
||||
fib(10)
|
@ -1,11 +0,0 @@
|
||||
let c = 10
|
||||
|
||||
fn add(a, b) {
|
||||
let c = a + b
|
||||
c
|
||||
}
|
||||
|
||||
let mut b = 20
|
||||
|
||||
println(add(1,2))
|
||||
println(c + b)
|
@ -1,17 +0,0 @@
|
||||
fn main() {
|
||||
let a = 10
|
||||
let b = 20
|
||||
a + b
|
||||
}
|
||||
|
||||
//this is a one-line comment
|
||||
|
||||
/* this is
|
||||
a multiline
|
||||
comment
|
||||
*/
|
||||
|
||||
print(main())
|
||||
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
|
||||
for n <- 1..=100 {
|
||||
if n % 15 == 0 {
|
||||
print("FizzBuzz")
|
||||
} else if n % 5 == 0 {
|
||||
print("Buzz")
|
||||
} else if n % 3 == 0 {
|
||||
print("Fizz")
|
||||
} else {
|
||||
print(n.to_string())
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
|
||||
fn main() {
|
||||
|
||||
//comments are C-style
|
||||
/* nested comments /* are cool */ */
|
||||
|
||||
}
|
||||
|
||||
|
||||
@annotations use the @ sigil
|
||||
|
||||
// variable expressions
|
||||
//variable declaration works like Rust
|
||||
let a: I32 = 20
|
||||
let mut b: String = 20
|
||||
|
||||
there(); can(); be(); multiple(); statements(); per_line();
|
||||
|
||||
//string interpolation
|
||||
// maybe
|
||||
let yolo = "I have ${a + b} people in my house"
|
||||
|
||||
// let expressions
|
||||
let a = 10, b = 20, c = 30 in a + b + c
|
||||
|
||||
//list literal
|
||||
let q = [1,2,3,4]
|
||||
|
||||
//lambda literal - uses haskell-ish syntax
|
||||
q.map(\(item) { item * 100 })
|
||||
|
||||
fn yolo(a: MyType, b: YourType): ReturnType<Param1, Param2> {
|
||||
if a == 20 {
|
||||
return "early"
|
||||
}
|
||||
}
|
||||
|
||||
/* for/while loop topics */
|
||||
//TODO I can probably get away with having one of `for`, `while`
|
||||
|
||||
//infinite loop
|
||||
while {
|
||||
if x() { break }
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
//conditional loop
|
||||
while conditionHolds() {
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
//iteration over a variable
|
||||
for i <- [1..1000] {
|
||||
|
||||
} //return type is return type of block
|
||||
|
||||
|
||||
//monadic decomposition
|
||||
for {
|
||||
a <- maybeInt();
|
||||
s <- foo()
|
||||
} return {
|
||||
a + s
|
||||
} //return type is Monad<return type of block>
|
||||
|
||||
/* end of for loops */
|
||||
|
||||
|
||||
|
||||
/* conditionals/pattern matching */
|
||||
|
||||
// `is` functions as an operator asking "does this pattern match"
|
||||
|
||||
x is Some(t) // type bool
|
||||
|
||||
if x {
|
||||
is Some(t) => {
|
||||
}
|
||||
is None => {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//syntax is, I guess, for <expr> <brace-block>, where <expr> is a bool, or a <arrow-expr>
|
||||
|
||||
// type level alises
|
||||
type alias <name> = <other type> #maybe thsi should be 'alias'?
|
||||
|
||||
/*
|
||||
what if type A = B meant that you could had to create A's with A(B), but when you used A's the interface was exactly like B's?
|
||||
maybe introduce a 'newtype' keyword for this
|
||||
*/
|
||||
|
||||
//declaring types of all stripes
|
||||
type MyData = { a: i32, b: String } // shorthand special-case for `type MyData = MyData { a: i32, b: String }`
|
||||
type MyType = MyType
|
||||
type Option<a> = None | Some(a)
|
||||
type Signal = Absence | SimplePresence(i32) | ComplexPresence {a: i32, b: MyCustomData}
|
||||
|
||||
//traits TODO I probably want to rename this
|
||||
|
||||
trait Bashable { }
|
||||
trait Luggable {
|
||||
fn lug(self, a: Option<Self>)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// lambdas - maybe I want to use ruby-style (not rust style) syntax
|
||||
// e.g.
|
||||
// Also TODO Nix uses `X: Y: Z` for in its value-level syntax, why can't I?
|
||||
let a: X -> Y -> Z = {|x,y| }
|
@ -1,17 +0,0 @@
|
||||
|
||||
println(sua(4))
|
||||
|
||||
fn sua(x): Int {
|
||||
x + 10
|
||||
}
|
||||
|
||||
|
||||
//let a = getline()
|
||||
|
||||
/*
|
||||
if a == "true" {
|
||||
println("You typed true")
|
||||
} else {
|
||||
println("You typed something else")
|
||||
}
|
||||
*/
|
@ -1,12 +0,0 @@
|
||||
|
||||
fn a(x) {
|
||||
x + 20
|
||||
}
|
||||
|
||||
fn x(x) {
|
||||
x + a(9384)
|
||||
}
|
||||
|
||||
a(0)
|
||||
x(1)
|
||||
|
@ -1,3 +0,0 @@
|
||||
|
||||
(display (+ 1 2))
|
||||
(display "Hello")
|
@ -1,8 +0,0 @@
|
||||
|
||||
fn めんどくさい(a) {
|
||||
a + 20
|
||||
}
|
||||
|
||||
print(めんどくさい(394))
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
|
||||
|
||||
a = 0
|
||||
while a < 100000
|
||||
print("hello", a)
|
||||
a = a + 1
|
||||
end
|
71
src/main.rs
71
src/main.rs
@ -1,71 +0,0 @@
|
||||
use std::{collections::HashSet, fs::File, io::Read, path::PathBuf, process::exit};
|
||||
|
||||
use schala_lang::{Schala, SchalaConfig};
|
||||
use schala_repl::{ComputationRequest, ProgrammingLanguageInterface, Repl};
|
||||
|
||||
//TODO specify multiple langs, and have a way to switch between them
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let matches = command_line_options().parse(&args[1..]).unwrap_or_else(|e| {
|
||||
eprintln!("Error parsing options: {}", e);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
if matches.opt_present("help") {
|
||||
println!("{}", command_line_options().usage("Schala metainterpreter"));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if matches.free.is_empty() {
|
||||
let state = Schala::new();
|
||||
let mut repl = Repl::new(state);
|
||||
let config = SchalaConfig { repl: true };
|
||||
repl.run_repl(config);
|
||||
} else {
|
||||
let paths: Vec<PathBuf> = matches.free.iter().map(PathBuf::from).collect();
|
||||
//TODO handle more than one file
|
||||
let filename = &paths[0];
|
||||
let extension = filename.extension().and_then(|e| e.to_str()).unwrap_or_else(|| {
|
||||
eprintln!("Source file `{}` has no extension.", filename.display());
|
||||
exit(1);
|
||||
});
|
||||
|
||||
//TODO this proably should be a macro for every supported language
|
||||
if extension == Schala::source_file_suffix() {
|
||||
let config = SchalaConfig { repl: false };
|
||||
|
||||
run_noninteractive(paths, Schala::new(), config);
|
||||
} else {
|
||||
eprintln!("Extension .{} not recognized", extension);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_noninteractive<L: ProgrammingLanguageInterface>(
|
||||
filenames: Vec<PathBuf>,
|
||||
mut language: L,
|
||||
config: L::Config,
|
||||
) {
|
||||
// for now, ony do something with the first filename
|
||||
|
||||
let filename = &filenames[0];
|
||||
let mut source_file = File::open(filename).unwrap();
|
||||
let mut buffer = String::new();
|
||||
source_file.read_to_string(&mut buffer).unwrap();
|
||||
|
||||
let request = ComputationRequest { source: &buffer, config, debug_requests: HashSet::new() };
|
||||
|
||||
let response = language.run_computation(request);
|
||||
match response.main_output {
|
||||
Ok(s) => println!("{}", s),
|
||||
Err(s) => eprintln!("{}", s),
|
||||
};
|
||||
}
|
||||
|
||||
fn command_line_options() -> getopts::Options {
|
||||
let mut options = getopts::Options::new();
|
||||
options.optflag("h", "help", "Show help text");
|
||||
//options.optflag("w", "webapp", "Start up web interpreter");
|
||||
options
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Schala Metainterpreter Web Evaluator</title>
|
||||
<style>
|
||||
.CodeArea {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
</div>
|
||||
<script src="bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,64 +0,0 @@
|
||||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
const superagent = require("superagent");
|
||||
|
||||
const serverAddress = "http://localhost:8000";
|
||||
|
||||
class CodeArea extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {value: "", lastOutput: null};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({value: event.target.value});
|
||||
}
|
||||
|
||||
submit(event) {
|
||||
console.log("Event", this.state.value);
|
||||
const source = this.state.value;
|
||||
|
||||
superagent.post(`${serverAddress}/input`)
|
||||
.send({ source })
|
||||
.set("accept", "json")
|
||||
.end((error, response) => {
|
||||
if (response) {
|
||||
console.log("Resp", response);
|
||||
this.setState({lastOutput: response.body.text})
|
||||
} else {
|
||||
console.error("Error: ", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderOutput() {
|
||||
if (!this.state.lastOutput) {
|
||||
return null;
|
||||
}
|
||||
return <textarea readOnly value={ this.state.lastOutput } />;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div className="CodeArea">
|
||||
<div className="input">
|
||||
<textarea value={ this.state.value } onChange={this.handleChange}>
|
||||
</textarea>
|
||||
<button onClick={ this.submit }>Run!</button>
|
||||
</div>
|
||||
<div className="output">
|
||||
{ this.renderOutput() }
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
const main = (<div>
|
||||
<h1>Schala web input</h1>
|
||||
<p>Write your source code here</p>
|
||||
<CodeArea/>
|
||||
</div>);
|
||||
|
||||
const rootDom = document.getElementById("main");
|
||||
ReactDOM.render(main, rootDom);
|
@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "static",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"babel": "^6.23.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^14.4.0",
|
||||
"react": "^15.6.1",
|
||||
"react-dom": "^15.6.1",
|
||||
"superagent": "^3.6.3",
|
||||
"uglify-js": "^3.1.1"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"babel-preset-react",
|
||||
"babel-preset-es2015"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "browserify main.jsx -t babelify -o bundle.js",
|
||||
"build-minify": "browserify main.jsx -t babelify | uglifyjs > bundle.js"
|
||||
}
|
||||
}
|
1728
static/yarn.lock
1728
static/yarn.lock
File diff suppressed because it is too large
Load Diff
2
subtrees/parser-combinator/.gitignore
vendored
Normal file
2
subtrees/parser-combinator/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
13
subtrees/parser-combinator/Cargo.toml
Normal file
13
subtrees/parser-combinator/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "parser-combinator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
arbitrary = "1.2.0"
|
||||
proptest = "1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.16.0"
|
10
subtrees/parser-combinator/README.md
Normal file
10
subtrees/parser-combinator/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Rust Parser Combinator
|
||||
|
||||
This is a super-basic Rust parser combinator library I wrote mostly
|
||||
as an exercise for myself. Inspired by [nom](https://github.com/rust-bakery/nom)
|
||||
and [chumsky](https://github.com/zesterer/chumsky)
|
||||
|
||||
## Ideas for future work
|
||||
|
||||
* See if some of the ideas in [Efficient Parsing with Parser Combinators](https://research.rug.nl/en/publications/efficient-parsing-with-parser-combinators)
|
||||
can be incorporated here.
|
198
subtrees/parser-combinator/src/choice/mod.rs
Normal file
198
subtrees/parser-combinator/src/choice/mod.rs
Normal file
@ -0,0 +1,198 @@
|
||||
use crate::parser::{ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub fn choice2<P1, P2, I, O, E>(parser1: P1, parser2: P2) -> impl Parser<I, O, E>
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
choice((parser1, parser2))
|
||||
}
|
||||
|
||||
pub fn choice<C, I, O, E>(choices: C) -> impl Parser<I, O, E>
|
||||
where
|
||||
C: Choice<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
let rep = choices.representation();
|
||||
(move |input| choices.parse(input), rep)
|
||||
}
|
||||
|
||||
pub trait Choice<I: Clone, O, E> {
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E>;
|
||||
fn representation(&self) -> Representation;
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2> Choice<I, O, E> for (P1, P2)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2, P3> Choice<I, O, E> for (P1, P2, P3)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1, &self.2];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1, &self.2];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2, P3, P4> Choice<I, O, E> for (P1, P2, P3, P4)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
P4: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1, &self.2, &self.3];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1, &self.2, &self.3];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2, P3, P4, P5> Choice<I, O, E> for (P1, P2, P3, P4, P5)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
P4: Parser<I, O, E>,
|
||||
P5: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![
|
||||
&self.0 as &dyn Parser<I, O, E>,
|
||||
&self.1,
|
||||
&self.2,
|
||||
&self.3,
|
||||
&self.4,
|
||||
];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![
|
||||
&self.0 as &dyn Parser<I, O, E>,
|
||||
&self.1,
|
||||
&self.2,
|
||||
&self.3,
|
||||
&self.4,
|
||||
];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2, P3, P4, P5, P6> Choice<I, O, E> for (P1, P2, P3, P4, P5, P6)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
P4: Parser<I, O, E>,
|
||||
P5: Parser<I, O, E>,
|
||||
P6: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![
|
||||
&self.0 as &dyn Parser<I, O, E>,
|
||||
&self.1,
|
||||
&self.2,
|
||||
&self.3,
|
||||
&self.4,
|
||||
&self.5,
|
||||
];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![
|
||||
&self.0 as &dyn Parser<I, O, E>,
|
||||
&self.1,
|
||||
&self.2,
|
||||
&self.3,
|
||||
&self.4,
|
||||
&self.5,
|
||||
];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
fn choice_loop<I, O, E>(input: I, parsers: Vec<&dyn Parser<I, O, E>>) -> ParseResult<I, O, E>
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
//TODO need a more principled way to return an error when no choices work
|
||||
let mut err = None;
|
||||
|
||||
for parser in parsers.iter() {
|
||||
match parser.parse(input.clone()) {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(e) => {
|
||||
err = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err.unwrap())
|
||||
}
|
||||
|
||||
fn repr_loop<I, O, E>(parsers: Vec<&dyn Parser<I, O, E>>) -> Representation
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
let mut iter = parsers.iter().map(|p| p.representation());
|
||||
Representation::from_choice(&mut iter)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::combinators::repeated;
|
||||
use crate::primitives::literal;
|
||||
|
||||
#[test]
|
||||
fn test_choice() {
|
||||
let p = choice2(
|
||||
literal("gnostika").to(1),
|
||||
repeated(literal(" ")).at_least(1).to(2),
|
||||
);
|
||||
assert_eq!(p.parse("gnostika twentynine"), Ok((1, " twentynine")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_several_choices() {
|
||||
let p = choice((
|
||||
literal("a").to(1),
|
||||
literal("q").to(10),
|
||||
repeated(literal("chutney")).to(200),
|
||||
literal("banana").to(10000),
|
||||
));
|
||||
|
||||
assert_eq!(p.parse("q drugs").unwrap(), (10, " drugs"));
|
||||
}
|
||||
}
|
16
subtrees/parser-combinator/src/combinators/map.rs
Normal file
16
subtrees/parser-combinator/src/combinators/map.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use crate::parser::{Parser, ParserInput};
|
||||
|
||||
pub fn map<P, F, I, O1, O2, E>(parser: P, map_fn: F) -> impl Parser<I, O2, E>
|
||||
where
|
||||
I: ParserInput,
|
||||
P: Parser<I, O1, E>,
|
||||
F: Fn(O1) -> O2,
|
||||
{
|
||||
let rep = parser.representation();
|
||||
let p = move |input| {
|
||||
parser
|
||||
.parse(input)
|
||||
.map(|(result, rest)| (map_fn(result), rest))
|
||||
};
|
||||
(p, rep)
|
||||
}
|
66
subtrees/parser-combinator/src/combinators/mod.rs
Normal file
66
subtrees/parser-combinator/src/combinators/mod.rs
Normal file
@ -0,0 +1,66 @@
|
||||
mod map;
|
||||
mod optional;
|
||||
mod repeated;
|
||||
mod separated_by;
|
||||
|
||||
pub use map::map;
|
||||
pub use optional::optional;
|
||||
pub use repeated::repeated;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parser::Parser;
|
||||
use crate::primitives::literal;
|
||||
|
||||
#[test]
|
||||
fn test_map() {
|
||||
let lit_a = literal("a");
|
||||
let output = lit_a.map(|s| s.to_uppercase()).parse("a yolo");
|
||||
assert_eq!(output.unwrap(), ("A".to_string(), " yolo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_or_more() {
|
||||
let p = repeated(literal("bongo ")).at_least(1);
|
||||
let input = "bongo bongo bongo bongo bongo ";
|
||||
|
||||
let (output, rest) = p.parse(input).unwrap();
|
||||
assert_eq!(rest, "");
|
||||
assert_eq!(output.len(), 5);
|
||||
|
||||
let (output, rest) = p.parse("bongo ecks").unwrap();
|
||||
assert_eq!(output.len(), 1);
|
||||
assert_eq!(rest, "ecks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_separated_by() {
|
||||
let p = repeated(literal("garb").to(20))
|
||||
.separated_by(repeated(literal(" ")).at_least(1), false);
|
||||
|
||||
assert_eq!(
|
||||
p.parse("garb garb garb garb").unwrap(),
|
||||
(vec![20, 20, 20, 20], "")
|
||||
);
|
||||
|
||||
assert!(p.parse("garb garb garb garb ").is_err());
|
||||
|
||||
let p =
|
||||
repeated(literal("garb").to(20)).separated_by(repeated(literal(" ")).at_least(1), true);
|
||||
|
||||
assert_eq!(
|
||||
p.parse("garb garb garb garb").unwrap(),
|
||||
(vec![20, 20, 20, 20], "")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
p.parse("garb garb garb garb ").unwrap(),
|
||||
(vec![20, 20, 20, 20], "")
|
||||
);
|
||||
assert_eq!(
|
||||
p.parse("garb garb garb garb q").unwrap(),
|
||||
(vec![20, 20, 20, 20], "q")
|
||||
);
|
||||
}
|
||||
}
|
17
subtrees/parser-combinator/src/combinators/optional.rs
Normal file
17
subtrees/parser-combinator/src/combinators/optional.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::parser::{Parser, ParserInput, Representation};
|
||||
|
||||
pub fn optional<P, I, O, E>(parser: P) -> impl Parser<I, Option<O>, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
let rep = Representation::from_choice(
|
||||
&mut [parser.representation(), Representation::new("ε")].into_iter(),
|
||||
);
|
||||
let p = move |input: I| match parser.parse(input.clone()) {
|
||||
Ok((output, rest)) => Ok((Some(output), rest)),
|
||||
Err(_e) => Ok((None, input)),
|
||||
};
|
||||
|
||||
(p, rep)
|
||||
}
|
94
subtrees/parser-combinator/src/combinators/repeated.rs
Normal file
94
subtrees/parser-combinator/src/combinators/repeated.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use crate::combinators::separated_by::SeparatedBy;
|
||||
use crate::parser::{BoxedParser, ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub fn repeated<'a, P, I, O>(parser: P) -> Repeated<'a, I, O>
|
||||
where
|
||||
P: Parser<I, O, I> + 'a,
|
||||
I: ParserInput + Clone + 'a,
|
||||
{
|
||||
Repeated {
|
||||
inner_parser: BoxedParser::new(parser),
|
||||
at_least: None,
|
||||
at_most: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Repeated<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
pub(super) inner_parser: BoxedParser<'a, I, O, I>,
|
||||
pub(super) at_least: Option<u16>,
|
||||
pub(super) at_most: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'a, I, O> Repeated<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
pub fn at_least(self, n: u16) -> Self {
|
||||
Self {
|
||||
at_least: Some(n),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn at_most(self, n: u16) -> Self {
|
||||
Self {
|
||||
at_most: Some(n),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separated_by<D, O2>(self, delimiter: D, allow_trailing: bool) -> SeparatedBy<'a, I, O>
|
||||
where
|
||||
D: Parser<I, O2, I> + 'a,
|
||||
O2: 'a,
|
||||
I: 'a,
|
||||
{
|
||||
SeparatedBy {
|
||||
inner_repeated: self,
|
||||
delimiter: BoxedParser::new(delimiter.to(())),
|
||||
allow_trailing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I, O> Parser<I, Vec<O>, I> for Repeated<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone + 'a,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, Vec<O>, I> {
|
||||
let at_least = self.at_least.unwrap_or(0);
|
||||
let at_most = self.at_most.unwrap_or(u16::MAX);
|
||||
|
||||
if at_most == 0 {
|
||||
return Ok((vec![], input));
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut count: u16 = 0;
|
||||
let mut further_input = input.clone();
|
||||
|
||||
while let Ok((item, rest)) = self.inner_parser.parse(further_input.clone()) {
|
||||
results.push(item);
|
||||
further_input = rest;
|
||||
count += 1;
|
||||
if count >= at_most {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if count < at_least {
|
||||
return Err(input);
|
||||
}
|
||||
|
||||
Ok((results, further_input))
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
Representation::repeated(
|
||||
self.inner_parser.representation(),
|
||||
self.at_least.unwrap_or(0),
|
||||
self.at_most.unwrap_or(u16::MAX),
|
||||
)
|
||||
}
|
||||
}
|
84
subtrees/parser-combinator/src/combinators/separated_by.rs
Normal file
84
subtrees/parser-combinator/src/combinators/separated_by.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use crate::combinators::repeated::Repeated;
|
||||
use crate::parser::{BoxedParser, ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub struct SeparatedBy<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
pub(super) inner_repeated: Repeated<'a, I, O>,
|
||||
pub(super) delimiter: BoxedParser<'a, I, (), I>,
|
||||
pub(super) allow_trailing: bool,
|
||||
}
|
||||
|
||||
impl<'a, I, O> Parser<I, Vec<O>, I> for SeparatedBy<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone + 'a,
|
||||
{
|
||||
fn representation(&self) -> Representation {
|
||||
Representation::new("sepby")
|
||||
}
|
||||
|
||||
fn parse(&self, input: I) -> ParseResult<I, Vec<O>, I> {
|
||||
let at_least = self.inner_repeated.at_least.unwrap_or(0);
|
||||
let at_most = self.inner_repeated.at_most.unwrap_or(u16::MAX);
|
||||
let parser = &self.inner_repeated.inner_parser;
|
||||
let delimiter = &self.delimiter;
|
||||
|
||||
if at_most == 0 {
|
||||
return Ok((vec![], input));
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut count: u16 = 0;
|
||||
let mut further_input;
|
||||
|
||||
match parser.parse(input.clone()) {
|
||||
Ok((item, rest)) => {
|
||||
results.push(item);
|
||||
further_input = rest;
|
||||
}
|
||||
Err(_e) => {
|
||||
if at_least > 0 {
|
||||
return Err(input);
|
||||
} else {
|
||||
return Ok((vec![], input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match delimiter.parse(further_input.clone()) {
|
||||
Ok(((), rest)) => {
|
||||
further_input = rest;
|
||||
}
|
||||
Err(_e) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match parser.parse(further_input.clone()) {
|
||||
Ok((item, rest)) => {
|
||||
results.push(item);
|
||||
further_input = rest;
|
||||
count += 1;
|
||||
}
|
||||
Err(_e) if self.allow_trailing => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
if count >= at_most {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if count < at_least {
|
||||
return Err(input);
|
||||
}
|
||||
|
||||
Ok((results, further_input))
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user