11 Commits

Author SHA1 Message Date
amery 30da2c7418 lexer: create Lexer factories for io.Reader, []byte and string
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-29 00:46:42 +00:00
amery 490605e09e build-sys: import build system from darvaza.org/core
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-29 00:46:26 +00:00
amery 60bc3bd48f Merge branch 'pr-amery-unreadrune' into next-amery 2023-08-29 00:46:19 +00:00
amery 6e866caa75 runes: fix ReadRune() to actually move the cursor
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-29 00:45:52 +00:00
amery 9e09f9de68 Merge branch 'pr-amery-lexer' into next-amery 2023-08-29 00:02:08 +00:00
amery 0120cf0cc6 lexer: introduce Lexer wrapper for io.RuneScanner
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-29 00:01:21 +00:00
amery 658a126d64 Merge branch 'pr-amery-unreadrune' into next-amery 2023-08-28 23:17:01 +00:00
amery 060151a3ac runes: Implement UnreadRune() and PeekRune()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-28 23:16:01 +00:00
amery 48b75ec3c3 lexer: introduce a Position (Line, Column) handler
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-28 23:02:37 +00:00
amery a4124fb3e1 lexer: introduce Accept()/AcceptAll() helpers
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-28 22:36:29 +00:00
amery d760902dce lexer: introduce StateFn and the basic state machine loop
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-08-28 22:36:26 +00:00
6 changed files with 104 additions and 155 deletions
-54
View File
@@ -1,54 +0,0 @@
package lexer
import (
"errors"
"fmt"
"strings"
)
var (
_ error = (*Error)(nil)
)
var (
// ErrUnacceptableRune indicates the read rune isn't acceptable in the context
ErrUnacceptableRune = errors.New("rune not acceptable in context")
// ErrNotImplemented indicates something hasn't been implemented yet
ErrNotImplemented = errors.New("not implemented")
)
// Error represents a generic parsing error
type Error struct {
Filename string
Line int
Column int
Content string
Err error
}
func (err Error) Error() string {
var s []string
switch {
case err.Line > 0 || err.Column > 0:
s = append(s, fmt.Sprintf("%s:%v:%v", err.Filename, err.Line, err.Column))
case err.Filename != "":
s = append(s, err.Filename)
}
if err.Err != nil {
s = append(s, err.Err.Error())
}
if err.Content != "" {
s = append(s, fmt.Sprintf("%q", err.Content))
}
return strings.Join(s, ": ")
}
func (err Error) Unwrap() error {
return err.Err
}
+88 -13
View File
@@ -2,30 +2,105 @@
package lexer
import (
"errors"
"io"
"asciigoat.org/core/runes"
)
// Lexer adds Accept and AcceptAll support to a
// [io.RuneScanner]
type Lexer struct {
io.RuneScanner
}
// Accept consumes a rune from the source if it meets the condition.
// it returns true if the condition was met and false if it wasn't.
func (p Lexer) Accept(cond func(rune) bool) bool {
return Accept(p, cond)
}
// AcceptAll consumes runes from the source as long as they meet the
// condition. it returns true if the condition was met for at least one rune,
// and false if it wasn't.
func (p Lexer) AcceptAll(cond func(rune) bool) bool {
return AcceptAll(p, cond)
}
// NewLexer extends a [io.RuneScanner] with Accept()/AcceptAll()
// functionality
func NewLexer(src io.RuneScanner) *Lexer {
if src == nil {
return nil
}
return &Lexer{src}
}
// NewLexerReader creates a Lexer from an [io.Reader]
func NewLexerReader(r io.Reader) *Lexer {
if r == nil {
return nil
}
return &Lexer{runes.NewReader(r)}
}
// NewLexerBytes creates a Lexer from raw bytes
func NewLexerBytes(b []byte) *Lexer {
return &Lexer{runes.NewReaderBytes(b)}
}
// NewLexerString creates a Lexer from a data string
func NewLexerString(s string) *Lexer {
return &Lexer{runes.NewReaderString(s)}
}
// StateFn is a State Function of the parser
type StateFn func() (StateFn, error)
// Run runs a state machine until the state function either
// returns nil or an error
func Run(fn StateFn) error {
for fn != nil {
var err error
var err error
for fn != nil && err == nil {
fn, err = fn()
switch {
case errors.Is(err, io.EOF):
// EOF
return nil
case err != nil:
// failed
return err
}
}
// ended
return nil
return err
}
// Accept consumes a rune from the source if it meets the condition.
// it returns true if the condition was met and false if it wasn't.
func Accept(src io.RuneScanner, cond func(r rune) bool) bool {
r, _, err := src.ReadRune()
switch {
case err != nil:
return false
case cond(r):
return true
default:
_ = src.UnreadRune()
return false
}
}
// AcceptAll consumes runes from the source as long as they meet the
// condition. it returns true if the condition was met for at least one rune,
// and false if it wasn't.
func AcceptAll(src io.RuneScanner, cond func(r rune) bool) bool {
var accepted bool
for {
r, _, err := src.ReadRune()
switch {
case err != nil:
return accepted
case cond(r):
accepted = true
default:
_ = src.UnreadRune()
return accepted
}
}
}
+13 -11
View File
@@ -41,26 +41,28 @@ func (p *Position) Step() {
p.Column++
}
// StepN moves the column N places forward
func (p *Position) StepN(n int) {
// Next returns a new Position one rune forward
// on the line
func (p Position) Next() Position {
if p.Line == 0 {
p.Reset()
}
switch {
case n > 0:
p.Column += n
default:
panic(fmt.Errorf("invalid %v increment", n))
return Position{
Line: p.Line,
Column: p.Column + 1,
}
}
// StepLine moves position to the start of the next line
func (p *Position) StepLine() {
// NextLine returns a new Position at the begining of the next
// line.
func (p Position) NextLine() Position {
if p.Line == 0 {
p.Reset()
}
p.Line++
p.Column = 1
return Position{
Line: p.Line + 1,
Column: 1,
}
}
-41
View File
@@ -1,41 +0,0 @@
package lexer
import (
"strings"
"unicode"
)
// NewIsNot generates a rune condition checker that reverses the
// decision of the given checker.
func NewIsNot(cond func(rune) bool) func(rune) bool {
return func(r rune) bool {
return !cond(r)
}
}
// NewIsIn generates a rune condition checker that accepts runes
// contained on the provided string
func NewIsIn(s string) func(rune) bool {
return func(r rune) bool {
return strings.ContainsRune(s, r)
}
}
// NewIsOneOf generates a run condition checker that accepts runes
// accepted by any of the given checkers
func NewIsOneOf(s ...func(rune) bool) func(rune) bool {
return func(r rune) bool {
for _, cond := range s {
if cond(r) {
return true
}
}
return false
}
}
// IsSpace reports whether the rune is a space character as
// defined by Unicode's White Space property
func IsSpace(r rune) bool {
return unicode.IsSpace(r)
}
+2
View File
@@ -0,0 +1,2 @@
// Package runes helps us work with runes
package runes
+1 -36
View File
@@ -1,4 +1,4 @@
package lexer
package runes
import (
"bytes"
@@ -198,41 +198,6 @@ func (b *Reader) PeekRune() (rune, int, error) {
return r, l, err
}
// Accept consumes a rune from the source if it meets the condition.
// it returns true if the condition was met and false if it wasn't.
func (b *Reader) Accept(cond func(r rune) bool) bool {
r, _, err := b.ReadRune()
switch {
case err != nil:
return false
case cond(r):
return true
default:
_ = b.UnreadRune()
return false
}
}
// AcceptAll consumes runes from the source as long as they meet the
// condition. it returns true if the condition was met for at least one rune,
// and false if it wasn't.
func (b *Reader) AcceptAll(cond func(r rune) bool) bool {
var accepted bool
for {
r, _, err := b.ReadRune()
switch {
case err != nil:
return accepted
case cond(r):
accepted = true
default:
_ = b.UnreadRune()
return accepted
}
}
}
// NewReader creates a new runes [Reader] using the given [io.Reader]
func NewReader(r io.Reader) *Reader {
if r == nil {