package gololang // The quoted literals of Golo: "strings", """multi-line strings""" and // 'characters', and the one rule that governs all three — each runs to its // closing quote, wherever that quote is. import "rickub.com/turbo-editors/turbo-core/syntax" // multilineStringQuote opens and closes a multi-line string. Inside one the // lexer interprets nothing — no escape, no interpolation — so the only thing // that can end it is these three runes. const multilineStringQuote = `"""` // takeQuoted colours a "…" or '…' literal from its opening quote to its // closing one, and reports it as still open when the line ends first. // // Carrying it is the honest reading of the language. The interpreter reads a // string to its closing quote and nothing stops it at a newline, so a line // ending inside a literal is not broken source — it is a literal with a line // break in it — and stopping the colour at the line would draw the next line // as code the interpreter will never run as code. // // A character literal is read the same way, by the same loop in lexer.go, so // it is carried the same way. That a 'character' spanning lines is nonsense to // a reader is beside the point: the colour says what the interpreter will do. func takeQuoted(s *syntax.LineScanner, quote rune, class syntax.Class, ifOpen carry) carry { start := s.Pos() s.Advance(1) // the opening quote closed := consumeQuoted(s, quote) s.Emit(start, s.Pos(), class) return stillOpen(closed, ifOpen) } // finishQuoted colours the rest of a literal opened on an earlier line, and // says whether it is still open at the end of this one. func finishQuoted(s *syntax.LineScanner, quote rune, class syntax.Class, ifOpen carry) carry { closed := consumeQuoted(s, quote) s.Emit(0, s.Pos(), class) return stillOpen(closed, ifOpen) } // consumeQuoted runs to the closing quote, or to the end of the line, and // reports which it found. // // A backslash takes the rune after it out of consideration. That one rule // covers every escape the lexer knows — \n, \t, \r, \\, \", \', \0 and \xHH — // and the ones it does not, which it keeps as the character itself; all any of // them need from this scanner is that the rune after the backslash cannot // close the literal. A backslash that ends the line escapes the newline, which // the lexer also reads as part of the string, and the carry covers that. func consumeQuoted(s *syntax.LineScanner, quote rune) bool { for !s.AtEnd() { switch s.Peek(0) { case '\\': s.Advance(2) case quote: s.Advance(1) return true default: s.Advance(1) } } return false } // takeMultilineString colours a """…""" literal from its opening quotes to its // closing ones, and reports it as still open when the line ends first. // // syntax.OpenBlockComment is named for comments, and what it does is exactly // this: from an opener to the first closer, or to the end of the line, in one // class, with no escapes considered. A multi-line string in Golo has no // escapes either — the lexer appends every rune it meets until the three // quotes — so the helper is the right tool under a misleading name, and the // continuation on later lines goes through FinishBlockComment for the same // reason. func takeMultilineString(s *syntax.LineScanner) carry { closed := syntax.OpenBlockComment(s, multilineStringQuote, multilineStringQuote, syntax.ClassString) return stillOpen(closed, multilineStringOpen) }