Browse Source

Recursive descent parser helper

Beoran 2 years ago
parent
commit
eadef8ac14
5 changed files with 439 additions and 0 deletions
  1. 0 0
      cmd/ll1gen/main.go
  2. 0 0
      generator/generator.go
  3. 0 0
      generator/template_functions.go
  4. 337 0
      rdpar/rdpar.go
  5. 102 0
      rdpar/rdpar_test.go

+ 0 - 0
cmd/ll1lex/main.go → cmd/ll1gen/main.go


+ 0 - 0
flexgen/generator.go → generator/generator.go


+ 0 - 0
old/template_functions.go → generator/template_functions.go


+ 337 - 0
rdpar/rdpar.go

@@ -0,0 +1,337 @@
+package rdpar
+
+import "io"
+import "unicode"
+
+import "fmt"
+import "strings"
+import "strconv"
+
+type Kind int
+
+const (
+	KindError Kind = -1
+	KindNone  Kind = 0
+	KindBool  Kind = iota + 1
+	KindInt
+	KindFloat
+	KindWord
+	KindString
+	KindLast
+)
+
+type Value interface {
+	// Kind() Kind
+}
+
+type Predicate func(c rune) bool
+type Converter func(s string) Value
+
+type Pos struct {
+	Name *string
+	Line int
+	Col  int
+}
+
+func (p Pos) String() string {
+	name := "<stdin>"
+	if p.Name != nil {
+		name = *p.Name
+	}
+	return fmt.Sprintf("%s:%d:%d", name, p.Line, p.Col)
+}
+
+type State struct {
+	Kind
+	Token strings.Builder
+}
+
+type Parser struct {
+	Pos
+	Stack []State
+	io.RuneScanner
+}
+
+func (p *Parser) WrapError(err error) Value {
+	return (fmt.Errorf("%s:%w", p.Pos.String(), err))
+}
+
+func (p *Parser) Errorf(f string, args ...interface{}) Value {
+	return p.WrapError(fmt.Errorf(f, args...))
+}
+
+func (p *Parser) Accept() string {
+	res := p.Buffer().String()
+	p.Pop()
+	return res
+}
+
+func (p *Parser) ConvertFloat(s string) Value {
+	f, err := strconv.ParseFloat(s, 64)
+	if err != nil {
+		return p.WrapError(err)
+	}
+	return float64(f)
+}
+
+func (p *Parser) ConvertInt(s string) Value {
+	i, err := strconv.ParseInt(s, 0, 64)
+	if err != nil {
+		return p.WrapError(err)
+	}
+	return int64(i)
+}
+
+func (p *Parser) AcceptConvert(conv Converter) Value {
+	return conv(p.Accept())
+}
+
+func (p Parser) State() Kind {
+	return p.Stack[len(p.Stack)-1].Kind
+}
+
+func (p Parser) Buffer() *strings.Builder {
+	return &(p.Stack[len(p.Stack)-1].Token)
+}
+
+func (p *Parser) Push(kind Kind) {
+	s := State{Kind: kind}
+	p.Stack = append(p.Stack, s)
+}
+
+func (p *Parser) Switch(kind Kind) {
+	p.Stack[len(p.Stack)-1].Kind = kind
+}
+
+func (p *Parser) Pop() bool {
+	if len(p.Stack) > 0 {
+		p.Stack = p.Stack[0 : len(p.Stack)-1]
+		return false
+	}
+	return true
+}
+
+func (p Parser) AppendToken(r rune) {
+	p.Buffer().WriteRune(r)
+}
+
+func (p *Parser) Read() (r rune, ev Value) {
+	r, _, err := p.ReadRune()
+	if err != nil {
+		return r, p.WrapError(err)
+	}
+	if r == '\n' {
+		p.Line++
+		p.Col = 0
+	}
+	p.Col++
+	return r, nil
+}
+
+func (p *Parser) Unread() {
+	// Do not bother to change pos here
+	p.UnreadRune()
+}
+
+func (p *Parser) Peek() (r rune, ev Value) {
+	r, err := p.Read()
+	p.Unread()
+	return r, err
+}
+
+func (p *Parser) ParseWord(end string) Value {
+	c, err := p.Peek()
+	if err != nil {
+		return err
+	}
+	if !unicode.IsLetter(c) {
+		return nil
+	}
+	p.Push(1)
+	for {
+		c, err = p.Read()
+		if err != nil {
+			return err
+		}
+		if strings.ContainsRune(end, c) {
+			p.Unread()
+			res := p.Accept()
+			return string(res)
+		} else {
+			p.AppendToken(c)
+		}
+	}
+}
+
+func (p *Parser) Skip(in string) (err Value) {
+	for {
+		r, err := p.Read()
+		if err != nil {
+			return err
+		}
+		if !strings.ContainsRune(in, r) {
+			p.Unread()
+			return nil
+		}
+	}
+}
+
+func IsError(v Value) bool {
+	if v == nil {
+		return false
+	}
+	_, ok := v.(error)
+	return ok
+}
+
+func (p *Parser) ParseNum(end string) Value {
+	isFloat := false
+	if ok := p.PeekIs(unicode.IsDigit); ok == nil || IsError(ok) {
+		return ok
+	}
+	p.Push(KindInt)
+	for {
+		r, err := p.Read()
+		if err != nil {
+			return err
+		}
+		if strings.ContainsRune(end, r) {
+			sval := p.Accept()
+			if isFloat {
+				fval, err := strconv.ParseFloat(sval, 64)
+				if err != nil {
+					return p.WrapError(err)
+				}
+				return float64(fval)
+			} else {
+				ival, err := strconv.ParseInt(sval, 0, 64)
+				if err != nil {
+					return p.WrapError(err)
+				}
+				return int64(ival)
+			}
+		} else if strings.ContainsRune("+-", r) {
+			if p.Buffer().Len() > 0 {
+				return p.Errorf("Sign only allowed at beginning of number")
+			}
+			p.AppendToken(r)
+		} else if strings.ContainsRune("0123456789", r) {
+			p.AppendToken(r)
+		} else if r == '.' || r == 'e' {
+			if isFloat {
+				return p.Errorf("Incorrect floating point literal")
+			}
+			p.AppendToken(r)
+			isFloat = true
+		} else {
+			return p.Errorf("Unexpected character in integer.")
+		}
+	}
+}
+
+func (p *Parser) PeekIs(pred func(rune) bool) (ok Value) {
+	c, err := p.Peek()
+	if err != nil {
+		return err
+	}
+	if !pred(c) {
+		return bool(false)
+	}
+	return bool(true)
+}
+
+func (p *Parser) PeekStart(start rune) (ok Value) {
+	c, err := p.Peek()
+	if err != nil {
+		return err
+	}
+	if c != start {
+		return bool(false)
+	}
+	return bool(true)
+}
+
+// uses the default \ escape
+func (p *Parser) ParseString(start, end rune) Value {
+	escaped := false
+	r, err := p.Peek()
+	if err != nil {
+		return err
+	}
+	if r != start {
+		return nil
+	}
+	r, err = p.Read() // skip quote
+	if err != nil {
+		return err
+	}
+	p.Push(1)
+	for {
+		r, err = p.Read()
+		if err != nil {
+			return err
+		}
+		if r == '\\' {
+			if escaped {
+				p.AppendToken('\\')
+				p.AppendToken('\\')
+				escaped = false
+			} else {
+				escaped = true
+			}
+		} else if escaped {
+			p.AppendToken('\\')
+			p.AppendToken(r)
+			escaped = false
+		} else if r == end {
+			sval := "\"" + p.Accept() + "\""
+			str, err := strconv.Unquote(sval)
+			if err != nil {
+				return p.WrapError(err)
+			}
+			return string(str)
+		} else {
+			p.AppendToken(r)
+		}
+	}
+}
+
+// Raw string, only the end and the escape can be escaped
+func (p *Parser) ParseRawString(start, end, esc rune) Value {
+	escaped := false
+	r, err := p.Peek()
+	if err != nil {
+		return err
+	}
+	if r != start {
+		return nil
+	}
+	r, err = p.Read() // skip quote
+	if err != nil {
+		return err
+	}
+	p.Push(1)
+	for {
+		r, err = p.Read()
+		if err != nil {
+			return err
+		}
+		if r == esc {
+			if escaped {
+				p.AppendToken(r)
+				escaped = false
+			} else {
+				escaped = true
+			}
+		} else if r == end {
+			if escaped {
+				p.AppendToken(r)
+				escaped = false
+			} else {
+				return p.Accept()
+			}
+		} else {
+			p.AppendToken(r)
+		}
+	}
+}

+ 102 - 0
rdpar/rdpar_test.go

@@ -0,0 +1,102 @@
+// This package contains a reusable recursive descent parser
+// Copyright Beoran beoran@gmail.com 2021
+// Licenced under the MIT licence.
+package rdpar
+
+import "testing"
+
+// import "os"
+// import "bufio"
+import "strings"
+import "strconv"
+
+func FatalIfErr(t *testing.T, v Value) {
+	if v != nil {
+		if err, isErr := v.(error); isErr {
+			t.Fatalf("Error: %s", err)
+		}
+	}
+}
+
+func TestParseWord(t *testing.T) {
+	in := strings.NewReader(`foo `)
+	p := &Parser{}
+	p.RuneScanner = in
+	res := p.ParseWord("\r\n ")
+	FatalIfErr(t, res)
+	want := string("foo")
+	if res != want {
+		t.Errorf("error: expected: %s, got %s.", want, res)
+	} else {
+		t.Logf("ok: %s, %s", want, res)
+	}
+}
+
+func TestParseInt(t *testing.T) {
+	in := strings.NewReader(`123 `)
+	p := &Parser{}
+	p.RuneScanner = in
+	res := p.ParseNum("\r\n ")
+	FatalIfErr(t, res)
+	want := int64(123)
+	if res != want {
+		t.Errorf("error: expected: %d, got %d.", want, res)
+	} else {
+		t.Logf("ok: %d, %d", want, res)
+	}
+}
+
+func TestParseFloat(t *testing.T) {
+	in := strings.NewReader(`123.456 `)
+	p := &Parser{}
+	p.RuneScanner = in
+	res := p.ParseNum("\r\n ")
+	FatalIfErr(t, res)
+	want := float64(123.456)
+	if res != want {
+		t.Errorf("error: expected: %f, got %f.", want, res)
+	} else {
+		t.Logf("ok: %f, %f", want, res)
+	}
+}
+
+func TestParseString(t *testing.T) {
+	instr := `"Hello\nWorld\\\""`
+	in := strings.NewReader(instr)
+	p := &Parser{}
+	p.RuneScanner = in
+	res := p.ParseString('"', '"')
+	FatalIfErr(t, res)
+	want, err := strconv.Unquote(instr)
+	FatalIfErr(t, err)
+	if res != want {
+		t.Errorf("error: expected: %s, got %v.", want, res)
+	} else {
+		t.Logf("ok: %s, %s", want, res)
+	}
+}
+
+func TestParsRawString(t *testing.T) {
+	instr := `[Hello&]World&&]`
+	in := strings.NewReader(instr)
+	p := &Parser{}
+	p.RuneScanner = in
+	res := p.ParseRawString('[', ']', '&')
+	FatalIfErr(t, res)
+	want := `Hello]World&`
+	if res != want {
+		t.Errorf("error: expected: %s, got %v.", want, res)
+	} else {
+		t.Logf("ok: %s, %s", want, res)
+	}
+}
+
+/*
+func TestTabpa(t *testing.T) {
+	fin, err := os.Open("testdata/test1.b1cfg")
+	FatalIfErr(t, err)
+	defer fin.Close()
+	res, err := ParseB1cfg(bufio.NewReader(fin))
+	FatalIfErr(t, err)
+	t.Logf("res: %v", res)
+}*/