Skip to content

Commit

Permalink
add RPN generator
Browse files Browse the repository at this point in the history
  • Loading branch information
Stepan Pyzhov committed Mar 22, 2019
1 parent 2e6847b commit 34091c6
Show file tree
Hide file tree
Showing 6 changed files with 409 additions and 30 deletions.
339 changes: 323 additions & 16 deletions buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ajson

import (
"io"
"strings"
)

type buffer struct {
Expand All @@ -11,28 +12,111 @@ type buffer struct {
}

const (
quotes byte = '"'
quote byte = '\''
coma byte = ','
colon byte = ':'
backslash byte = '\\'
skipS byte = ' '
skipN byte = '\n'
skipR byte = '\r'
skipT byte = '\t'
bracketL byte = '['
bracketR byte = ']'
bracesL byte = '{'
bracesR byte = '}'
dollar byte = '$'
dot byte = '.'
//asterisk byte = '*'
quotes byte = '"'
quote byte = '\''
coma byte = ','
colon byte = ':'
backslash byte = '\\'
skipS byte = ' '
skipN byte = '\n'
skipR byte = '\r'
skipT byte = '\t'
bracketL byte = '['
bracketR byte = ']'
bracesL byte = '{'
bracesR byte = '}'
parenthesesL byte = '('
parenthesesR byte = ')'
dollar byte = '$'
at byte = '@'
dot byte = '.'
asterisk byte = '*'
plus byte = '+'
minus byte = '-'
division byte = '/'
exclamation byte = '!'
caret byte = '^'
signL byte = '<'
signG byte = '>'
signE byte = '='
ampersand byte = '&'
pipe byte = '|'
question byte = '?'
)

var (
_null = []byte("null")
_true = []byte("true")
_false = []byte("false")

// Operator precedence
// From https://golang.org/ref/spec#Operator_precedence
//
// Precedence Operator
// 5 * / % << >> & &^
// 4 + - | ^
// 3 == != < <= > >=
// 2 &&
// 1 ||
//
// Arithmetic operators
// From https://golang.org/ref/spec#Arithmetic_operators
//
// + sum integers, floats, complex values, strings
// - difference integers, floats, complex values
// * product integers, floats, complex values
// / quotient integers, floats, complex values
// % remainder integers
//
// & bitwise AND integers
// | bitwise OR integers
// ^ bitwise XOR integers
// &^ bit clear (AND NOT) integers
//
// << left shift integer << unsigned integer
// >> right shift integer >> unsigned integer
//
priority = map[string]int8{
"!": 7, // additional: factorial
"**": 6, // additional: power
"*": 5,
"/": 5,
"%": 5,
"<<": 5,
">>": 5,
"&": 5,
"&^": 5,
"+": 4,
"-": 4,
"|": 4,
"^": 4,
"==": 3,
"!=": 3,
"<": 3,
"<=": 3,
">": 3,
">=": 3,
"&&": 2,
"||": 1,
}

rightOp = map[string]bool{
"**": true,
}

// fixme
functions = map[string]bool{
"sin": true,
"cos": true,
}
// fixme
constants = map[string]bool{
"pi": true,
"e": true,
"true": true,
"false": true,
"null": true,
}
)

func newBuffer(body []byte) (b *buffer) {
Expand Down Expand Up @@ -187,6 +271,229 @@ func (b *buffer) step() error {
return io.EOF
}

// reads until the end of the token e.g.: `@.length`, `@['foo'].bar[(@.length - 1)].baz`
func (b *buffer) token() (err error) {
var (
c byte
str bool
stack = make([]byte, 0)
)
tokenLoop:
for ; b.index < b.length; b.index++ {
c = b.data[b.index]
switch {
case c == quote:
if !str {
str = true
stack = append(stack, c)
} else if !b.backslash() {
if len(stack) == 0 || stack[len(stack)-1] != quote {
return b.errorSymbol()
}
str = false
stack = stack[:len(stack)-1]
}
case c == bracketL && !str:
stack = append(stack, c)
case c == bracketR && !str:
if len(stack) == 0 || stack[len(stack)-1] != bracketL {
return b.errorSymbol()
}
stack = stack[:len(stack)-1]
case c == parenthesesL && !str:
stack = append(stack, c)
case c == parenthesesR && !str:
if len(stack) == 0 || stack[len(stack)-1] != parenthesesL {
return b.errorSymbol()
}
stack = stack[:len(stack)-1]
case str:
continue
case c == dot || c == at || c == dollar || c == question || c == asterisk || (c >= 'A' && c <= 'z') || (c >= '0' && c <= '9'): // standard token name
continue
case len(stack) != 0:
continue
default:
break tokenLoop
}
}
if len(stack) != 0 {
return b.errorEOF()
}
return io.EOF
}

func (b *buffer) rpn() (result []string, err error) {
var (
c byte
start int
temp string
current string
found bool
variable bool
stack = make([]string, 0)
)
for {
c, err = b.first()
if err != nil {
break
}
switch true {
case c == asterisk || c == division || c == minus || c == plus || c == caret || c == ampersand || c == pipe || c == signL || c == signG || c == signE || c == exclamation: // operations
if variable {
variable = false
current = string(c)

c, err = b.next()
if err == nil {
temp = current + string(c)
if priority[temp] != 0 {
current = temp
} else {
b.index--
}
} else {
err = nil
}

found = false
for len(stack) > 0 {
temp = stack[len(stack)-1]
found = false
if temp[0] >= 'A' && temp[0] <= 'z' { // function
found = true
} else if priority[temp] != 0 { // operation
if priority[temp] > priority[current] {
found = true
} else if priority[temp] == priority[current] && !rightOp[temp] {
found = true
}
}

if found {
stack = stack[:len(stack)-1]
result = append(result, temp)
} else {
break
}
}
stack = append(stack, current)
break
}
if c != minus && c != plus {
return nil, b.errorSymbol()
}
fallthrough // for numbers like `-1e6`
case (c >= '0' && c <= '9') || c == '.': // numbers
variable = true
start = b.index
err = b.numeric()
if err != nil && err != io.EOF {
return nil, err
}
current = string(b.data[start:b.index])
result = append(result, current)
if err != nil {
err = nil
} else {
b.index--
}
case c == quote: // string
variable = true
start = b.index
err = b.string(quote)
if err != nil {
return nil, b.errorEOF()
}
current = string(b.data[start : b.index+1])
result = append(result, current)
case c == dollar || c == at: // variable : like @.length , $.expensive, etc.
variable = true
start = b.index
err = b.token()
if err != nil {
if err != io.EOF {
return nil, err
}
err = nil
} else {
b.index--
}
current = string(b.data[start:b.index])
result = append(result, current)
case c == parenthesesL: // (
variable = false
current = string(c)
stack = append(stack, current)
case c == parenthesesR: // )
variable = true
current = string(c)
found = false
for len(stack) > 0 {
temp = stack[len(stack)-1]
stack = stack[:len(stack)-1]
if temp == "(" {
found = true
break
}
result = append(result, temp)
}
if !found { // have no parenthesesL
return nil, errorRequest("formula has no left parentheses")
}
default: // prefix functions or etc.
start = b.index
variable = true
for ; b.index < b.length; b.index++ {
c = b.data[b.index]
if c == parenthesesL { // function detection, example: sin(...), round(...), etc.
variable = false
break
}
if c < 'A' || c > 'z' {
if !(c >= '0' && c <= '9') && c != '_' { // constants detection, example: true, false, null, PI, e, etc.
break
}
}
}
current = strings.ToLower(string(b.data[start:b.index]))
b.index--
if !variable {
if _, found = functions[current]; !found {
return nil, errorRequest("wrong formula, '%s' is not a function", current)
}
stack = append(stack, current)
} else {
if _, found = constants[current]; !found {
return nil, errorRequest("wrong formula, '%s' is not a constant", current)
}
result = append(result, current)
}
}
err = b.step()
if err != nil {
break
}
}

if err != io.EOF {
return
}
err = nil

for len(stack) > 0 {
temp = stack[len(stack)-1]
_, ok := functions[temp]
if priority[temp] == 0 && !ok { // operations only
return nil, errorRequest("wrong formula, '%s' is not an operation or function", temp)
}
result = append(result, temp)
stack = stack[:len(stack)-1]
}

return
}

func (b *buffer) errorEOF() error {
return errorEOF(b)
}
Expand Down
Loading

0 comments on commit 34091c6

Please sign in to comment.