diff options
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | spec.md | 36 | ||||
| -rw-r--r-- | src/Interpreter.hs | 5 | ||||
| -rw-r--r-- | test/Spec.hs | 10 | ||||
| -rw-r--r-- | test/scripts/record1.milch | 15 |
5 files changed, 55 insertions, 26 deletions
@@ -2,7 +2,7 @@ ## Summary -`MILCH` (or `Milch`, or `milch`) is a interpreted, dynamically typed functional programming language that has some Lisp-inspired syntax and Haskell-inspired semantics. Milch is german for 🥛. +`MILCH` (or `Milch`, or `milch`) is a interpreted, dynamically typed functional programming language that has some Lisp-inspired syntax and Haskell-inspired semantics. Milch is German for 🥛. I'm developing this language by myself as a hobby, and the spec is going to change all the time. Some features documented here might not yet be implemented. @@ -14,18 +14,19 @@ Check out the TODO ## Building -Install a Haskell toolchain with Stack and GHC. Run +Install a Haskell toolchain with Stack and GHC. To build and install the `milch` binary in your `PATH`, run: - stack build stack install -to build and install the `milch` binary in your `PATH`. - -Then, run +To see the help text, run: milch -h -to see the help text. +## Tests + +To run the test suite, run: + + stack test ## Author @@ -1,28 +1,36 @@ # Language spec - Dynamically and weakly typed, interpreted -- Impure functions marked with ! (convention) +- Impure functions marked with ! (convention), purity is tracked by the interpreter - Boolean functions marked with ? (convention) - Loops implemented with recursion ## Value types - Number -3.14 - Symbol PI - Boolean true - String "foobar" - Vector [1 2 3] - Hash map { a 1 b 2 } - Function (\[x y] (+ x y)) + Number -3.14 + Symbol PI + Tag :tag + Boolean true + String "foobar" + Vector [1 2 3] + Hash map { a 1 b 2 } + Function (\[x y] (+ x y)) ## Syntax ### Function calls -A sequence of values surrounded by parens is considered a function call In a function call, the first element must evaluate to a function value. The function will be invoked with the rest of the elements as arguments to the function. +A sequence of values surrounded by parens is considered a function call In a function call, the first element must evaluate to a function value. The function will be invoked with the rest of the elements as arguments to the function. A function call with no arguments evaluates to the function itself. ### Function definition -Calling the builtin variadic function '\' constructs a new function. The first argument must be a vector of symbols (parameter list). The rest of the arguments form the function body. The evaluated value of the last argument is returned as the return value of the function. + +Calling the builtin variadic function `\` constructs a new function. The first argument must be a vector of symbols (parameter list). The rest of the arguments form the function body. The evaluated value of the last argument is returned as the return value of the function. + +Memoized pure functions can be created with a special `let memo`-binding: + + (let memo f (\[x] x)) + +Impure functions can be created with the builtin `\!`. ### Currying @@ -43,20 +51,20 @@ Evaluates second argument and stores the resulting value in the environment. `\` Defines a function. - (\[x y] + x y) + (\[x y] (+ x y)) `print!` -Prints a value and a newline. +Prints a value without a newline. (print! PI) `match` -Matches first argument (value) to even positioned arguments (matchers). On match (at position i), evaluates the odd positioned argument (branch) right after it (at position i + 1). If given an even number of arguments, the last argument will act as the default branch. +Matches first argument (value) to even positioned arguments (matchers). On match (at position i), evaluates the odd positioned argument (branch) right after it (at position i + 1). (match (sum2 1 1) 2 (do-thing) 3 (do-other-thing) - (do-else)) + otherwise (do-else)) `head` Gets the first element of a vector. diff --git a/src/Interpreter.hs b/src/Interpreter.hs index c5c19e0..ca87a82 100644 --- a/src/Interpreter.hs +++ b/src/Interpreter.hs @@ -20,9 +20,7 @@ import Tokenizer ( tokenize' ) import Parser ( parse ) curryCall :: [AST] -> ASTNode -> LContext AST -curryCall [] (ASTFunction fIsPure f) = do - checkPurity fIsPure - f (makeNonsenseAST ASTUnit) +curryCall [] astNode = return $ makeNonsenseAST astNode curryCall (arg:[]) (ASTFunction fIsPure f) = do checkPurity fIsPure f arg @@ -33,7 +31,6 @@ curryCall (arg:rest) f = do checkPurity fIsPure f' arg other -> throwL (astPos g) $ "cannot call value " ++ show other ++ " as a function" -curryCall _ astFn = throwL "" $ "unreachable: curryCall, astFn: " ++ show astFn traverseAndReplace :: String -> AST -> AST -> AST traverseAndReplace param arg ast@AST { an = ASTSymbol sym } diff --git a/test/Spec.hs b/test/Spec.hs index 6cb9fac..a17f344 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -101,7 +101,7 @@ e2eTests = testGroup "e2e" [ let expectedLastAST = astInteger 12586269025 assertEqual "" expectedLastAST (last gotASTs), - do let env = makeEnv [builtinAdd2, builtinEq2] + do let env = builtinEnv let script1 = "(let a :thing)\ \(let b :thing)\ \(eq? a b)" @@ -109,6 +109,14 @@ e2eTests = testGroup "e2e" [ runInlineScript "<test>" script1 let expectedLastAST = astBoolean True + assertEqual "" expectedLastAST (last gotASTs), + + do let env = builtinEnv + script1 <- readFile "test/scripts/record1.milch" + (gotASTs, _) <- expectSuccessL env $ + runInlineScript "<test>" script1 + + let expectedLastAST = astInteger 369 assertEqual "" expectedLastAST (last gotASTs) ] diff --git a/test/scripts/record1.milch b/test/scripts/record1.milch new file mode 100644 index 0000000..8594179 --- /dev/null +++ b/test/scripts/record1.milch @@ -0,0 +1,15 @@ +(import "core/common") + +(record A + foo + bar) + +(let a (A/create "123" 456)) + +(pipe a [ + A/get-foo + parse-int + (* 3) + (A/create (A/get-foo a)) + (A/get-bar) +]) |
