Recursive Descent into Madness
Madness is a Swift µframework for parsing strings in simple context-free grammars. Combine parsers from simple Swift expressions and parse away:
let digit = %("0"..."9") <|> %("a"..."f") <|> %("A"..."F")
let hex = digit+ |> map { strtol(join("", $0), nil, 16) }
parse(%"0x" *> hex, "0xdeadbeef") // => 3,735,928,559
Your parsers can produce your own model objects directly, making Madness ideal for experimenting with grammars, for example in a playground.
See Madness.playground
for some examples of parsing with Madness.
Use
-
Lexing
Madness can be used to write lexers, lexeme parsers, and scannerless parsers. @bencochran has built a lexer and parser for the LLVM tutorial language, Kaleidoscope.
-
Any
any
parses any single character.
-
Strings
%"hello"
parses the string “hello”.
-
Ranges
%("a"..."z")
parses any lowercase letter from “a” to “z” inclusive.
-
Concatenation
x <*> y <*> z
parses
x
followed byy
and produces parses as(X, Y)
. -
Alternation
x <|> y
parses
x
, and if it fails,y
, and produces parses asEither<X, Y>
. Ifx
andy
are of the same type, then it produces parses asX
.oneOf([x1, x2, x3])
tries a sequence of parsers until the first success, producing parses as
X
.anyOf(["x", "y", "z"])
tries to parse one each of a set of literals in sequence, collecting each successful parse into an array until none match.
allOf(["x", "y", "z"])
greedier than
anyOf
, parsing every match from a set of literals in sequence, including duplicates. -
Repetition
x*
parses
x
0 or more times, producing parses as[X]
.x+
parses
x
one or more times.x * 3
parses
x
exactly three times.x * (3..<6)
parses
x
three to five times. UseInt.max
for the upper bound to parse three or more times. -
Mapping
x |> map { $0 } { $0 } <^> x x --> { _, _, y in y }
parses
x
and maps its parse trees using the passed function. Use mapping to build your model objects.-->
passes the input and parsed range as well as the parsed data for e.g. error reporting or AST construction. -
Ignoring
Some text is just decoration.
x *> y
parsesx
and theny
just like<*>
, but drops the result ofx
.x <* y
does the same, but drops the result ofy
.
API documentation is in the source.
This way Madness lies
∞ loop de loops
Madness employs simple—naïve, even—recursive descent parsing. Among other things, that means that it can’t parse any arbitrary grammar that you could construct with it. In particular, it can’t parse left-recursive grammars:
let number = %("0"..."9")
let addition = expression <*> %"+" <*> expression
let expression = addition <|> number
expression
is left-recursive: its first term is addition
, whose first term is expression
. This will cause infinite loops every time expression
is invoked; try to avoid it.
@numist
I love ambiguity more thanAlternations try their left operand before their operand, and are short-circuiting. This means that they disambiguate (arbitrarily) to the left, which can be handy; but this can have unintended consequences. For example, this parser:
%"x" <|> %"xx"
will not parse “xx” completely.
Integration
- Add this repository as a submodule and check out its dependencies, and/or add it to your Cartfile if you’re using carthage to manage your dependencies.
- Drag
Madness.xcodeproj
into your project or workspace, and do the same with its dependencies (i.e. the other.xcodeproj
files included inMadness.xcworkspace
). NB:Madness.xcworkspace
is for standalone development of Madness, whileMadness.xcodeproj
is for targets using Madness as a dependency. - Link your target against
Madness.framework
and each of the dependency frameworks. - Application targets should ensure that the framework gets copied into their application bundle. (Framework targets should instead require the application linking them to include Madness and its dependencies.)