wrote lexer... again. doesn't properly handle strings though
This commit is contained in:
parent
4f9baf7514
commit
b8ef198384
136
lex.go
Normal file
136
lex.go
Normal file
@ -0,0 +1,136 @@
|
||||
package ginger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/mediocregopher/lexgo"
|
||||
)
|
||||
|
||||
const (
|
||||
number lexgo.TokenType = lexgo.UserDefined + iota
|
||||
identifier
|
||||
punctuation
|
||||
)
|
||||
|
||||
var numberSet = "0123456789"
|
||||
var whitespaceSet = " \n\r\t\v\f"
|
||||
var punctuationSet = ",{}()<>|"
|
||||
|
||||
func newLexer(r io.Reader) *lexgo.Lexer {
|
||||
return lexgo.NewLexer(r, lexWhitespace)
|
||||
}
|
||||
|
||||
func lexWhitespace(lexer *lexgo.Lexer) lexgo.LexerFunc {
|
||||
r, err := lexer.ReadRune()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.ContainsRune(whitespaceSet, r) {
|
||||
return lexWhitespace
|
||||
}
|
||||
|
||||
if r == '/' {
|
||||
n, err := lexer.PeekRune()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var lexComment func(*lexgo.Lexer) bool
|
||||
if n == '/' {
|
||||
lexComment = lexLineComment
|
||||
} else if n == '*' {
|
||||
lexComment = lexBlockComment
|
||||
}
|
||||
if lexComment != nil {
|
||||
if !lexComment(lexer) {
|
||||
return nil
|
||||
}
|
||||
return lexWhitespace
|
||||
}
|
||||
}
|
||||
|
||||
lexer.BufferRune(r)
|
||||
|
||||
switch {
|
||||
case strings.ContainsRune(punctuationSet, r):
|
||||
return lexPunctuation
|
||||
case strings.ContainsRune(numberSet, r):
|
||||
return lexNumber
|
||||
default:
|
||||
return lexIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
// assumes the punctuation has already been buffered
|
||||
func lexPunctuation(lexer *lexgo.Lexer) lexgo.LexerFunc {
|
||||
lexer.Emit(punctuation)
|
||||
return lexWhitespace
|
||||
}
|
||||
|
||||
func lexGeneralExpr(lexer *lexgo.Lexer, typ lexgo.TokenType) lexgo.LexerFunc {
|
||||
for {
|
||||
r, err := lexer.ReadRune()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.ContainsRune(whitespaceSet, r) {
|
||||
lexer.Emit(typ)
|
||||
return lexWhitespace
|
||||
}
|
||||
|
||||
if strings.ContainsRune(punctuationSet, r) {
|
||||
lexer.Emit(typ)
|
||||
lexer.BufferRune(r)
|
||||
return lexPunctuation
|
||||
}
|
||||
|
||||
lexer.BufferRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
func lexNumber(lexer *lexgo.Lexer) lexgo.LexerFunc {
|
||||
return lexGeneralExpr(lexer, number)
|
||||
}
|
||||
|
||||
func lexIdentifier(lexer *lexgo.Lexer) lexgo.LexerFunc {
|
||||
return lexGeneralExpr(lexer, identifier)
|
||||
}
|
||||
|
||||
func lexLineComment(lexer *lexgo.Lexer) bool {
|
||||
for {
|
||||
r, err := lexer.ReadRune()
|
||||
if err != nil {
|
||||
return false
|
||||
} else if r == '\n' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lexBlockComment(lexer *lexgo.Lexer) bool {
|
||||
for {
|
||||
r, err := lexer.ReadRune()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if r == '*' || r == '/' {
|
||||
n, err := lexer.PeekRune()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if r == '*' && n == '/' {
|
||||
_, err = lexer.ReadRune()
|
||||
return err == nil
|
||||
}
|
||||
if r == '/' && n == '*' {
|
||||
if !lexBlockComment(lexer) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
lex_test.go
Normal file
71
lex_test.go
Normal file
@ -0,0 +1,71 @@
|
||||
package ginger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
. "testing"
|
||||
|
||||
"github.com/mediocregopher/lexgo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var lexTestSrc = `
|
||||
// this is a comment
|
||||
// // this is also a comment
|
||||
a
|
||||
anIdentifier
|
||||
1
|
||||
100
|
||||
1.5
|
||||
1.5e9
|
||||
|
||||
/* block comment */
|
||||
prefix /*
|
||||
Another block comment
|
||||
/* Embedded */
|
||||
/*
|
||||
Super embedded
|
||||
*/
|
||||
*/ suffix
|
||||
|
||||
// this one is kind of fun, technically it's a comment
|
||||
/*/
|
||||
|
||||
(punctuation,is{cool}<> )
|
||||
-tab
|
||||
`
|
||||
|
||||
func TestLex(t *T) {
|
||||
l := newLexer(bytes.NewBufferString(lexTestSrc))
|
||||
|
||||
assertNext := func(typ lexgo.TokenType, val string) {
|
||||
t.Logf("asserting %q", val)
|
||||
tok := l.Next()
|
||||
assert.Equal(t, typ, tok.TokenType)
|
||||
assert.Equal(t, val, tok.Val)
|
||||
}
|
||||
|
||||
assertNext(identifier, "a")
|
||||
assertNext(identifier, "anIdentifier")
|
||||
assertNext(number, "1")
|
||||
assertNext(number, "100")
|
||||
assertNext(number, "1.5")
|
||||
assertNext(number, "1.5e9")
|
||||
assertNext(identifier, "prefix")
|
||||
assertNext(identifier, "suffix")
|
||||
assertNext(punctuation, "(")
|
||||
assertNext(identifier, "punctuation")
|
||||
assertNext(punctuation, ",")
|
||||
assertNext(identifier, "is")
|
||||
assertNext(punctuation, "{")
|
||||
assertNext(identifier, "cool")
|
||||
assertNext(punctuation, "}")
|
||||
assertNext(punctuation, "<")
|
||||
assertNext(punctuation, ">")
|
||||
assertNext(punctuation, ")")
|
||||
assertNext(identifier, "-tab")
|
||||
|
||||
tok := l.Next()
|
||||
assert.Equal(t, tok.TokenType, lexgo.Err)
|
||||
assert.Equal(t, tok.Err, io.EOF)
|
||||
}
|
27
types.go
Normal file
27
types.go
Normal file
@ -0,0 +1,27 @@
|
||||
package ginger
|
||||
|
||||
type Expr struct {
|
||||
// [0-9]+
|
||||
Int int
|
||||
|
||||
// true | false
|
||||
Bool bool
|
||||
|
||||
// [Expr [, Expr]]
|
||||
Tuple []Expr
|
||||
|
||||
// { [Statement (;\s)]* }
|
||||
Block []Expr
|
||||
|
||||
// [Expr | Expr]
|
||||
Pipeline []Expr
|
||||
|
||||
// [a-z]+
|
||||
Identifier string
|
||||
|
||||
// Expr > Expr
|
||||
Statement *struct {
|
||||
Input Expr
|
||||
Into Expr
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user