Skip to content

Commit dd58e80

Browse files
committed
Fix parsing ambiguity
1 parent bf28cc7 commit dd58e80

File tree

3 files changed

+69
-2
lines changed

3 files changed

+69
-2
lines changed

ShapeScript/Lexer.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,27 @@ public func tokenize(_ input: String) throws -> [Token] {
1616
var spaceBefore = true
1717
_ = characters.skipWhitespaceAndComments()
1818
while let token = try characters.readToken(spaceBefore: spaceBefore) {
19-
if token.type != .linebreak || tokens.last?.type != .linebreak {
19+
switch (tokens.last?.type, token.type) {
20+
case (.linebreak?, .linebreak):
21+
break // Skip duplicate linebreak
22+
case (.identifier?, .lparen) where spaceBefore && tokens.count > 1:
23+
switch tokens[tokens.count - 2].type {
24+
case .infix, .prefix:
25+
// Insert parens for disambiguation
26+
let identifier = tokens.removeLast()
27+
let range = identifier.range
28+
let lRange = range.lowerBound ..< range.lowerBound
29+
let rRange = range.upperBound ..< range.upperBound
30+
tokens += [
31+
Token(type: .lparen, range: lRange),
32+
identifier,
33+
Token(type: .rparen, range: rRange),
34+
token,
35+
]
36+
default:
37+
tokens.append(token)
38+
}
39+
default:
2040
tokens.append(token)
2141
}
2242
spaceBefore = characters.skipWhitespaceAndComments()
@@ -40,7 +60,7 @@ public func tokenize(_ input: String) throws -> [Token] {
4060
return tokens
4161
}
4262

43-
/// Note: only includes keywords that start a command, not joining words
63+
// Note: only includes keywords that start a command, not joining words
4464
public enum Keyword: String, CaseIterable {
4565
case define
4666
case `for`

ShapeScriptTests/InterpreterTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2237,6 +2237,34 @@ class InterpreterTests: XCTestCase {
22372237
XCTAssertEqual(delegate.log, [2])
22382238
}
22392239

2240+
func testFunctionAmbiguity() {
2241+
let program = "print -cos(pi) + sin(pi)"
2242+
let delegate = TestDelegate()
2243+
XCTAssertNoThrow(try evaluate(parse(program), delegate: delegate))
2244+
XCTAssertEqual(delegate.log, [-cos(Double.pi) + sin(.pi)])
2245+
}
2246+
2247+
func testFunctionAmbiguity2() {
2248+
let program = """
2249+
define a 1
2250+
define b 2
2251+
print -a (pi) + b (pi)
2252+
"""
2253+
let delegate = TestDelegate()
2254+
XCTAssertNoThrow(try evaluate(parse(program), delegate: delegate))
2255+
XCTAssertEqual(delegate.log, [-1, Double.pi + 2, Double.pi])
2256+
}
2257+
2258+
func testFunctionAmbiguity3() {
2259+
let program = """
2260+
define a 1
2261+
print -a (pi)
2262+
"""
2263+
let delegate = TestDelegate()
2264+
XCTAssertNoThrow(try evaluate(parse(program), delegate: delegate))
2265+
XCTAssertEqual(delegate.log, [-1, Double.pi])
2266+
}
2267+
22402268
// MARK: Numeric comparison
22412269

22422270
func testGT() {

ShapeScriptTests/LexerTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,25 @@ class LexerTests: XCTestCase {
7676
}
7777
}
7878

79+
func testInsertPhantomParensForDisambiguation() {
80+
let input = "-a (b)"
81+
let tokens: [TokenType] = [
82+
.prefix(.minus), .lparen, .identifier("a"), .rparen,
83+
.lparen, .identifier("b"), .rparen, .eof,
84+
]
85+
XCTAssertEqual(try tokenize(input).map { $0.type }, tokens)
86+
}
87+
88+
func testInsertPhantomParensForDisambiguation2() {
89+
let input = "a + b (c)"
90+
let tokens: [TokenType] = [
91+
.identifier("a"), .infix(.plus),
92+
.lparen, .identifier("b"), .rparen,
93+
.lparen, .identifier("c"), .rparen, .eof,
94+
]
95+
XCTAssertEqual(try tokenize(input).map { $0.type }, tokens)
96+
}
97+
7998
// MARK: numbers
8099

81100
func testZero() {

0 commit comments

Comments
 (0)