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
4 changed files with 160 additions and 36 deletions
+89
View File
@@ -1,6 +1,60 @@
// Package lexer provides basic helpers to implement parsers
package lexer
import (
"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)
@@ -15,3 +69,38 @@ func Run(fn StateFn) error {
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
}
}
}
+68
View File
@@ -0,0 +1,68 @@
package lexer
import "fmt"
// Position indicates a line and column pair on a file.
// Counting starts at 1.
type Position struct {
Line int
Column int
}
// String generates a pretty "(Line, Column)"" representation of the Position
func (p Position) String() string {
if p.Line == 0 {
p.Reset()
}
return fmt.Sprintf("(%v, %v)", p.Line, p.Column)
}
// GoString generates a string representation of the Position for %#v usage
func (p Position) GoString() string {
if p.Line == 0 {
p.Reset()
}
return fmt.Sprintf("lexer.Position{%v, %v}", p.Line, p.Column)
}
// Reset places a position at (1,1)
func (p *Position) Reset() {
p.Line, p.Column = 1, 1
}
// Step moves the column one place
func (p *Position) Step() {
if p.Line == 0 {
p.Reset()
}
p.Column++
}
// Next returns a new Position one rune forward
// on the line
func (p Position) Next() Position {
if p.Line == 0 {
p.Reset()
}
return Position{
Line: p.Line,
Column: p.Column + 1,
}
}
// NextLine returns a new Position at the begining of the next
// line.
func (p Position) NextLine() Position {
if p.Line == 0 {
p.Reset()
}
return Position{
Line: p.Line + 1,
Column: 1,
}
}
+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 {