summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2026-02-12 22:59:29 +0300
committer2026-02-12 23:19:54 +0300
commit8a451b0269d2d25229463ee72f946860b3edf87d (patch)
tree35fb7a1ab772157853eb701ce1ac2d7125960ee9
downloadlex-8a451b0269d2d25229463ee72f946860b3edf87d.tar.gz
lex-8a451b0269d2d25229463ee72f946860b3edf87d.tar.bz2
lex-8a451b0269d2d25229463ee72f946860b3edf87d.tar.xz
lex-8a451b0269d2d25229463ee72f946860b3edf87d.zip
Начальный релизHEADv0.0.1master
-rw-r--r--go.mod3
-rw-r--r--states.go3
-rw-r--r--token.go10
-rw-r--r--tokenizer.go106
4 files changed, 122 insertions, 0 deletions
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..719088b
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module go.neonxp.ru/lex
+
+go 1.25.0
diff --git a/states.go b/states.go
new file mode 100644
index 0000000..6b75a7b
--- /dev/null
+++ b/states.go
@@ -0,0 +1,3 @@
+package lex
+
+type StateFunc func(l *Lexer) StateFunc
diff --git a/token.go b/token.go
new file mode 100644
index 0000000..ef3c92b
--- /dev/null
+++ b/token.go
@@ -0,0 +1,10 @@
+package lex
+
+type Token struct {
+ Typ Typ
+ Pos int
+ Value string
+ Error error
+}
+
+type Typ int
diff --git a/tokenizer.go b/tokenizer.go
new file mode 100644
index 0000000..1ddd8d0
--- /dev/null
+++ b/tokenizer.go
@@ -0,0 +1,106 @@
+// Package lex представляет собой достаточно простой лексер произвольных выражений.
+// Практически полностью аналогичен лексеру от Роба Пайка.
+package lex
+
+import (
+ "strings"
+ "unicode/utf8"
+)
+
+func Do(initState StateFunc, input string) <-chan Token {
+ t := &Lexer{
+ pos: 0,
+ input: input,
+ start: 0,
+ ch: make(chan Token),
+ }
+
+ go func() {
+ defer close(t.ch)
+ for state := initState; state != nil; {
+ state = state(t)
+ }
+ }()
+
+ return t.ch
+}
+
+type Lexer struct {
+ pos int
+ start int
+ input string
+ width int
+ ch chan Token
+}
+
+func (l *Lexer) Next() (ch rune) {
+ if l.pos >= len(l.input) {
+ l.width = 0
+ return 0
+ }
+ ch, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
+ l.pos += l.width
+ return ch
+}
+
+func (l *Lexer) Peek() rune {
+ ch := l.Next()
+ l.Back()
+ return ch
+}
+
+func (l *Lexer) Back() {
+ l.pos -= l.width
+}
+
+func (l *Lexer) Skip() {
+ l.start = l.pos
+}
+
+func (l *Lexer) EmitToken(typ Typ) {
+ value := ""
+ if l.pos > l.start {
+ value = l.input[l.start:l.pos]
+ }
+ l.ch <- Token{
+ Typ: typ,
+ Value: value,
+ Pos: l.pos,
+ }
+ l.start = l.pos
+}
+
+func (l *Lexer) EmitError(err error) {
+ value := ""
+ if l.pos > l.start {
+ value = l.input[l.start:l.pos]
+ }
+ l.ch <- Token{
+ Value: value,
+ Pos: l.pos,
+ Error: err,
+ }
+}
+
+func (l *Lexer) Accept(valid string) bool {
+ if strings.ContainsRune(valid, l.Next()) {
+ return true
+ }
+ l.Back()
+
+ return false
+}
+
+func (l *Lexer) AcceptRun(valid string) {
+ for strings.ContainsRune(valid, l.Next()) {
+ }
+ l.Back()
+}
+
+func (l *Lexer) AcceptNotRun(invalid string, greedy bool) {
+ for ch := l.Next(); !strings.ContainsRune(invalid, ch) && ch != 0; ch = l.Next() {
+ }
+ if !greedy {
+ l.Back()
+ }
+}