Skip to content

Commit

Permalink
better documentation and support Base.parse
Browse files Browse the repository at this point in the history
  • Loading branch information
TotalVerb committed Jan 11, 2020
1 parent c071fdf commit 7fa6021
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 11 deletions.
44 changes: 39 additions & 5 deletions src/Lists/Lists.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
@reexport module Lists
"""
Provides a dynamically typed singly-linked list implementation, `List` similar to that
available in most lisp dialects. This module provides basic Julia functionality to
manipulate such lists, exported under both the Julia and Scheme names where they differ
(such as `Base.first` vs. `car`.
"""
module Lists

import FunctionalCollections: append
using Base.Iterators
Expand All @@ -8,16 +14,37 @@ const car = Base.first
export Cons, List, isnil, ispair, car, cdr, caar, cadr, cddr, nil, append, ++, cons, list,
islist, Nil

"""
An object representing nothing, or an empty list.
The singleton instance of this type is called `nil`. This type is isomorphic to, and very
similar, to `Nothing`. However, it is often useful to distinguish the `nil` used within many
lisp dialects from a true `Nothing`, because `nil` is iterable and represents an empty list.
"""
struct Nil end
const nil = Nil.instance

"""
An object which is essentially a pair of two objects.
The two objects are referred to, for historical reasons, as `car` and `cdr`.These
abbreviations are not semantically relevant today, so can generally be thought of as the
head object and the tail object. For `Cons` objects which are (proper) lists, the `car`
(head) object will be the first element of the list, and the `cdr` (tail) object will be a
list representing the remaining elements.
"""
struct Cons
car
cdr
end
const cons = Cons

car::Cons) = α.car
"""
The tail of a `List`.
Note that the tail need not be a list (or even a `List`) if the list is improper.
"""
cdr::Cons) = α.cdr
caar::Cons) = car(car(α))
cadr::Cons) = car(cdr(α))
Expand All @@ -34,11 +61,12 @@ end
Base.:(==)(α::Cons, β::Cons) = car(α) == car(β) && cdr(α) == cdr(β)

"""
An immutable linked list.
An immutable singly linked list, which may be improper.
A `List` is either `Nil` (empty) or `Cons`, where the first element is an arbitrary object
and the second element is a `List`. Note that this second requirement is not enforced by the
type system, since improper lists are allowable in many lisp dialects.
A list is either `Nil` (empty) or `Cons`, where the first element is an arbitrary object and
the second element is a list. The `List` type, however, includes all instances of `Cons`,
including those for which the second element is not a list. Such `List` instances are called
improper lists, which are allowed because they are used in many lisp dialects.
"""
const List = Union{Cons, Nil}

Expand All @@ -51,6 +79,12 @@ isnil(::Cons) = false

ispair::List) = !isnil(α)

"""
Return `true` if the provided `List` is in fact a list, i.e., it is not improper.
This is the case if the list is a `Nil` (empty list) or a `Cons` with a proper list as its
tail.
"""
islist(::Nil) = true
islist::Cons) = islist(cdr(α))

Expand Down
8 changes: 4 additions & 4 deletions src/Lists/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
=#

# TODO: missing lots of native s-expression types here. also non-native types
# TODO: improper list printing
"""
Convenience function to produce compact strings from (proper) lists of lisp objects.
"""
unparse::List) = "(" * join(unparse.(α), " ") * ")"
unparse(b::Bool) = b ? "#t" : "#f"
unparse(s::Symbol) = string(s)
Expand All @@ -28,12 +32,8 @@ space(ctx::ShowListContext) = max(5, ctx.limit - ctx.indent)
# performance is really not our concern
sindent(ctx::ShowListContext) = " " ^ ctx.indent

# “if we append or prepend, we should indent, not indented”
# no. indent is also a noun. indented makes it clear what this does
indented(ctx::ShowListContext, i=2) = ShowListContext(ctx.indent + i, ctx.limit)

# putting an ‘s’ in front of all functions that return strings is terrible style
# but sometimes terrible style is the best we’ve got
sprintwidth(α) = sum(charwidth(c) for c in unparse(α))

spprintall(ctx::ShowListContext, α) = join((β -> spprint(ctx, β)) α, '\n')
Expand Down
6 changes: 5 additions & 1 deletion src/Parser/Parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ end
"""
parse(s::AbstractString)
Read the given string `s` as a single s-expression.
Read the given string `s` as a single s-expression. The alternative vocabulary
`Base.parse(SExpression, s)` is also supported and may be preferred.
"""
function parse(s::AbstractString)
buf = IOBuffer(s)
Expand Down Expand Up @@ -337,6 +338,9 @@ parse(io::IO) = let x = nextobject(io)
end
end

# allow `parse(SExpression, o)`, which reads nicer than `SExpression.parse(o)`
Base.parse(::Type{SExpression}, o) = parse(o) :: SExpression

"""
parsefile(filename::AbstractString)
Expand Down
2 changes: 1 addition & 1 deletion src/SExpressions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ include("Parser/Parser.jl")

export SExpression
import .Parser: parse, parseall, parsefile, SExpression
using .Lists
@reexport using .Lists

macro sx_str(x::String)
QuoteNode(parse(x))
Expand Down
4 changes: 4 additions & 0 deletions test/parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,8 @@ end
@test @eval(sx"foo") == :foo
end

@testset "interface" begin
@test parse(SExpression, "(1 . + . (2 . * . 3))") == List(:+, 1, List(:*, 2, 3))
end

end

2 comments on commit 7fa6021

@TotalVerb
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request updated: JuliaRegistries/General/7729

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if Julia TagBot is installed, or can be done manually through the github interface, or via:

git tag -a v1.0.0 -m "<description of version>" 7fa602137a46d8a7538d4d33ce66d760225d86f2
git push origin v1.0.0

Please sign in to comment.