URL
https://opencores.org/ocsvn/openrisc/openrisc/trunk
Subversion Repositories openrisc
[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [libgo/] [go/] [text/] [scanner/] [scanner.go] - Rev 747
Compare with Previous | Blame | View Log
// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// Package scanner provides a scanner and tokenizer for UTF-8-encoded text.// It takes an io.Reader providing the source, which then can be tokenized// through repeated calls to the Scan function. For compatibility with// existing tools, the NUL character is not allowed (implementation// restriction).//// By default, a Scanner skips white space and Go comments and recognizes all// literals as defined by the Go language specification. It may be// customized to recognize only a subset of those literals and to recognize// different white space characters.//// Basic usage pattern://// var s scanner.Scanner// s.Init(src)// tok := s.Scan()// for tok != scanner.EOF {// // do something with tok// tok = s.Scan()// }//package scannerimport ("bytes""fmt""io""os""unicode""unicode/utf8")// TODO(gri): Consider changing this to use the new (token) Position package.// A source position is represented by a Position value.// A position is valid if Line > 0.type Position struct {Filename string // filename, if anyOffset int // byte offset, starting at 0Line int // line number, starting at 1Column int // column number, starting at 1 (character count per line)}// IsValid returns true if the position is valid.func (pos *Position) IsValid() bool { return pos.Line > 0 }func (pos Position) String() string {s := pos.Filenameif pos.IsValid() {if s != "" {s += ":"}s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)}if s == "" {s = "???"}return s}// Predefined mode bits to control recognition of tokens. For instance,// to configure a Scanner such that it only recognizes (Go) identifiers,// integers, and skips comments, set the Scanner's Mode field to://// ScanIdents | ScanInts | SkipComments//const (ScanIdents = 1 << -IdentScanInts = 1 << -IntScanFloats = 1 << -Float // includes IntsScanChars = 1 << -CharScanStrings = 1 << -StringScanRawStrings = 1 << -RawStringScanComments = 1 << -CommentSkipComments = 1 << -skipComment // if set with ScanComments, comments become white spaceGoTokens = ScanIdents | ScanFloats | ScanChars | ScanStrings | ScanRawStrings | ScanComments | SkipComments)// The result of Scan is one of the following tokens or a Unicode character.const (EOF = -(iota + 1)IdentIntFloatCharStringRawStringCommentskipComment)var tokenString = map[rune]string{EOF: "EOF",Ident: "Ident",Int: "Int",Float: "Float",Char: "Char",String: "String",RawString: "RawString",Comment: "Comment",}// TokenString returns a (visible) string for a token or Unicode character.func TokenString(tok rune) string {if s, found := tokenString[tok]; found {return s}return fmt.Sprintf("%q", string(tok))}// GoWhitespace is the default value for the Scanner's Whitespace field.// Its value selects Go's white space characters.const GoWhitespace = 1<<'\t' | 1<<'\n' | 1<<'\r' | 1<<' 'const bufLen = 1024 // at least utf8.UTFMax// A Scanner implements reading of Unicode characters and tokens from an io.Reader.type Scanner struct {// Inputsrc io.Reader// Source buffersrcBuf [bufLen + 1]byte // +1 for sentinel for common case of s.next()srcPos int // reading position (srcBuf index)srcEnd int // source end (srcBuf index)// Source positionsrcBufOffset int // byte offset of srcBuf[0] in sourceline int // line countcolumn int // character countlastLineLen int // length of last line in characters (for correct column reporting)lastCharLen int // length of last character in bytes// Token text buffer// Typically, token text is stored completely in srcBuf, but in general// the token text's head may be buffered in tokBuf while the token text's// tail is stored in srcBuf.tokBuf bytes.Buffer // token text head that is not in srcBuf anymoretokPos int // token text tail position (srcBuf index); valid if >= 0tokEnd int // token text tail end (srcBuf index)// One character look-aheadch rune // character before current srcPos// Error is called for each error encountered. If no Error// function is set, the error is reported to os.Stderr.Error func(s *Scanner, msg string)// ErrorCount is incremented by one for each error encountered.ErrorCount int// The Mode field controls which tokens are recognized. For instance,// to recognize Ints, set the ScanInts bit in Mode. The field may be// changed at any time.Mode uint// The Whitespace field controls which characters are recognized// as white space. To recognize a character ch <= ' ' as white space,// set the ch'th bit in Whitespace (the Scanner's behavior is undefined// for values ch > ' '). The field may be changed at any time.Whitespace uint64// Start position of most recently scanned token; set by Scan.// Calling Init or Next invalidates the position (Line == 0).// The Filename field is always left untouched by the Scanner.// If an error is reported (via Error) and Position is invalid,// the scanner is not inside a token. Call Pos to obtain an error// position in that case.Position}// Init initializes a Scanner with a new source and returns s.// Error is set to nil, ErrorCount is set to 0, Mode is set to GoTokens,// and Whitespace is set to GoWhitespace.func (s *Scanner) Init(src io.Reader) *Scanner {s.src = src// initialize source buffer// (the first call to next() will fill it by calling src.Read)s.srcBuf[0] = utf8.RuneSelf // sentinels.srcPos = 0s.srcEnd = 0// initialize source positions.srcBufOffset = 0s.line = 1s.column = 0s.lastLineLen = 0s.lastCharLen = 0// initialize token text buffer// (required for first call to next()).s.tokPos = -1// initialize one character look-aheads.ch = -1 // no char read yet// initialize public fieldss.Error = nils.ErrorCount = 0s.Mode = GoTokenss.Whitespace = GoWhitespaces.Line = 0 // invalidate token positionreturn s}// TODO(gri): The code for next() and the internal scanner state could benefit// from a rethink. While next() is optimized for the common ASCII// case, the "corrections" needed for proper position tracking undo// some of the attempts for fast-path optimization.// next reads and returns the next Unicode character. It is designed such// that only a minimal amount of work needs to be done in the common ASCII// case (one test to check for both ASCII and end-of-buffer, and one test// to check for newlines).func (s *Scanner) next() rune {ch, width := rune(s.srcBuf[s.srcPos]), 1if ch >= utf8.RuneSelf {// uncommon case: not ASCII or not enough bytesfor s.srcPos+utf8.UTFMax > s.srcEnd && !utf8.FullRune(s.srcBuf[s.srcPos:s.srcEnd]) {// not enough bytes: read some more, but first// save away token text if anyif s.tokPos >= 0 {s.tokBuf.Write(s.srcBuf[s.tokPos:s.srcPos])s.tokPos = 0// s.tokEnd is set by Scan()}// move unread bytes to beginning of buffercopy(s.srcBuf[0:], s.srcBuf[s.srcPos:s.srcEnd])s.srcBufOffset += s.srcPos// read more bytes// (an io.Reader must return io.EOF when it reaches// the end of what it is reading - simply returning// n == 0 will make this loop retry forever; but the// error is in the reader implementation in that case)i := s.srcEnd - s.srcPosn, err := s.src.Read(s.srcBuf[i:bufLen])s.srcPos = 0s.srcEnd = i + ns.srcBuf[s.srcEnd] = utf8.RuneSelf // sentinelif err != nil {if s.srcEnd == 0 {if s.lastCharLen > 0 {// previous character was not EOFs.column++}s.lastCharLen = 0return EOF}if err != io.EOF {s.error(err.Error())}// If err == EOF, we won't be getting more// bytes; break to avoid infinite loop. If// err is something else, we don't know if// we can get more bytes; thus also break.break}}// at least one bytech = rune(s.srcBuf[s.srcPos])if ch >= utf8.RuneSelf {// uncommon case: not ASCIIch, width = utf8.DecodeRune(s.srcBuf[s.srcPos:s.srcEnd])if ch == utf8.RuneError && width == 1 {// advance for correct error positions.srcPos += widths.lastCharLen = widths.column++s.error("illegal UTF-8 encoding")return ch}}}// advances.srcPos += widths.lastCharLen = widths.column++// special situationsswitch ch {case 0:// implementation restriction for compatibility with other toolss.error("illegal character NUL")case '\n':s.line++s.lastLineLen = s.columns.column = 0}return ch}// Next reads and returns the next Unicode character.// It returns EOF at the end of the source. It reports// a read error by calling s.Error, if not nil; otherwise// it prints an error message to os.Stderr. Next does not// update the Scanner's Position field; use Pos() to// get the current position.func (s *Scanner) Next() rune {s.tokPos = -1 // don't collect token texts.Line = 0 // invalidate token positionch := s.Peek()s.ch = s.next()return ch}// Peek returns the next Unicode character in the source without advancing// the scanner. It returns EOF if the scanner's position is at the last// character of the source.func (s *Scanner) Peek() rune {if s.ch < 0 {s.ch = s.next()}return s.ch}func (s *Scanner) error(msg string) {s.ErrorCount++if s.Error != nil {s.Error(s, msg)return}pos := s.Positionif !pos.IsValid() {pos = s.Pos()}fmt.Fprintf(os.Stderr, "%s: %s\n", pos, msg)}func (s *Scanner) scanIdentifier() rune {ch := s.next() // read character after first '_' or letterfor ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch) {ch = s.next()}return ch}func digitVal(ch rune) int {switch {case '0' <= ch && ch <= '9':return int(ch - '0')case 'a' <= ch && ch <= 'f':return int(ch - 'a' + 10)case 'A' <= ch && ch <= 'F':return int(ch - 'A' + 10)}return 16 // larger than any legal digit val}func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' }func (s *Scanner) scanMantissa(ch rune) rune {for isDecimal(ch) {ch = s.next()}return ch}func (s *Scanner) scanFraction(ch rune) rune {if ch == '.' {ch = s.scanMantissa(s.next())}return ch}func (s *Scanner) scanExponent(ch rune) rune {if ch == 'e' || ch == 'E' {ch = s.next()if ch == '-' || ch == '+' {ch = s.next()}ch = s.scanMantissa(ch)}return ch}func (s *Scanner) scanNumber(ch rune) (rune, rune) {// isDecimal(ch)if ch == '0' {// int or floatch = s.next()if ch == 'x' || ch == 'X' {// hexadecimal intch = s.next()for digitVal(ch) < 16 {ch = s.next()}} else {// octal int or floatseenDecimalDigit := falsefor isDecimal(ch) {if ch > '7' {seenDecimalDigit = true}ch = s.next()}if s.Mode&ScanFloats != 0 && (ch == '.' || ch == 'e' || ch == 'E') {// floatch = s.scanFraction(ch)ch = s.scanExponent(ch)return Float, ch}// octal intif seenDecimalDigit {s.error("illegal octal number")}}return Int, ch}// decimal int or floatch = s.scanMantissa(ch)if s.Mode&ScanFloats != 0 && (ch == '.' || ch == 'e' || ch == 'E') {// floatch = s.scanFraction(ch)ch = s.scanExponent(ch)return Float, ch}return Int, ch}func (s *Scanner) scanDigits(ch rune, base, n int) rune {for n > 0 && digitVal(ch) < base {ch = s.next()n--}if n > 0 {s.error("illegal char escape")}return ch}func (s *Scanner) scanEscape(quote rune) rune {ch := s.next() // read character after '/'switch ch {case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote:// nothing to doch = s.next()case '0', '1', '2', '3', '4', '5', '6', '7':ch = s.scanDigits(ch, 8, 3)case 'x':ch = s.scanDigits(s.next(), 16, 2)case 'u':ch = s.scanDigits(s.next(), 16, 4)case 'U':ch = s.scanDigits(s.next(), 16, 8)default:s.error("illegal char escape")}return ch}func (s *Scanner) scanString(quote rune) (n int) {ch := s.next() // read character after quotefor ch != quote {if ch == '\n' || ch < 0 {s.error("literal not terminated")return}if ch == '\\' {ch = s.scanEscape(quote)} else {ch = s.next()}n++}return}func (s *Scanner) scanRawString() {ch := s.next() // read character after '`'for ch != '`' {if ch < 0 {s.error("literal not terminated")return}ch = s.next()}}func (s *Scanner) scanChar() {if s.scanString('\'') != 1 {s.error("illegal char literal")}}func (s *Scanner) scanComment(ch rune) rune {// ch == '/' || ch == '*'if ch == '/' {// line commentch = s.next() // read character after "//"for ch != '\n' && ch >= 0 {ch = s.next()}return ch}// general commentch = s.next() // read character after "/*"for {if ch < 0 {s.error("comment not terminated")break}ch0 := chch = s.next()if ch0 == '*' && ch == '/' {ch = s.next()break}}return ch}// Scan reads the next token or Unicode character from source and returns it.// It only recognizes tokens t for which the respective Mode bit (1<<-t) is set.// It returns EOF at the end of the source. It reports scanner errors (read and// token errors) by calling s.Error, if not nil; otherwise it prints an error// message to os.Stderr.func (s *Scanner) Scan() rune {ch := s.Peek()// reset token text positions.tokPos = -1s.Line = 0redo:// skip white spacefor s.Whitespace&(1<<uint(ch)) != 0 {ch = s.next()}// start collecting token texts.tokBuf.Reset()s.tokPos = s.srcPos - s.lastCharLen// set token position// (this is a slightly optimized version of the code in Pos())s.Offset = s.srcBufOffset + s.tokPosif s.column > 0 {// common case: last character was not a '\n's.Line = s.lines.Column = s.column} else {// last character was a '\n'// (we cannot be at the beginning of the source// since we have called next() at least once)s.Line = s.line - 1s.Column = s.lastLineLen}// determine token valuetok := chswitch {case unicode.IsLetter(ch) || ch == '_':if s.Mode&ScanIdents != 0 {tok = Identch = s.scanIdentifier()} else {ch = s.next()}case isDecimal(ch):if s.Mode&(ScanInts|ScanFloats) != 0 {tok, ch = s.scanNumber(ch)} else {ch = s.next()}default:switch ch {case '"':if s.Mode&ScanStrings != 0 {s.scanString('"')tok = String}ch = s.next()case '\'':if s.Mode&ScanChars != 0 {s.scanChar()tok = Char}ch = s.next()case '.':ch = s.next()if isDecimal(ch) && s.Mode&ScanFloats != 0 {tok = Floatch = s.scanMantissa(ch)ch = s.scanExponent(ch)}case '/':ch = s.next()if (ch == '/' || ch == '*') && s.Mode&ScanComments != 0 {if s.Mode&SkipComments != 0 {s.tokPos = -1 // don't collect token textch = s.scanComment(ch)goto redo}ch = s.scanComment(ch)tok = Comment}case '`':if s.Mode&ScanRawStrings != 0 {s.scanRawString()tok = String}ch = s.next()default:ch = s.next()}}// end of token texts.tokEnd = s.srcPos - s.lastCharLens.ch = chreturn tok}// Pos returns the position of the character immediately after// the character or token returned by the last call to Next or Scan.func (s *Scanner) Pos() (pos Position) {pos.Filename = s.Filenamepos.Offset = s.srcBufOffset + s.srcPos - s.lastCharLenswitch {case s.column > 0:// common case: last character was not a '\n'pos.Line = s.linepos.Column = s.columncase s.lastLineLen > 0:// last character was a '\n'pos.Line = s.line - 1pos.Column = s.lastLineLendefault:// at the beginning of the sourcepos.Line = 1pos.Column = 1}return}// TokenText returns the string corresponding to the most recently scanned token.// Valid after calling Scan().func (s *Scanner) TokenText() string {if s.tokPos < 0 {// no token textreturn ""}if s.tokEnd < 0 {// if EOF was reached, s.tokEnd is set to -1 (s.srcPos == 0)s.tokEnd = s.tokPos}if s.tokBuf.Len() == 0 {// common case: the entire token text is still in srcBufreturn string(s.srcBuf[s.tokPos:s.tokEnd])}// part of the token text was saved in tokBuf: save the rest in// tokBuf as well and return its contents.tokBuf.Write(s.srcBuf[s.tokPos:s.tokEnd])s.tokPos = s.tokEnd // ensure idempotency of TokenText() callreturn s.tokBuf.String()}
