-
Notifications
You must be signed in to change notification settings - Fork 0
Calculator example
Import SwiLex
to define terminal tokens needed by the SwiLex
lexer. :
import SwiLex
Import SwiParse
to define a parser and its grammar:
import SwiParse
In this example we will create a parser for the following grammar to build a simple calculator :
<expr> → <expr> + <term>
| <expr> - <term>
| <term>
<term> → <term> * <factor>
| <term> / <factor>
| <factor>
<fact> → ( <expr> )
| number
Define a SwiLaxable
enum to list the terminal tokens of the grammar with their corresponding regular expressions:
enum Terminal: String, SwiLexable {
static var separators: Set<Character> = [" "]
case number = "[0-9]+"
case plus = #"\+"#
case minus = #"-"#
case times = #"\*"#
case divide = #"/"#
case lParenthesis = #"\("#
case rParenthesis = #"\)"#
case eof
case none // empty token
}
Define a SwiParsable
enum to list the non-terminal tokens used in the grammar:
enum NonTerminal: SwiParsable {
case expr
case fact
case term
case start // starting token
}
Define the reduction actions for each rule of the grammar :
func sum(a: Int, op _: Substring, b: Int) -> Int { a + b }
func minus(a: Int, op _: Substring, b: Int) -> Int { a - b }
func times(a: Int, op _: Substring, b: Int) -> Int { a * b }
func divide(a: Int, op _: Substring, b: Int) -> Int { a / b }
func parenthesis(_: Substring, a: Int, _: Substring) -> Int { a }
func toInt(s: Substring) -> Int { Int(s) ?? 0 }
func keepValue(a: Int) -> Int { a }
In a grammar, each rule leads to the reduction of one or more terminal / non-terminal tokens (the right part of the rule) into a non-terminal token (the left part of the rule) by applying a reduction action.
The reduction action of a rule in SwiParse
takes the value of each token being reduced by the rule as argument (the right part of the rule) and returns the new value of the reduced non-terminal token (the left part of the rule).
Terminal tokens' values are passed as Substrings (directly read by the SwiLex
lexer) while non-terminal ones are the output of the rule that produced them (a non-terminal token is necessarily the result of a reduction rule thus its value is the output of the rule's reduction action).
Next, define a parser with the grammar by providing its rules and their reduction actions:
let parser = try SwiParse<Terminal, NonTerminal>(rules: [
.start => [Word(.expr)], // accepting rule
.expr => [Word(.expr), Word(.plus), Word(.term)] >>>> sum,
.expr => [Word(.expr), Word(.minus), Word(.term)] >>>> minus,
.expr => [Word(.term)] >>>> keepValue,
.term => [Word(.term), Word(.times), Word(.fact)] >>>> times,
.term => [Word(.term), Word(.divide), Word(.fact)] >>>> divide,
.term => [Word(.fact)] >>>> keepValue,
.fact => [Word(.lParenthesis), Word(.expr), Word(.rParenthesis)] >>>> parenthesis,
.fact => [Word(.number)] >>>> toInt,
])
Note that each grammar should have a single starting rule that will be used as
Finally, parse an input string using the defined parser:
let result = try parser.parse(input: "(1 + 2) * 3")
// 9
A mistake? A bug? Please report it by opening an issue or via twitter.