aboutsummaryrefslogtreecommitdiff
path: root/parser/reader.go
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2023-09-02 18:49:53 +0200
committerThomas Voss <mail@thomasvoss.com> 2023-09-08 23:16:19 +0200
commit643623dbecdc1ccb6f3ac77e4ebabdc6ca1d8d06 (patch)
treea9d6b50ad7263e792bc276f765ada74a5661a8b1 /parser/reader.go
Genesis commit
Diffstat (limited to 'parser/reader.go')
-rw-r--r--parser/reader.go94
1 files changed, 94 insertions, 0 deletions
diff --git a/parser/reader.go b/parser/reader.go
new file mode 100644
index 0000000..22a8e6f
--- /dev/null
+++ b/parser/reader.go
@@ -0,0 +1,94 @@
+package parser
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "unicode"
+ "unicode/utf8"
+)
+
+type position struct {
+ col uint
+ row uint
+ prevCol uint
+}
+
+func (p position) String() string {
+ return fmt.Sprintf("%d:%d", p.row+1, p.col)
+}
+
+type reader struct {
+ r *bufio.Reader
+ pos position
+}
+
+func (reader *reader) peekRune() (rune, error) {
+ bytes := make([]byte, 0, 4)
+ var err error
+
+ // Peeking the next rune is annoying. We want to get the next rune
+ // which could be the next 1–4 bytes. Normally we can just call
+ // reader.r.Peek(4) but that doesn’t work here as the last rune in a
+ // file could be a 1–3 byte rune, so we would fail with an EOF error.
+ for i := 4; i > 0; i-- {
+ if bytes, err = reader.r.Peek(i); err == io.EOF {
+ continue
+ } else if err != nil {
+ return 0, err
+ } else {
+ rune, _ := utf8.DecodeRune(bytes)
+ return rune, nil
+ }
+ }
+
+ return 0, io.EOF
+}
+
+func (reader *reader) unreadRune() error {
+ if reader.pos.col == 0 {
+ reader.pos.col = reader.pos.prevCol
+ reader.pos.row--
+ } else {
+ reader.pos.col--
+ }
+
+ return reader.r.UnreadRune()
+}
+
+func (reader *reader) readRune() (rune, error) {
+ rune, _, err := reader.r.ReadRune()
+ if rune == '\n' {
+ reader.pos.prevCol = reader.pos.col
+ reader.pos.col = 0
+ reader.pos.row++
+ } else {
+ reader.pos.col++
+ }
+ return rune, err
+}
+
+func (reader *reader) readNonSpaceRune() (rune, error) {
+ if err := reader.skipSpaces(); err != nil {
+ return 0, err
+ }
+
+ if r, err := reader.readRune(); err != nil {
+ return 0, err
+ } else {
+ return r, nil
+ }
+}
+
+func (reader *reader) skipSpaces() error {
+ for {
+ if rune, err := reader.readRune(); err != nil {
+ if err == io.EOF {
+ return nil
+ }
+ return err
+ } else if !unicode.IsSpace(rune) {
+ return reader.unreadRune()
+ }
+ }
+}