Packages

A simple Lisp like language parser

Current section

Files

Jump to
lix_parser lib lix_parser.ex
Raw

lib/lix_parser.ex

defmodule LixParser do
use LixParser.Types
@moduledoc ~S"""
# Abstract
Lix is a simple Lisp dialect which is parsed by `LixParser.parse` into an AST.
This AST can then be interpreted as seen fit. An example of a useful interpretation can
be found in [`LixEval`](https://hex.pm/packages/lix_eval) which defines a minimum of
special forms (`def`, `fn`, `defn`, `let` and `use`)
The syntax of Lix is defined by the following doctests
# Syntax
## Literals
### Numbers
iex(1)> parse("42")
{:ok, {:lit, 42}}
As long as we get an `:ok` result we can use `parse!` too
iex(2)> parse!("43")
{:lit, 43}
There are floats too
iex(3)> parse!("-44.0")
{:lit, -44.0}
### Atoms
iex(4)> parse!(":atom")
{:lit, :atom}
### Strings
iex(5)> parse!(~s{"hello"})
{:lit, "hello"}
## Identifiers
As no semantic checks can be made by the parser, the ast is augemented by the position in case of all
other constructs.
This will allow an interpreter, evaluator or semantic checker to point to the correct position in the
source.
iex(6)> parse!("variable")
{:id, {1, 1}, ~W[variable]}
The name of the identifier is a list because we can have compound names, as follows
iex(7)> parse!(" alpha.beta")
{:id, {3, 1}, ~W[alpha beta]}
And we can have more of this of course
iex(8)> parse!("\n\t a.b.c")
{:id, {4, 2}, ~W[a b c]}
Note that positioning does not know about tabwidth in this version of [`Minipeg`](https://gitlab.com/lab42-hex-projects/minipeg) and thus
just assumes tabwidth 1
Operators are just identifiers too
iex(9)> parse!("+")
{:id, {1, 1}, ["+"]}
They can consist of up to three symbols
iex(10)> parse!("@@-")
{:id, {1, 1}, ["@@-"]}
## Compound Expressions
Literals and Identifiers are the **only scalar** expressions in Lix.
They can be combined into lists (`(...)`), quoted lists (`[...]`) and maps (`{...}`) as follows
### Lists (s-expressions)
iex(11)> parse!("(+ 2 4)")
{:list, {1, 1}, [{:id, {2, 1}, ["+"]}, {:lit, 2}, {:lit, 4}]}
### Quoted lists
iex(12)> parse!(~s{[(:a) "b"]})
{:quoted, {1, 1}, [{:list, {2, 1}, [lit: :a]}, {:lit, "b"}]}
### Maps
iex(13)> parse!("{:a 1 b 2}")
{:map, {1, 1}, [{:lit, :a}, {:lit, 1}, {:id, {7, 1}, ~W[b]}, {:lit, 2}]}
## Error Handling
Few, experienced with parsing, would argue that error handling is the most difficult aspect of good parsing.
In this early version of the `LixParser` error handling is very rudimentary, which is just an euphemisim for
_outright terrible_. Hopefully this will get better along with better support from the underlying PEG library
which is Minipeg.
### Bad number
This should not parse, but does
iex(14)> parse("42.")
{:ok, {:lit, 42}}
iex(15)> parse(".42")
{:error, "no alternative could be parsed in <binary>:1,1"}
Missing closing parens
iex(16)> parse("(")
{:error, "no alternative could be parsed in <binary>:1,1"}
Odd number of elements in a map
iex(17)> parse("{:a 1 :b}")
{:error, "no alternative could be parsed in <binary>:1,1"}
"""
@spec parse(binary()) :: result_t()
defdelegate parse(input), to: LixParser.Parser
@spec parse!(binary()) :: ast_t()
def parse!(input) do
# N.B. No wuth here
{:ok, ast} = parse(input)
ast
end
end
# SPDX-License-Identifier: Apache-2.0