Replies: 1 comment 3 replies
-
Hi @gmedori, building on top of the arithmetic parser demo I was able to come up with this: import Parsing
import Testing
indirect enum Expr: Equatable {
case and(Expr, Expr)
case or(Expr, Expr)
case value(String)
}
@Suite struct ExprTests {
@Test func expr() throws {
var output: Expr!
let input = "A B or C or D E"[...].utf8
output = try ExprParser().parse(input)
#expect(
output
== .and(
.and(
.value("A"),
.or(
.or(
.value("B"),
.value("C")
),
.value("D")
)
),
.value("E")
)
)
}
}
struct ExprParser: Parser {
var body: some Parser<Substring.UTF8View, Expr> {
InfixOperator(associativity: .left) {
" ".utf8.map { Expr.and }
} lowerThan: {
Or()
}
}
}
struct Or: Parser {
var body: some Parser<Substring.UTF8View, Expr> {
InfixOperator(associativity: .left) {
" or ".utf8.map { Expr.or }
} lowerThan: {
Factor()
}
}
}
struct Factor: Parser {
var body: some Parser<Substring.UTF8View, Expr> {
OneOf {
Parse {
"(".utf8
ExprParser()
")".utf8
}
OneOf {
Prefix(while: { $0 != .init(ascii: " ") })
Rest()
}
.map { Expr.value(String(Substring($0))) }
}
}
}
public struct InfixOperator<Input, Operator: Parser, Operand: Parser>: Parser
where
Operator.Input == Input,
Operand.Input == Input,
Operator.Output == (Operand.Output, Operand.Output) -> Operand.Output
{
public let `associativity`: Associativity
public let operand: Operand
public let `operator`: Operator
@inlinable
public init(
associativity: Associativity,
@ParserBuilder<Input> _ operator: () -> Operator,
@ParserBuilder<Input> lowerThan operand: () -> Operand
) {
self.associativity = `associativity`
self.operand = operand()
self.operator = `operator`()
}
@inlinable
public func parse(_ input: inout Input) rethrows -> Operand.Output {
switch associativity {
case .left:
var lhs = try self.operand.parse(&input)
var rest = input
while true {
do {
let operation = try self.operator.parse(&input)
let rhs = try self.operand.parse(&input)
rest = input
lhs = operation(lhs, rhs)
} catch {
input = rest
return lhs
}
}
case .right:
var lhs: [(Operand.Output, Operator.Output)] = []
while true {
let rhs = try self.operand.parse(&input)
do {
let operation = try self.operator.parse(&input)
lhs.append((rhs, operation))
} catch {
return lhs.reversed().reduce(rhs) { rhs, pair in
let (lhs, operation) = pair
return operation(lhs, rhs)
}
}
}
}
}
}
public enum Associativity {
case left
case right
} The only thing it does not do is collapse nested or's (e.g. |
Beta Was this translation helpful? Give feedback.
3 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
So I am trying to write a ParserPrinter for an arbitrary boolean expression with the following rules:
expr1 expr2
, you should AND them together.expr1 or expr2
, you should OR them together.expr1 expr2 or expr3
is equivalent toexpr1 (expr2 or expr3)
.I already have parser-printers that can operate on individual terms, so now I'm just working on the boolean-ness. Here's my biggest requirement:
The resulting syntax tree should group together identical operations at the same depth in a list rather than a tree structure. What I mean by that can be visualized like so:
Given the following input where each single character is a term:
A B or C or D E
, the resulting syntax tree should look like this:So all the AND operations at the same level accumulate into a list rather than a binary tree, same as the OR operations one level deeper.
I've looked at a few examples from the repo for inspiration. Specifically, I looked at the arithmetic parser to understand how I might achieve precedence, and I looked at the JSON parser to see how I might achieve the plurality I'm looking for, but I've spent so much time thinking about this that my brain feels like mush and I'm having trouble piecing together the puzzle.
Can anyone provide any guidance as to how I might achieve what I'm looking for?
Beta Was this translation helpful? Give feedback.
All reactions