Skip to content

Commit ac824d1

Browse files
committed
refactor readString to prep for blockStringValue impl and additional tests.
add tests for empty string and blockstring. take out trimming of string since that is moving to blockStringValue(rawString:) next. update comment for readString
1 parent 610bf0d commit ac824d1

File tree

3 files changed

+70
-33
lines changed

3 files changed

+70
-33
lines changed

Sources/GraphQL/Language/AST.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ final public class Token {
5555
case int = "Int"
5656
case float = "Float"
5757
case string = "String"
58+
case blockString = "BlockString"
5859
case comment = "Comment"
5960

6061
public var description: String {

Sources/GraphQL/Language/Lexer.swift

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -540,18 +540,44 @@ func readDigits(source: Source, start: Int, firstCode: UInt8) throws -> Int {
540540
}
541541

542542
/**
543-
* Reads a string token from the source file.
543+
* Reads a `.string` token from the source file.
544544
*
545545
* "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
546+
*
547+
* augmented to support blockstrings """ """ and return `.blockString` token if found.
546548
*/
547549
func readString(source: Source, start: Int, line: Int, col: Int, prev: Token) throws -> Token {
550+
let token = try readRawString(source: source, start: start, line: line, col: col, prev: prev)
551+
552+
if token.kind == .blockString,
553+
let rawString = token.value {
554+
let valueString = blockStringValue(rawValue: rawString)
555+
return Token(kind: token.kind,
556+
start: token.start,
557+
end: token.end,
558+
line: token.line,
559+
column: token.column,
560+
value: valueString,
561+
prev: token.prev,
562+
next: token.next)
563+
}
564+
return token
565+
}
566+
567+
/** Reads a raw string token from the source.
568+
*
569+
* Doesn't do any clean up of leading indentations or trailing whitespace for blockstring lines;
570+
* so if `token.kind` == `.blockString`, call `blockStringValue` with `token.value` for that.
571+
*
572+
* returns: Token of kind `.string` or `.blockString`
573+
*/
574+
func readRawString(source: Source, start: Int, line: Int, col: Int, prev: Token) throws -> Token {
548575
let body = source.body
549576
var positionIndex = body.utf8.index(body.utf8.startIndex, offsetBy: start + 1)
550577
var chunkStartIndex = positionIndex
551578
var currentCode: UInt8? = 0
552579
var value = ""
553580
var blockString = false
554-
var chunkEndTrim = 0
555581

556582
// if we have minimum 5 more quotes worth of characters left after eating the first quote, check for block quote
557583
// body.utf8.index(positionIndex, offsetBy: 5) < body.utf8.endIndex
@@ -593,12 +619,7 @@ func readString(source: Source, start: Int, line: Int, col: Int, prev: Token) th
593619
codeNext == 34,
594620
let codeNextNext = body.charCode(at: body.utf8.index(after: body.utf8.index(after: positionIndex))),
595621
codeNextNext == 34 {
596-
// if closing """ is on a line by itself then we set chunkEndTrim to 1 to trim the last return before it
597-
if let code = body.charCode(at: body.utf8.index(before: positionIndex)),
598-
(code == 0x000A || code == 0x000D) {
599-
chunkEndTrim = 1 // flag the need to trim the last return
600-
}
601-
positionIndex = body.utf8.index(after: body.utf8.index(after: positionIndex)) // so we clean up on exit
622+
positionIndex = body.utf8.index(after: body.utf8.index(after: positionIndex)) // so we clean up quotes on exit
602623
break
603624
}
604625

@@ -676,22 +697,26 @@ func readString(source: Source, start: Int, line: Int, col: Int, prev: Token) th
676697
)
677698
}
678699

679-
let valueRangeEnd = body.utf8.index(positionIndex, offsetBy: (blockString ? -2 - chunkEndTrim : 0))
680-
value += String(body.utf8[chunkStartIndex ..< valueRangeEnd])!
700+
if blockString {
701+
let valueRangeEnd = body.utf8.index(positionIndex, offsetBy: -2)
702+
if chunkStartIndex < valueRangeEnd { // empty string?
703+
value += String(body.utf8[chunkStartIndex ..< valueRangeEnd])!
704+
}
705+
} else {
706+
value += String(body.utf8[chunkStartIndex ..< positionIndex])!
707+
}
681708

682-
return Token(
683-
kind: .string,
684-
start: start,
685-
end: body.offset(of: positionIndex) + 1,
686-
line: line,
687-
column: col,
688-
value: value,
689-
prev: prev
690-
)
709+
return Token(kind: blockString ? .blockString : .string,
710+
start: start,
711+
end: body.offset(of: positionIndex) + 1,
712+
line: line,
713+
column: col,
714+
value: value,
715+
prev: prev)
691716
}
692717

693718
/**
694-
* BlockStringValue(rawValue: String)
719+
* blockStringValue(rawValue: String)
695720
*
696721
* Transcription of the algorithm specified in the [spec](http://spec.graphql.org/draft/#BlockStringValue())
697722
*
@@ -723,8 +748,8 @@ func readString(source: Source, start: Int, line: Int, col: Int, prev: Token) th
723748
*/
724749

725750
func blockStringValue(rawValue: String) -> String {
726-
assert(false, "implement this!")
727-
return ""
751+
print("\n\n **** blockStringValue Not Yet Implemented **** \n\n")
752+
return rawValue
728753
}
729754

730755
/**

Tests/GraphQLTests/LanguageTests/LexerTests.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,9 @@ class LexerTests : XCTestCase {
209209
}
210210

211211
func testMultiLineStrings() throws {
212-
let token = try lexOne(#" """ Multi-line string\n With Inner "foo" \n should be Valid """ "#)
213-
XCTAssertEqual(token.start, 1)
214-
XCTAssert(token.kind == .string)
215-
212+
let token = try lexOne(#" """ Multi-line string\n With Inner "foo" \n should be Valid """ "#)
216213
let expected = Token(
217-
kind: .string,
214+
kind: .blockString,
218215
start: 1,
219216
end: 64,
220217
line: 1,
@@ -235,12 +232,12 @@ class LexerTests : XCTestCase {
235232
"""#)
236233

237234
let expected = Token(
238-
kind: .string,
235+
kind: .blockString,
239236
start: 0,
240237
end: 59,
241238
line: 1,
242239
column: 1,
243-
value: " Multi-line string\nwith Inner \"foo\"\nshould be valid"
240+
value: " Multi-line string\nwith Inner \"foo\"\nshould be valid\n"
244241
)
245242

246243
XCTAssertEqual(token, expected, "expected: \n \(dump(expected))\ngot: \n\(dump(token))\n")
@@ -269,13 +266,27 @@ class LexerTests : XCTestCase {
269266
}
270267

271268

272-
func fails_testEmptyQuote() throws {
273-
XCTFail("Implement This!")
269+
func testEmptyQuote() throws {
270+
let token = try lexOne(#" "" "#)
271+
let expected = Token(kind: .string, start: 1, end: 3, line: 1, column: 2, value: "")
272+
XCTAssertEqual(token, expected, "\n\(dump(expected))\n\(dump(token))\n")
274273
}
275274

276-
func fails_testEmptyBlockQuote() throws {
277-
XCTFail("Implement This!")
275+
func testEmptySimpleMultilineBlockQuote() throws {
276+
let token = try lexOne(#" """""" "#)
277+
let expected = Token(kind: .blockString, start: 1, end: 7, line: 1, column: 2, value: "")
278+
XCTAssertEqual(token, expected, "\n\(dump(expected))\n\(dump(token))\n")
278279
}
280+
281+
func testEmptyTrimmedCharactersMultilineBlockQuote() throws {
282+
let token = try lexOne(#"""
283+
"""
284+
"""
285+
"""#)
286+
let expected = Token(kind: .blockString, start: 0, end: 7, line: 1, column: 1, value: "")
287+
XCTAssertEqual(token, expected, "\n\(dump(expected))\n\(dump(token))\n")
288+
}
289+
279290

280291
func testStringErrors() throws {
281292
XCTAssertThrowsError(try lexOne("\""))

0 commit comments

Comments
 (0)