Samir Benmendil [Tue, 23 Jan 2018 22:46:38 +0000 (22:46 +0000)]
Show values for Lists
`unwordsList :: [LispVal] -> String` composes three functions:
* `map :: (a -> b) -> [a] -> [b]` a function with two "arguments"[0], a
function `(a -> b)` and a list of `a`s (`[a]`) and returns a `[b]`, i.e.
it applies the function in the first argument to every element of the
second
* `showAll :: LispVal -> String` described in the previous commit.
`map showAll` returns a function that applies `showAll` to each
element of the list it gets.
* `unwords :: [String] -> String` which joins a list of Strings with
spaces.
The `.` dot operator is used to pass the output of rhs to the input of
lhs. So in essence, `unwordsList` is a function that will `unword` the
list of Strings returned by `map`ing `showVal` to the list of `LispVal`s
passed to `unwordsList`.
[0] Remember that all functions are curried, and actually only take one
argument and return a "partial function" that takes the next argument.
Samir Benmendil [Sun, 21 Jan 2018 00:56:01 +0000 (00:56 +0000)]
Support #bodx in number parser
`where radix = ...` returns a function that takes a number and parses it
accordingly. The function is chosed by 'try'ing to read a '#' followed
by any of "bodx" and if that fails assume the base is decimal.
The `>>` sequencing operator is used to combine `char` and `oneOf`, they
are executed one after another, discarding the value of the first.
`readInt` takes a base (2), a predicate returning what chars are valid
and a function that describes the Integer value for a char.
`(\x -> elem x "01")` is a lambda that returns true if x is in "01".
`(read . (:[]))` is interesting. The `.` operator composes the functions
`read` and `:[]`, where `:` is the append to list function and `[]` is
the "first argument", i.e. `:[]` appends an element to the empty list.
`let ((a,_):_) = ...` extracts the number from the `[(Number,String)]`
type returned by the `read*` functions.
Because strings starting with '#' are valid Atoms as well, we need to
parse for numbers first.
Samir Benmendil [Sat, 20 Jan 2018 22:32:20 +0000 (22:32 +0000)]
Support escaped quotes
`innerChar` matches a char that is not a backslash or a quote. If it is
either, it will match a backslash, capture a following quote and lift a
quote char into the `innerChar` monad.
This will not support anything other than quote after the backslash char.
Samir Benmendil [Sun, 14 Jan 2018 00:41:31 +0000 (00:41 +0000)]
Parse Numbers
`many1 digit` matches one or more digits. But it's result is actually a
`Parser String`. We need a way to operate on the value inside the `Parser
String` monad. This is where `liftM` comes in.
Another new thing here is the `.` operator which applies the function on
the right to any input of the function on the left. Or in other words it
applies the right function and passes the result to the function on the
left. In this case, `read` is applied to the parsed digits and the
result (of type Integer) is passed to the `Number` constructor of
`LispVal`.
Samir Benmendil [Sun, 14 Jan 2018 00:24:21 +0000 (00:24 +0000)]
Handle boolean constants
Turns out that Scheme defines `#t` and `#f` to be `true` and `false`.
`let` is used to devife a new symbol `atom` which is a String.
`case...of` matches the literals and constructs a `LispVal` of the
appropriate type and value. `_` is a match all token.
Samir Benmendil [Sun, 14 Jan 2018 00:19:18 +0000 (00:19 +0000)]
Parse Atoms which start with a letter or symbol
`<|>` is a Parser combinator, the choice operator. It will try the first
parser, if it doesn't match anything, try the second parser, and so on.
`a:b` creates a list from `a` and `b`. Another possibility would be to
use `[a] ++ b`, where a new list with a single element is created `[a]`
and concatenated with `++`
Samir Benmendil [Sun, 14 Jan 2018 00:03:01 +0000 (00:03 +0000)]
Parse Strings surrounded by `"`
We need to use a `do-block` because we would like to keep the return
value of a parse in the middle. So we parse a single `char '"'`, parse
`many (noneOf "\"")` and assign the return value to `x` and parse
another `"`.
We apply the `String` constructor to the value of `x` to turn it into a
`LispVal` with `String x`.
Finally we inject the `LispVal` into the `Parser` monad. We need to do
this because each line of a `do-block` needs to be of the same type but
the result of `String x` is a `LispVal`, not a `Parser`. `return` wraps
the `LispVal` up in a `Parser` action that consumes no input.
Consider `a $ b c` as syntactic sugar for `a (b c)`.
Samir Benmendil [Sat, 13 Jan 2018 23:24:30 +0000 (23:24 +0000)]
Ignore spaces
We add the spaces `Parser` which returns nothing `()` and skips any
spaces.
`spaces` is already defined in Parsec, but does not do exactly what we
want, we can hide it from the import.
`skipMany a` matches 0 or more `a` and skips them.
`>>` is the **bind** operator. It may have completely different meanings
depending on the monad you use. In the Parser monad in means "try the
first parameter, then try the second with anything that is left", i.e.
"first skip all the spaces, then match the symbol".
Samir Benmendil [Sat, 13 Jan 2018 23:05:55 +0000 (23:05 +0000)]
readExpr function to check whether the input matches symbol
`readExpr :: String -> String` defines `readExpr` to be a function
(`->`) from String to a String.
`readExpr input = ...` will assign anything on the rhs to the `readExpr`
function where `input` is mapped to the first parameter of that
function.
`case ... of` is a *filter*.
`parse a b c` is a function from Parsec where `a` is the parser it should
use, `b` is the name of the input which is used for error messages and
`c` is the String to parse.
`parse` returns an `Either` type whose `Left` constructor indicates an
error. If we get a `Right` value, we bind it to `val`, ignore it and
return the String "Found Value".
Samir Benmendil [Sat, 13 Jan 2018 22:41:38 +0000 (22:41 +0000)]
Define a parser for various symbols in Scheme
`symbol :: Parser Char` is probably not needed (?)
I don't understand where the `Parser` type comes from, I cannot see it
in the doc of `Text.ParserCombinators.Parsec`. It may be that
`ParserCombinators` is a compatibility layer and that `Parser` is
defined somewhere else.
Samir Benmendil [Sat, 13 Jan 2018 22:22:28 +0000 (22:22 +0000)]
Add Parsec dependencie
This is a Parser module that will be used in the project.
`stack` makes it quite easy to add dependencies. Simply change the
`dependencies` list in `package.yaml` and run `stack build` again. Any
missing modules will be installed.
Samir Benmendil [Sat, 13 Jan 2018 22:06:26 +0000 (22:06 +0000)]
Print the sum of two arguments
`read`'s type is defined as `read :: (Read a) => String -> a`, i.e.
`read` takes as `String` and returns an `a` (number?). I don't know what
`=>` means. (`(Read a)` is an instance of class Read ?)
`show`'s type is a bit more complicated it seems as it has multiple
instances for various types of input. But in essence it takes a type and
returns a `String`.
Samir Benmendil [Sat, 13 Jan 2018 21:56:06 +0000 (21:56 +0000)]
Hello World with argument
This will simply print "Hello, " followed by the first argument from the
commandline.
Every Haskell program needs to have a main "action" in the Main module.
This is the entry point.
Files starting with `module <Foo>` will be part of the `Foo` module.
By convention modules are capitalized and definitions aren't.
`import System.Environment` provides `getArgs` to access cli arguments.
Haskell is strongly typed and types may be assigned with `::`. In this case
`main` is of type `IO ()` which is an IO action holding no information.
(more on that later hopefully, as I don't fully understand that.)