// emit passes an item back to the client. func (l *lexer) emit(t itemType) { if l.pos > ast.Pos(len(l.input)) { l.pos = ast.Pos(len(l.input)) } l.lastEmit = item{t, l.pos, l.input[l.start:l.pos]} l.items <- l.lastEmit l.start = l.pos }
// next returns the next rune in the input. func (l *lexer) next() (r rune) { if l.pos >= ast.Pos(len(l.input)) { l.width = 0 return eof } r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) l.pos += ast.Pos(l.width) return r }
func maybeEmitText(l *lexer, backup int) { if l.pos-ast.Pos(backup) > l.start { l.pos -= ast.Pos(backup) if allSpaceWithNewline(l.input[l.start:l.pos]) { l.ignore() } else { l.emit(itemText) } l.pos += ast.Pos(backup) } }
// scanNumber scans a number according to Soy's specification. // // It returns the scanned itemType (itemFloat or itemInteger) and a flag // indicating if an error was found. // // Floats must be in decimal and must either: // // - Have digits both before and after the decimal point (both can be // a single 0), e.g. 0.5, -100.0, or // - Have a lower-case e that represents scientific notation, // e.g. -3e-3, 6.02e23. // // Integers can be: // // - decimal (e.g. -827) // - hexadecimal (must begin with 0x and must use capital A-F, // e.g. 0x1A2B). func scanNumber(l *lexer) (typ itemType, ok bool) { typ = itemInteger // Optional leading sign. hasSign := l.accept("+-") if ast.Pos(len(l.input)) >= l.pos+2 && l.input[l.pos:l.pos+2] == "0x" { // Hexadecimal. if hasSign { // No signs for hexadecimals. return } l.acceptRun("0x") if !l.acceptRun(hexDigits) { // Requires at least one digit. return } if l.accept(".") { // No dots for hexadecimals. return } } else { // Decimal. if !l.acceptRun(decDigits) { // Requires at least one digit. return } if l.accept(".") { // Float. if !l.acceptRun(decDigits) { // Requires a digit after the dot. return } typ = itemFloat } else { if (!hasSign && l.input[l.start] == '0' && l.pos > l.start+1) || (hasSign && l.input[l.start+1] == '0' && l.pos > l.start+2) { // Integers can't start with 0. return } } if l.accept("e") { l.accept("+-") if !l.acceptRun(decDigits) { // A digit is required after the scientific notation. return } typ = itemFloat } } // Next thing must not be alphanumeric. if isAlphaNumeric(l.peek()) { l.next() return } ok = true return }
// lexLiteral scans until a closing literal delimiter, "{\literal}". // It emits the literal text and the closing tag. // // A literal section contains raw text and may include braces. // itemLiteral has already been emitted. func lexLiteral(l *lexer) stateFn { // emit the closing of the initial {literal} tag var ch = l.next() for isSpace(ch) { ch = l.next() } if ch != '}' { return l.errorf("expected closing tag after {literal..") } if l.doubleDelim && l.next() != '}' { return l.errorf("expected double closing braces in tag") } l.emit(itemRightDelim) // accept everything as itemText until we see the {/literal} var end bool var delimLen int for { if !l.doubleDelim && strings.HasPrefix(l.input[l.pos:], "{/literal}") { end, delimLen = true, 1 } else if l.doubleDelim && strings.HasPrefix(l.input[l.pos:], "{{/literal}}") { end, delimLen = true, 2 } if end { if l.pos > l.start { l.emit(itemText) } l.pos += ast.Pos(delimLen) l.emit(itemLeftDelim) l.pos += ast.Pos(len("/literal")) l.emit(itemLiteralEnd) l.pos += ast.Pos(delimLen) l.emit(itemRightDelim) return lexText } if l.next() == eof { return l.errorf("unclosed literal") } } return lexText }
// lexLiteral scans until a closing literal delimiter, "{\literal}". // It emits the literal text and the closing tag. // // A literal section contains raw text and may include braces. // itemLiteral has already been emitted. func lexLiteral(l *lexer) stateFn { // emit the closing of the initial {literal} tag var ch = l.next() for isSpace(ch) { ch = l.next() } if ch != '}' { return l.errorf("expected closing tag after {literal..") } if l.doubleDelim && l.next() != '}' { return l.errorf("expected double closing braces in tag") } l.emit(itemRightDelim) // Fast forward through the literal section. var expectClose, delimLen = "{/literal}", 1 if l.doubleDelim { expectClose, delimLen = "{{/literal}}", 2 } var i = strings.Index(l.input[l.pos:], expectClose) if i == -1 { return l.errorf("unclosed literal") } l.pos += ast.Pos(i) // Accept everything as itemText until we see the {/literal} // Emit the other various tokens. if i > 0 { l.emit(itemText) } l.pos += ast.Pos(delimLen) l.emit(itemLeftDelim) l.pos += ast.Pos(len("/literal")) l.emit(itemLiteralEnd) l.pos += ast.Pos(delimLen) l.emit(itemRightDelim) return lexText }
func lexSoyDocParam(l *lexer) { l.pos += ast.Pos(len("@param")) switch ch := l.next(); { case ch == '?': if l.next() != ' ' { return } l.backup() l.emit(itemSoyDocOptionalParam) case ch == ' ': l.backup() l.emit(itemSoyDocParam) default: return // what a fakeout } // skip all spaces for { var r = l.next() if r == eof || !isSpace(r) { break } } l.backup() l.ignore() // extract the param for { var r = l.next() if isSpaceEOL(r) || r == eof { l.pos-- l.emit(itemIdent) // don't skip newlines. the outer routine needs to know about it if isSpace(r) || r == eof { l.pos++ } l.ignore() break } } }
// backup steps back one rune. Can only be called once per call of next. func (l *lexer) backup() { l.pos -= ast.Pos(l.width) }