306 lines
6.0 KiB
Go
306 lines
6.0 KiB
Go
|
package interp
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"unsafe"
|
||
|
)
|
||
|
|
||
|
var err_num_args *error_t = &error_t{msg: "wrong number of arguments"}
|
||
|
|
||
|
/*
|
||
|
value stack (p.stack)
|
||
|
|
||
|
<--- SP
|
||
|
ARG1
|
||
|
ARG0
|
||
|
NAME
|
||
|
RET
|
||
|
|
||
|
evaluation stack (p.ctx)
|
||
|
*/
|
||
|
|
||
|
func (p *process_t) push_cnode_value(val *Cnode_t) error {
|
||
|
if p.sp >= cap(p.stack) {
|
||
|
return fmt.Errorf("stack full")
|
||
|
}
|
||
|
|
||
|
p.stack[p.sp] = unsafe.Pointer(uintptr(unsafe.Pointer(val)) | 1)
|
||
|
p.sp++
|
||
|
p.ctx.count++
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (p *process_t) push_string_value(val string) error {
|
||
|
if p.sp >= cap(p.stack) {
|
||
|
return fmt.Errorf("stack full")
|
||
|
}
|
||
|
|
||
|
p.stack[p.sp] = unsafe.Pointer(&val)
|
||
|
p.sp++
|
||
|
p.ctx.count++
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (p *process_t) merge_top_values() error {
|
||
|
var new_val string
|
||
|
|
||
|
if p.sp < 2 {
|
||
|
return fmt.Errorf("stack corrupt")
|
||
|
}
|
||
|
new_val = *(*string)(p.stack[p.sp-2]) + *(*string)(p.stack[p.sp-1])
|
||
|
p.sp--
|
||
|
p.stack[p.sp] = nil
|
||
|
p.stack[p.sp-1] = unsafe.Pointer(&new_val)
|
||
|
p.ctx.count--
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (p *process_t) pop_value() unsafe.Pointer {
|
||
|
var v unsafe.Pointer
|
||
|
p.sp--
|
||
|
v = p.stack[p.sp]
|
||
|
p.stack[p.sp] = nil
|
||
|
return v
|
||
|
}
|
||
|
|
||
|
func (p *process_t) call() error {
|
||
|
var (
|
||
|
proc func(*process_t) error
|
||
|
callee *string
|
||
|
)
|
||
|
|
||
|
callee = p.GetCalleeName()
|
||
|
// TODO: use a map
|
||
|
switch *callee {
|
||
|
case "if":
|
||
|
proc = proc_if
|
||
|
case "puts":
|
||
|
proc = proc_puts
|
||
|
default:
|
||
|
proc = proc_unknown
|
||
|
}
|
||
|
return proc(p)
|
||
|
}
|
||
|
|
||
|
func (p *process_t) GetCalleeName() *string {
|
||
|
return (*string)(p.stack[p.sp-p.ctx.count+1])
|
||
|
}
|
||
|
|
||
|
func (p *process_t) GetArg(idx int) unsafe.Pointer {
|
||
|
return (p.stack[p.sp-p.ctx.count+2+idx])
|
||
|
}
|
||
|
|
||
|
func (p *process_t) GetNumArgs() int {
|
||
|
return p.ctx.count - 2
|
||
|
}
|
||
|
|
||
|
func (p *process_t) Return(val string) {
|
||
|
p.stack[p.sp-p.ctx.count] = unsafe.Pointer(&val)
|
||
|
}
|
||
|
|
||
|
func (p *process_t) push_context(node *Cnode_t) {
|
||
|
p.ctx = &context_t{count: 0, parent_ctx: p.ctx, parent_node: node}
|
||
|
}
|
||
|
|
||
|
func (p *process_t) pop_context() (node *Cnode_t) {
|
||
|
var i int
|
||
|
|
||
|
node = p.ctx.parent_node
|
||
|
|
||
|
// clean up the unused part of the stack
|
||
|
for i = 1; i < p.ctx.count; i++ {
|
||
|
p.stack[p.sp-p.ctx.count+i] = nil
|
||
|
}
|
||
|
|
||
|
// pop off the cleaned arguments
|
||
|
p.sp -= p.ctx.count - 1 // keep the return value in the stack
|
||
|
p.ctx = p.ctx.parent_ctx
|
||
|
|
||
|
if p.ctx != nil {
|
||
|
p.ctx.count++ // let the return value be the argument to the caller
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (interp *Interp) eval_atom_node(node *Cnode_t) (*string, error) {
|
||
|
|
||
|
var (
|
||
|
p process_t
|
||
|
v *string
|
||
|
err error
|
||
|
)
|
||
|
|
||
|
p.interp = interp
|
||
|
p.push_context(nil)
|
||
|
p.push_string_value("") // placeholder for return value
|
||
|
|
||
|
for node != nil {
|
||
|
switch node.code {
|
||
|
case CNODE_BRACKET: // this is a container
|
||
|
if node.child != nil {
|
||
|
p.push_context(node)
|
||
|
err = p.push_string_value("") // placeholder for return value
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
node = node.child
|
||
|
continue
|
||
|
} else {
|
||
|
err = p.push_string_value("")
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case CNODE_BRACE: // this is a container
|
||
|
err = p.push_cnode_value(node)
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
|
||
|
case CNODE_DQUOTE: // this is a container
|
||
|
// TODO: at the feed layer, recompose CNODE_DQUOTE item to
|
||
|
// successive ATOM item
|
||
|
// "abc $ddd [xx 11]" must be TEXT("abc ") + VAR("ddd") + BRACKET["xx 11"] without SEP in between
|
||
|
|
||
|
// this is not right.....
|
||
|
if node.child != nil {
|
||
|
// TODO: something is not right here. handle inner stuffs
|
||
|
//fmt.Printf("pushing [%s]\n", string(node.child.token))
|
||
|
err = p.push_string_value(string(node.child.token))
|
||
|
} else {
|
||
|
err = p.push_string_value("")
|
||
|
}
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
|
||
|
case CNODE_VAR:
|
||
|
|
||
|
case CNODE_TEXT:
|
||
|
//fmt.Printf("pushing [%s]\n", string(node.token))
|
||
|
err = p.push_string_value(string(node.token))
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
|
||
|
case CNODE_SEP:
|
||
|
// skip
|
||
|
case CNODE_EOL:
|
||
|
// skip
|
||
|
|
||
|
default:
|
||
|
err = fmt.Errorf("internal error - non-atom node - %d", node.code)
|
||
|
goto oops
|
||
|
}
|
||
|
|
||
|
node = node.next
|
||
|
check_end:
|
||
|
if node == nil { // reached the last node
|
||
|
err = p.call()
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
if p.ctx.parent_ctx != nil {
|
||
|
node = p.pop_context()
|
||
|
if node.seqno > 0 {
|
||
|
// merge the top 2 values (return from [] + previous argument)
|
||
|
// for instance, the expression `aa[qq 11]` must product a single word
|
||
|
// `aa` concatenated of the result of `qq 11`
|
||
|
err = p.merge_top_values()
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
}
|
||
|
node = node.next
|
||
|
|
||
|
// take `aa 11 22 [dd [bb cc]]` as an example
|
||
|
// after [bb cc] is called, it must call `[dd]`` followed by `aa`.
|
||
|
// this goto loop is to handle successive calls when a nested call is the
|
||
|
// last argument
|
||
|
goto check_end
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
v = (*string)(p.pop_value())
|
||
|
p.pop_context()
|
||
|
if p.ctx != nil {
|
||
|
err = fmt.Errorf("internal error - dangling process context")
|
||
|
goto oops
|
||
|
}
|
||
|
|
||
|
return v, nil
|
||
|
|
||
|
oops:
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
func (interp *Interp) eval_arg(p *process_t, pos int) (*string, error) {
|
||
|
var (
|
||
|
ptr uintptr
|
||
|
)
|
||
|
|
||
|
ptr = uintptr(p.GetArg(pos))
|
||
|
if ptr&1 == 1 { // cnode
|
||
|
ptr &= ^uintptr(1)
|
||
|
interp.dump_cnodes((*Cnode_t)(unsafe.Pointer(ptr)), true)
|
||
|
return interp.eval_atom_node((*Cnode_t)(unsafe.Pointer(ptr)).child)
|
||
|
} else {
|
||
|
return (*string)(unsafe.Pointer(ptr)), nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (interp *Interp) eval_arg_literally(p *process_t, pos int) (*string, error) {
|
||
|
var (
|
||
|
ptr uintptr
|
||
|
//cnode *Cnode_t
|
||
|
)
|
||
|
|
||
|
ptr = uintptr(p.GetArg(pos))
|
||
|
if ptr&1 == 1 { // cnode
|
||
|
ptr &= ^uintptr(1)
|
||
|
//cnode = (*Cnode_t)(unsafe.Pointer(ptr))
|
||
|
//cnode.child i hate this portion....
|
||
|
return nil, fmt.Errorf("not supported - unable to evaluate {} literally")
|
||
|
} else {
|
||
|
return (*string)(unsafe.Pointer(ptr)), nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (interp *Interp) EvalText(text []rune) (*string, error) {
|
||
|
var (
|
||
|
v *string
|
||
|
node *Cnode_t
|
||
|
err error
|
||
|
)
|
||
|
|
||
|
interp.BeginFeed() // this resets the feed stack to the initial state
|
||
|
|
||
|
err = interp.FeedRunes(text)
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
|
||
|
node, err = interp.EndFeed()
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
|
||
|
//fmt.Printf("--------------------\n")
|
||
|
//interp.dump_cnodes(node, true)
|
||
|
//fmt.Printf("--------------------\n")
|
||
|
|
||
|
v, err = interp.Execute(node)
|
||
|
if err != nil {
|
||
|
goto oops
|
||
|
}
|
||
|
|
||
|
return v, nil
|
||
|
|
||
|
oops:
|
||
|
return nil, err
|
||
|
}
|