Some basic structures
This commit is contained in:
33
mmql/engine/definitions.go
Normal file
33
mmql/engine/definitions.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package engine
|
||||
|
||||
const (
|
||||
K_SPACES = "\t\n\r "
|
||||
// Must be defined in pairs with
|
||||
// open bracket on left and close bracket on right
|
||||
K_BRACKETS = "()[]{}"
|
||||
K_QUOTES = "'\"`"
|
||||
)
|
||||
|
||||
const K_STATEMENT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"
|
||||
|
||||
var KEYWORDS = []string {
|
||||
"ANYWHERE",
|
||||
"BOUGHT",
|
||||
"BUY",
|
||||
"CREATE",
|
||||
"EVERY",
|
||||
"EXCHANGE",
|
||||
"FOR",
|
||||
"FROM",
|
||||
"GET",
|
||||
"LIMIT",
|
||||
"LIST",
|
||||
"OF",
|
||||
"SELL",
|
||||
"SOLD",
|
||||
"TRIGGER",
|
||||
"TRIGGERS",
|
||||
"TYPE",
|
||||
"UPDATE",
|
||||
"WITH",
|
||||
}
|
311
mmql/engine/lexer.go
Normal file
311
mmql/engine/lexer.go
Normal file
@@ -0,0 +1,311 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
stmtd "github.com/tgckpg/mmqlengine/mmql/statements"
|
||||
)
|
||||
|
||||
type LexerExpect struct {
|
||||
Statements bool
|
||||
Keywords bool
|
||||
Brackets bool
|
||||
Quotes bool
|
||||
Key string
|
||||
}
|
||||
|
||||
type Lexer struct {
|
||||
SupportedStmts *map[string] func( *Lexer ) ( stmtd.IStatement, error )
|
||||
reader *strings.Reader
|
||||
readToken bool
|
||||
}
|
||||
|
||||
func ( this *Lexer ) Parse( line string ) ( stmtd.IStatement, error ) {
|
||||
|
||||
this.reader = strings.NewReader( line )
|
||||
|
||||
ex := LexerExpect{
|
||||
Statements: true,
|
||||
Keywords: false,
|
||||
Brackets: true,
|
||||
Quotes: true,
|
||||
}
|
||||
|
||||
return this.ReadStatement( &ex )
|
||||
}
|
||||
|
||||
func ( this *Lexer ) SetReader( reader *strings.Reader ) {
|
||||
this.reader = reader
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads
|
||||
* 123,456,789.00001
|
||||
* 1234.567
|
||||
* 1234
|
||||
* 10.5k
|
||||
*/
|
||||
func ( this *Lexer ) ReadDecimal() ( f float64, err error ) {
|
||||
reader := this.reader
|
||||
var b strings.Builder
|
||||
|
||||
dot := false
|
||||
comma := false
|
||||
k := false
|
||||
end := false
|
||||
for {
|
||||
r, _, _err := reader.ReadRune()
|
||||
err = _err
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if comma {
|
||||
err = fmt.Errorf( "Unexpected ',' char" )
|
||||
return
|
||||
}
|
||||
if b.Len() == 0 {
|
||||
err = fmt.Errorf( "Nothing left to read" )
|
||||
return
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if end && !strings.ContainsRune( K_SPACES, r ) {
|
||||
if k {
|
||||
b.WriteRune( 'k' )
|
||||
}
|
||||
b.WriteRune( r )
|
||||
err = fmt.Errorf( "Cannot parse '%s'", b.String() )
|
||||
return
|
||||
}
|
||||
|
||||
if r == ',' {
|
||||
if comma || dot {
|
||||
err = fmt.Errorf( "Unexpected ',' char" )
|
||||
return
|
||||
}
|
||||
comma = true
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.ContainsRune( K_SPACES, r ) {
|
||||
if comma {
|
||||
err = fmt.Errorf( "Unexpected ',' char" )
|
||||
return
|
||||
}
|
||||
if 0 < b.Len() {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '.' {
|
||||
if !dot {
|
||||
b.WriteRune( r )
|
||||
dot = true
|
||||
continue
|
||||
}
|
||||
err = fmt.Errorf( "Unexpected '.' char" )
|
||||
return
|
||||
}
|
||||
|
||||
if r == 'k' {
|
||||
k = true
|
||||
end = true
|
||||
continue
|
||||
}
|
||||
|
||||
if '0' <= r && r <= '9' {
|
||||
b.WriteRune( r )
|
||||
comma = false
|
||||
} else {
|
||||
b.WriteRune( r )
|
||||
err = fmt.Errorf( "Cannot parse '%s'", b.String() )
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
f, err = strconv.ParseFloat( b.String(), 64 )
|
||||
if k {
|
||||
f *= 1000
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ( this *Lexer ) ReadAlpha() ( s string, err error ) {
|
||||
reader := this.reader
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
for {
|
||||
r, _, _err := reader.ReadRune()
|
||||
err = _err
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if 0 < b.Len() {
|
||||
err = nil
|
||||
} else {
|
||||
err = fmt.Errorf( "Nothing left to read" )
|
||||
return
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if strings.ContainsRune( K_SPACES, r ) {
|
||||
if 0 < b.Len() {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ( 'A' <= r && r <= 'Z' ) || ( 'a' <= r && r <= 'z' ) {
|
||||
b.WriteRune( r )
|
||||
}
|
||||
}
|
||||
s = b.String()
|
||||
return
|
||||
}
|
||||
|
||||
func ( this *Lexer ) ReadStatement( expecting *LexerExpect ) ( stmtd.IStatement, error ) {
|
||||
reader := this.reader
|
||||
|
||||
var stmt stmtd.IStatement
|
||||
|
||||
for {
|
||||
r, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expecting.Quotes && strings.ContainsRune( K_QUOTES, r ) {
|
||||
s, err := this.ReadUntilClose( r, true, '\\' )
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stmt = stmtd.QuotedValue{
|
||||
RawStatement: &stmtd.RawStatement{ Value: s },
|
||||
Quote: string( r ),
|
||||
}
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
bracketIndex := strings.IndexRune( K_BRACKETS, r )
|
||||
if expecting.Brackets && bracketIndex % 2 == 0 {
|
||||
cl := rune( K_BRACKETS[ bracketIndex + 1 ] )
|
||||
s, err := this.ReadUntilClose( cl, false, 'A' )
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteRune( r )
|
||||
b.WriteRune( cl )
|
||||
|
||||
stmt = stmtd.BracketedValue{
|
||||
RawStatement: &stmtd.RawStatement{ Value: s },
|
||||
Brackets: b.String(),
|
||||
}
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
s := expecting.Key
|
||||
if s == "" && strings.ContainsRune( K_STATEMENT_CHARS, r ) {
|
||||
s = this.ReadUntilNot( K_STATEMENT_CHARS, true, r )
|
||||
}
|
||||
|
||||
if expecting.Statements && s != "" {
|
||||
|
||||
if this.SupportedStmts == nil {
|
||||
return nil, fmt.Errorf( "Statement not supported: %s", s )
|
||||
}
|
||||
|
||||
if f, ok := (*this.SupportedStmts)[ strings.ToUpper( s ) ]; ok {
|
||||
|
||||
// If we are using key, we need to move 1 rune back
|
||||
if expecting.Key != "" {
|
||||
err = reader.UnreadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return f( this )
|
||||
} else {
|
||||
var supported strings.Builder
|
||||
comma := false
|
||||
for k := range *this.SupportedStmts {
|
||||
if comma {
|
||||
supported.WriteString( ", " )
|
||||
} else {
|
||||
comma = true
|
||||
}
|
||||
supported.WriteString( k )
|
||||
}
|
||||
return nil, fmt.Errorf( "Statement not supported: %s, supported statements are: %s", s, supported.String() )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf( "Nothing to read" )
|
||||
}
|
||||
|
||||
func ( this *Lexer ) ReadUntilNot( charRange string, writeHead bool, head rune ) string {
|
||||
var b strings.Builder
|
||||
|
||||
if writeHead {
|
||||
b.WriteRune( head )
|
||||
}
|
||||
|
||||
reader := this.reader
|
||||
for {
|
||||
r, _, err := reader.ReadRune()
|
||||
|
||||
if err == nil && strings.ContainsRune( charRange, r ) {
|
||||
b.WriteRune( r )
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func ( this *Lexer ) ReadUntilClose( closing rune, canEsc bool, escRune rune ) ( string, error ) {
|
||||
var b strings.Builder
|
||||
|
||||
reader := this.reader
|
||||
for {
|
||||
r, _, err := reader.ReadRune()
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return "", fmt.Errorf( "Unexpected end of data after opening %s", string( closing ) )
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
if r == closing {
|
||||
break
|
||||
} else if canEsc && r == escRune {
|
||||
r2, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if r2 != closing {
|
||||
b.WriteRune( r )
|
||||
b.WriteRune( r2 )
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteRune( r )
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
119
mmql/engine/lexer_test.go
Normal file
119
mmql/engine/lexer_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
stmtd "github.com/tgckpg/mmqlengine/mmql/statements"
|
||||
)
|
||||
|
||||
func TestParse( t *testing.T ) {
|
||||
lexer := Lexer{}
|
||||
s, err := lexer.Parse( "\"ABC\"" )
|
||||
if err != nil {
|
||||
t.Error( err )
|
||||
}
|
||||
|
||||
got := (*any( s ).( stmtd.QuotedValue ).RawStatement).Value
|
||||
if got != "ABC" {
|
||||
t.Errorf( "Expected ABC, got %s", got )
|
||||
}
|
||||
|
||||
s, err = lexer.Parse( "\"ABC" )
|
||||
if err == nil {
|
||||
t.Errorf( "Expected error, got value %s", s )
|
||||
}
|
||||
|
||||
s, err = lexer.Parse( "( ABC )" )
|
||||
|
||||
got = (*any( s ).( stmtd.BracketedValue ).RawStatement).Value
|
||||
if got != " ABC " {
|
||||
t.Errorf( "Expected ABC, got %s", got )
|
||||
}
|
||||
|
||||
s, err = lexer.Parse( "( ABC" )
|
||||
if err == nil {
|
||||
t.Errorf( "Expected error, got value %s", s )
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecimal( t *testing.T ) {
|
||||
lexer := Lexer{}
|
||||
read_oks := map[string] float64 {
|
||||
"1,000k": 1000000,
|
||||
"10.8": 10.8,
|
||||
"10,000.60": 10000.6,
|
||||
"10,000.60 this_string_should_not_be_read": 10000.6,
|
||||
"10,000 this_string_should_not_be_read": 10000,
|
||||
"10.": 10,
|
||||
".2": 0.2,
|
||||
".2k": 200,
|
||||
"1 g": 1,
|
||||
" 1 g": 1,
|
||||
}
|
||||
|
||||
for k := range read_oks {
|
||||
lexer.SetReader( strings.NewReader( k ) )
|
||||
f, err := lexer.ReadDecimal()
|
||||
if err != nil {
|
||||
t.Errorf( "Expected number for %s, got %s", k, err )
|
||||
continue
|
||||
}
|
||||
|
||||
expects, _ := read_oks[ k ]
|
||||
if f != expects {
|
||||
t.Errorf( "Expected %f, got %f", expects, f )
|
||||
}
|
||||
}
|
||||
|
||||
read_fails := []string {
|
||||
"1,000ki",
|
||||
"10,000.60s",
|
||||
"10,,0",
|
||||
"100s",
|
||||
"10, 000",
|
||||
"10,",
|
||||
}
|
||||
|
||||
for _, v := range read_fails {
|
||||
lexer.SetReader( strings.NewReader( v ) )
|
||||
f, err := lexer.ReadDecimal()
|
||||
if err == nil {
|
||||
t.Errorf( "Expected error for %s, got %f", v, f )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlpha( t *testing.T ) {
|
||||
lexer := Lexer{}
|
||||
read_oks := map[string] string {
|
||||
"abcd efgh": "abcd",
|
||||
" abcd": "abcd",
|
||||
}
|
||||
|
||||
for k := range read_oks {
|
||||
lexer.SetReader( strings.NewReader( k ) )
|
||||
f, err := lexer.ReadAlpha()
|
||||
if err != nil {
|
||||
t.Errorf( "Expected string for \"%s\", got %s", k, err )
|
||||
continue
|
||||
}
|
||||
|
||||
expects, _ := read_oks[ k ]
|
||||
if f != expects {
|
||||
t.Errorf( "Expected %s, got %s", expects, f )
|
||||
}
|
||||
}
|
||||
|
||||
read_fails := []string {
|
||||
"1", " ",
|
||||
}
|
||||
|
||||
for _, v := range read_fails {
|
||||
lexer.SetReader( strings.NewReader( v ) )
|
||||
f, err := lexer.ReadAlpha()
|
||||
if err == nil {
|
||||
t.Errorf( "Expected error for %s, got %s", v, f )
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user