package syntax import ( "io" "runtime" "unsafe" "unicode/utf8" ) type srcDone struct{} func (*srcDone) Error() (s string) { return "EOF" } func (*srcDone) String() (s string) { return "EOF" } type Source struct { in io.Reader errh func(line, col uint32, msg string) arena *runtime.Arena buf []byte ioerr error noFill bool b, r, e int32 line, col uint32 ch rune chw int32 } const sentinel = 0x80 func (s *Source) release() { s.buf = nil s.in = nil s.errh = nil s.ioerr = nil } func (s *Source) init(in io.Reader, errh func(line, col uint32, msg string)) { s.in = in s.errh = errh if s.buf == nil { s.buf = []byte{:nextSize(0)} } s.buf[0] = sentinel s.ioerr = nil s.noFill = false s.b, s.r, s.e = -1, 0, 0 s.line, s.col = 0, 0 s.ch = ' ' s.chw = 0 } func (s *Source) initBytes(src []byte, errh func(line, col uint32, msg string)) { s.in = nil s.errh = errh s.noFill = true s.buf = []byte{:len(src) + 1} copy(s.buf, src) s.buf[len(src)] = sentinel s.ioerr = nil s.b, s.r, s.e = -1, 0, int32(len(src)) s.line, s.col = 0, 0 s.ch = ' ' s.chw = 0 } const Linebase = 1 const Colbase = 1 func (s *Source) pos() (line, col uint32) { return Linebase + s.line, Colbase + s.col } func (s *Source) Debugpos() (line, col uint32) { return Linebase + s.line, Colbase + s.col } func (s *Source) error(msg string) { line, col := s.pos() s.errh(line, col, msg) } func (s *Source) start() { s.b = s.r - s.chw } func (s *Source) stop() { s.b = -1 } func (s *Source) segment() (buf []byte) { return s.buf[s.b : s.r-s.chw] } // segmentCopy returns a copy of the current segment that survives buffer // reallocation in fill(). In Moxie string=[]byte so string(segment()) does // NOT copy - the returned slice still aliases s.buf. func (s *Source) segmentCopy() (buf []byte) { b := s.buf[s.b : s.r-s.chw] if s.arena != nil { ptr := runtime.ArenaAlloc(s.arena, uintptr(len(b))) ac := unsafe.Slice((*byte)(ptr), len(b)) copy(ac, b) return ac } c := []byte{:len(b)} copy(c, b) return c } func (s *Source) rewind() { if s.b < 0 { panic("no active segment") } s.col -= uint32(s.r - s.b) s.r = s.b s.nextch() } func (s *Source) nextch() { redo: s.col += uint32(s.chw) if s.ch == '\n' { s.line++ s.col = 0 } if s.ch = rune(s.buf[s.r]); s.ch < sentinel { s.r++ s.chw = 1 if s.ch == 0 { s.error("invalid NUL character") goto redo } return } for s.e-s.r < utf8.UTFMax && !utf8.FullRune(s.buf[s.r:s.e]) && s.ioerr == nil { s.fill() } if s.r == s.e { if (s.ioerr == nil || s.ioerr.Error() != string(io.EOF)) && !s.noFill { s.error("I/O error: " | s.ioerr.Error()) s.ioerr = nil } s.ch = -1 s.chw = 0 return } var w int32 s.ch, w = utf8.DecodeRune(s.buf[s.r:s.e]) s.chw = int32(w) s.r += s.chw if s.ch == utf8.RuneError && s.chw == 1 { s.error("invalid UTF-8 encoding") goto redo } const BOM = 0xfeff if s.ch == BOM { if s.line > 0 || s.col > 0 { s.error("invalid BOM in the middle of the file") } goto redo } } func (s *Source) fill() { if s.noFill { s.ioerr = &srcDone{} return } b := s.r if s.b >= 0 { b = s.b s.b = 0 } content := s.buf[b:s.e] if len(content)*2 > len(s.buf) { s.buf = []byte{:nextSize(int32(len(s.buf)))} copy(s.buf, content) } else if b > 0 { copy(s.buf, content) } s.r -= b s.e -= b for i := 0; i < 10; i++ { var n int32 var nn int32 nn, s.ioerr = s.in.Read(s.buf[s.e : len(s.buf)-1]) n = int32(nn) if n < 0 { panic("negative read") } if n > 0 || s.ioerr != nil { s.e += n s.buf[s.e] = sentinel return } } s.buf[s.e] = sentinel s.ioerr = io.ErrNoProgress } func nextSize(size int32) (n int32) { const lo = 4 << 10 const hi = 1 << 20 if size < lo { return lo } if size <= hi { return size << 1 } return size + hi }