package jslang // The literals of JavaScript that need more than a helper call: template // literals, which may cross lines, and numbers, which come in more shapes than // a run of digits. import "rickub.com/turbo-editors/turbo-core/syntax" // --- template literals ------------------------------------------------------- // openTemplate colours a template literal that starts here, and reports // whether it also ended on this line. // // The whole literal is a string, interpolations included. Colouring the code // inside ${…} would mean the scanner reaching back into itself with a nesting // depth to carry, for a construct that is usually one short expression; the // cost accepted is that a backtick inside an interpolation ends the literal // early. The reference says so. func openTemplate(s *syntax.LineScanner) bool { start := s.Pos() s.Advance(1) // past the opening backtick, so it is not found as the closing one return takeTemplateFrom(s, start) } // finishTemplate colours the continuation of a template opened on an earlier // line, and reports whether it ended on this one. func finishTemplate(s *syntax.LineScanner) bool { return takeTemplateFrom(s, s.Pos()) } // takeTemplateFrom colours from start to the end of the template, or to the // end of the line when it does not close here. // // The start is passed in rather than read back from the scanner afterwards: // runToBacktick has already moved the position, and Emit drops empty spans, so // anything that tried to colour "what is left" would colour nothing at all. func takeTemplateFrom(s *syntax.LineScanner, start int) bool { closed := runToBacktick(s) if !closed { s.Advance(s.Len() - s.Pos()) } s.Emit(start, s.Pos(), syntax.ClassString) return closed } // runToBacktick advances to just past the next unescaped backtick, and reports // whether it found one. func runToBacktick(s *syntax.LineScanner) bool { for !s.AtEnd() { switch s.Peek(0) { case '\\': s.Advance(2) case '`': s.Advance(1) return true default: s.Advance(1) } } return false } // --- numbers ----------------------------------------------------------------- // takeNumber colours a numeric literal in every shape the language has: // 1_000, 0xFF, 0o17, 0b1010, 1.5e-3, .5, and the trailing n of a BigInt. // // The n is taken as part of the number rather than as an identifier beside it, // because 10n is one literal. A dot is part of the number only when a digit // follows it, so 1.toString() — invalid, but typed — stops at the 1, and the // sign after an exponent's e is part of the number only outside a hexadecimal // literal, where 0xE + 1 is a sum and not an exponent. func takeNumber(s *syntax.LineScanner) { start := s.Pos() hexadecimal := s.Peek(0) == '0' && (s.Peek(1) == 'x' || s.Peek(1) == 'X') s.Advance(1) for !s.AtEnd() { r := s.Peek(0) switch { case syntax.IsWordRune(r) || (r == '.' && syntax.IsDigit(s.Peek(1))): s.Advance(1) case (r == '+' || r == '-') && !hexadecimal && isExponent(s.Peek(-1)): s.Advance(1) default: s.Emit(start, s.Pos(), syntax.ClassNumber) return } } s.Emit(start, s.Pos(), syntax.ClassNumber) } // isExponent reports whether a rune is the e of an exponent, which is what // makes the sign after it part of the number rather than an operator. func isExponent(r rune) bool { return r == 'e' || r == 'E' }