starting the project

This commit is contained in:
chunghh 2023-07-21 10:11:16 +09:00
commit 70646c256e
7 changed files with 1523 additions and 0 deletions

5
Makefile Normal file
View File

@ -0,0 +1,5 @@
all:
cd bin && CGO_ENABLED=0 go build -x -o pcl main.go
clean:
cd bin && go clean -x && rm -f pcl

119
bin/main.go Normal file
View File

@ -0,0 +1,119 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
"path"
pcl "pcl/interp"
)
func main() {
var (
interp *pcl.Interp
f *os.File
r *bufio.Reader
c rune
node *pcl.Cnode_t
v *string
err error
)
if len(os.Args) != 2 {
fmt.Printf("Usage: %s filename\n", path.Base(os.Args[0]))
goto oops
}
interp, err = pcl.NewInterp(1000, true)
if err != nil {
fmt.Printf("Error %s\n", err.Error())
goto oops
}
f, err = os.Open(os.Args[1])
if err != nil {
fmt.Printf("Error %s\n", err.Error())
goto oops
}
interp.BeginFeed()
r = bufio.NewReader(f)
for {
c, _, err = r.ReadRune()
if err != nil {
if err == io.EOF {
break
}
fmt.Printf("Error in ReadRune - %s\n", err.Error())
goto oops
}
err = interp.FeedRune(c)
if err != nil {
fmt.Printf("Error in FeedRune - %s\n", err.Error())
goto oops
}
}
node, err = interp.EndFeed()
if err != nil {
fmt.Printf("Error in EndFeed %s\n", err.Error())
goto oops
}
interp.Dump(node)
fmt.Printf("------------- Executing ----------------\n")
v, err = interp.Execute(node)
if err != nil {
fmt.Printf("Error in Execute - %s\n", err.Error())
goto oops
}
if v == nil {
panic("return value mut not be nil")
}
fmt.Printf("RETURN VALUE = [%s]\n", *v)
/*
err = interp.FeedRunes([]rune(`
proc inc{x} {
puts {10 20}
return [expr $x + 1]
}
\{abc 11 2\ \1011 \ 2\x65 \uBc29\uaD6cdefg\uZZ\xZZ\U0000BC29\UAD6cZZ \
[donkey 1 [expr [expr 2 + 3] + 3] ]
hello { world { }man}
"command { l a n g }"
set a [puts "1111" "22 22" "3333 [expr "123" + 2] 4444"]
abc [expr [expr 2 + "4[expr 2 * 6]"] + 9]
puts $a ${ kkk qqq }
puts "\x65\ubc29\n"
{}`))
*/
//err = interp.FeedRunes([]rune(`hello [world [1 9] 2]
//`))
/*
if err != nil {
fmt.Printf("ERROR %s\n", err)
} else {
err = interp.EndFeed()
if err != nil {
fmt.Printf("ERROR %s\n", err)
}
}*/
interp.Close()
f.Close()
os.Exit(0)
oops:
if interp != nil {
interp.Close()
}
if f != nil {
f.Close()
}
os.Exit(1)
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module pcl
go 1.20

305
interp/eval.go Normal file
View File

@ -0,0 +1,305 @@
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
}

649
interp/feed.go Normal file
View File

@ -0,0 +1,649 @@
package interp
import (
"unicode"
)
var err_refeed *error_t = &error_t{msg: "refeed"} // this is not a real error and is used as a control element
var err_sudden_eof *error_t = &error_t{msg: "unexpected end of input"}
var err_right_bracket *error_t = &error_t{msg: "unbalanced right bracket"}
var err_right_brace *error_t = &error_t{msg: "unbalanced right brace"}
var err_feed_state *error_t = &error_t{msg: "internal error - invalid feed state"}
func is_delim(c rune) bool {
return c == EOF_RUNE || c == '[' || c == ']' || c == '{' || c == '}' || c == '"' || c == ';' || unicode.IsSpace(c)
}
func is_xdigit(c rune) bool {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
}
func digit_to_code(c rune) int32 {
if c >= '0' && c <= '9' {
return int32(c) - int32('0')
} else if c >= 'A' && c <= 'Z' {
return int32(c) - int32('A') + 10
} else if c >= 'a' && c <= 'z' {
return int32(c) - int32('a') + 10
} else {
return 0 // this is an error
}
}
func escape_max_to_rune(max int) rune {
switch max {
case 8:
return 'U'
case 4:
return 'u'
case 3:
return '0'
case 2:
return 'x'
default:
return '\x00'
}
}
func escape_single_rune(c rune) rune {
switch c {
case 'a':
return '\a'
case 'b':
return '\b'
case 'f':
return '\f'
case 'n':
return '\n'
case 'r':
return '\r'
case 't':
return '\t'
case 'v':
return '\v'
case '\n':
return ' '
default:
return c
}
}
func (interp *Interp) init_or_cmd(c rune, mode int) error {
var err error
if c == EOF_RUNE {
if mode >= 1 {
/* not in INIT */
return err_sudden_eof
}
interp.pop_feed_struct()
} else if c == '\\' {
interp.push_feed_struct(FEED_WORD)
interp.start_escape()
} else if c == '"' {
interp.push_feed_struct(FEED_DQUOTE)
} else if c == '[' {
interp.push_feed_struct(FEED_BRACKET)
} else if c == ']' {
if mode == 1 && interp.feed.state == FEED_BRACKET {
interp.pop_feed_struct()
} else if interp.strict {
err = err_right_bracket
} else {
goto normal_rune
}
} else if c == '{' {
interp.push_feed_struct(FEED_BRACE)
} else if c == '}' {
if mode == 2 && interp.feed.state == FEED_BRACE {
interp.pop_feed_struct()
} else if interp.strict {
err = err_right_brace
} else {
goto normal_rune
}
} else if c == '$' {
interp.push_feed_struct(FEED_DOLLAR)
} else if c == '\n' || c == ';' {
interp.push_feed_struct(FEED_EOL)
interp.add_rune_to_token(c)
} else if unicode.IsSpace(c) {
interp.push_feed_struct(FEED_SEP)
interp.add_rune_to_token(c)
} else {
goto normal_rune
}
done:
return err
normal_rune:
interp.push_feed_struct(FEED_WORD)
interp.add_rune_to_token(c)
goto done
}
func (interp *Interp) do_init(c rune) error {
return interp.init_or_cmd(c, 0)
}
func (interp *Interp) do_bracket(c rune) error {
return interp.init_or_cmd(c, 1)
}
func (interp *Interp) do_brace(c rune) error {
/*
var err error
if c == '}' {
interp.pop_feed_struct()
} else {
interp.push_feed_struct(FEED_BRACED_TEXT)
err = err_refeed
}
return err
*/
return interp.init_or_cmd(c, 2)
}
func (interp *Interp) do_braced_text(c rune) error {
var err error
/* text enclosed in {} */
if c == EOF_RUNE {
err = err_sudden_eof
} else if interp.feed.extra.escaped {
// escaping in braces are simpler than the regular escaping(handle_escape())
if c == '\n' {
c = ' '
}
interp.add_rune_to_token(c)
interp.feed.extra.escaped = false
} else if c == '\\' {
// escaping is supported inside {}. but escaping is a bit different
// {abc\}} produces abc\}, not abc}
interp.feed.extra.escaped = true /* so not using start_escape() */
interp.add_rune_to_token(c)
} else if c == '{' {
interp.feed.extra.brace_count++
interp.add_rune_to_token(c)
} else if c == '}' {
if interp.feed.extra.brace_count > 0 {
interp.add_rune_to_token(c)
interp.feed.extra.brace_count--
} else {
interp.pop_feed_struct()
err = err_refeed // back to FEED_BRACE
}
} else {
interp.add_rune_to_token(c)
}
return err
}
func (interp *Interp) do_dquote(c rune) error {
var err error
/*
if c == '"' {
interp.pop_feed_struct()
// } else if c == '[' {
// interp.push_feed_struct(FEED_BRACKET)
// } else if c == ']' && interp.strict {
// err = err_right_bracket
} else {
interp.push_feed_struct(FEED_DQUOTED_TEXT)
err = err_refeed
}
return err
*/
if c == EOF_RUNE {
return err_sudden_eof
} else if c == '\\' {
interp.push_feed_struct(FEED_DQUOTED_TEXT)
interp.start_escape()
} else if c == '"' {
interp.pop_feed_struct()
} else if c == '[' {
interp.push_feed_struct(FEED_BRACKET)
} else if c == ']' {
if interp.feed.state == FEED_BRACKET {
interp.pop_feed_struct()
} else if interp.strict {
err = err_right_bracket
} else {
goto normal_rune
}
} else if c == '$' {
interp.push_feed_struct(FEED_DOLLAR)
} else {
goto normal_rune
}
done:
return err
normal_rune:
interp.push_feed_struct(FEED_DQUOTED_TEXT)
interp.add_rune_to_token(c)
goto done
}
func (interp *Interp) do_dquoted_text(c rune) error {
var err error
/*
if c == EOF_RUNE {
err = err_sudden_eof
} else if interp.feed.extra.escaped {
err = interp.handle_escape(c)
} else if c == '\\' {
interp.start_escape()
} else if c == '"' {
interp.pop_feed_struct()
err = err_refeed
} else if c == '[' {
interp.pop_feed_struct()
err = err_refeed
} else if c == ']' && interp.strict {
err = err_right_bracket
} else if c == '$' {
// a variable inside a double-quoted string
interp.push_feed_struct(FEED_DOLLAR)
} else {
interp.add_rune_to_token(c)
}*/
if interp.feed.extra.escaped {
err = interp.handle_escape(c)
} else if c == '\\' {
interp.start_escape()
} else if c == EOF_RUNE || c == '"' || c == '[' {
interp.pop_feed_struct()
err = err_refeed
} else {
interp.add_rune_to_token(c)
}
return err
}
func (interp *Interp) do_dollar(c rune) error {
var err error
if unicode.IsLetter(c) || c == '_' || unicode.IsDigit(c) {
interp.add_rune_to_token(c)
} else if c == '{' {
if len(interp.feed.token) > 0 {
err = err_refeed
} else {
interp.feed.extra.var_braced = true
}
} else if c == '}' && interp.feed.extra.var_braced {
interp.pop_feed_struct()
} else if interp.feed.extra.var_braced {
interp.add_rune_to_token(c)
} else {
if len(interp.feed.token) == 0 {
// $ is followed by an invalid variable letter.
// switch to a normal word mode by hack and add a dollar sign as a token
interp.feed.state = FEED_WORD
interp.add_rune_to_token('$')
} else {
interp.pop_feed_struct()
}
err = err_refeed
}
return err
}
func (interp *Interp) do_word(c rune) error {
var err error
if interp.feed.extra.escaped {
err = interp.handle_escape(c)
} else if c == '\\' {
interp.start_escape()
} else if is_delim(c) { // TODO: if not str characters
interp.pop_feed_struct()
err = err_refeed
} else {
interp.add_rune_to_token(c)
}
return err
}
func (interp *Interp) do_sep(c rune) error {
if c == '\n' || !unicode.IsSpace(c) {
interp.pop_feed_struct()
return err_refeed
}
interp.add_rune_to_token(c)
return nil
}
func (interp *Interp) do_eol(c rune) error {
if c != '\n' {
interp.pop_feed_struct()
return err_refeed
}
interp.add_rune_to_token(c)
return nil
}
func (interp *Interp) push_feed_struct(state feed_state_t) *feed_struct_t {
var feed *feed_struct_t = &feed_struct_t{state: state}
feed.parent = interp.feed
if feed.parent == nil {
feed.level = 0
} else {
feed.level = feed.parent.level + 1
}
interp.feed = feed
return feed
}
func (interp *Interp) finalize_feed_struct(feed *feed_struct_t, is_stmt bool) {
var stmt_cnode *Cnode_t
if feed.cnode_tmp_last != nil {
if is_stmt {
// wrap the list of cnodes as a single statement node
stmt_cnode = &Cnode_t{code: CNODE_STMT, child: feed.cnode_tmp_first} // token is empty
if feed.cnode_last == nil {
feed.cnode_first = stmt_cnode
} else {
feed.cnode_last.next = stmt_cnode
}
feed.cnode_last = stmt_cnode
} else {
if feed.cnode_last == nil {
feed.cnode_first = feed.cnode_tmp_first
} else {
feed.cnode_last.next = feed.cnode_tmp_first
}
feed.cnode_last = feed.cnode_tmp_last
}
feed.cnode_tmp_first = nil
feed.cnode_tmp_last = nil
}
}
func (inter *Interp) add_cnode_to_feed_struct(feed *feed_struct_t, cnode *Cnode_t) {
if feed.cnode_tmp_last == nil {
feed.cnode_tmp_first = cnode
} else {
feed.cnode_tmp_last.next = cnode
}
feed.cnode_tmp_last = cnode
}
var feed_to_cnode_code_tab [11]cnode_code_t = [11]cnode_code_t{
FEED_TOP: CNODE_EOL, // this must never be used
FEED_INIT: CNODE_INIT,
FEED_BRACKET: CNODE_BRACKET,
FEED_BRACE: CNODE_BRACE,
FEED_BRACED_TEXT: CNODE_TEXT,
FEED_DQUOTE: CNODE_DQUOTE,
FEED_DQUOTED_TEXT: CNODE_TEXT,
FEED_DOLLAR: CNODE_VAR,
FEED_SEP: CNODE_SEP,
FEED_WORD: CNODE_TEXT,
FEED_EOL: CNODE_EOL,
}
func (interp *Interp) pop_feed_struct() *feed_struct_t {
var (
feed *feed_struct_t
parent *feed_struct_t
cnode *Cnode_t
)
feed = interp.feed // to pop off
if feed == nil {
// excessive pop request. it's an internal implementation error
// TODO: return error???
panic("excessive pop off feed stack")
return nil // TODO: return failure???
}
parent = feed.parent
//fmt.Printf("parent %p feed [%p]>>\n", parent, feed)
if parent == nil {
// this must be FEED_TOP being popped off.
if feed.state != FEED_TOP {
panic("invalid internal state - top feed struct not FEED_TOP")
}
interp.feed = nil // indicate that the feed stack is empty
return feed // return the old feed struct
}
if feed.state == FEED_DOLLAR && len(feed.token) == 0 {
// switch the dollar sign not followed by name to a literal dollar sign text
feed.state = FEED_WORD
interp.add_rune_to_token('$')
}
if feed.state == FEED_EOL {
interp.finalize_feed_struct(parent, true)
parent.cnode_cont = false
} else if feed.state == FEED_SEP {
parent.cnode_cont = false
} else {
cnode = &Cnode_t{code: feed_to_cnode_code_tab[feed.state], token: feed.token}
if cnode.code == CNODE_BRACKET || cnode.code == CNODE_BRACE || cnode.code == CNODE_INIT {
// popping a container feed struct
interp.finalize_feed_struct(feed, true)
cnode.child = feed.cnode_first
} else if cnode.code == CNODE_DQUOTE {
interp.finalize_feed_struct(feed, false)
cnode.child = feed.cnode_first
} /*else if cnode.code == CNODE_VAR {
fmt.Printf(">>>>\n")
interp.dump_cnodes(parent.cnode_first, true)
fmt.Printf(">>>>\n")
interp.dump_cnodes(parent.cnode_tmp_first, true)
fmt.Printf(">>>>\n")
interp.dump_cnodes(cnode, true)
}*/
// add the current cnode to the parent feed struct
interp.add_cnode_to_feed_struct(parent, cnode)
if parent.cnode_cont {
interp.add_cnode_to_feed_struct(parent, &Cnode_t{code: CNODE_JOIN})
} else {
parent.cnode_cont = true
}
if feed.state == FEED_INIT {
if parent.state != FEED_TOP {
panic("internal error - parent struct not FEED_TOP")
}
interp.finalize_feed_struct(parent, false)
}
}
interp.feed = parent
return feed
}
func (interp *Interp) add_rune_to_token(c rune) {
interp.feed.token = append(interp.feed.token, c)
}
func (interp *Interp) start_escape() {
interp.feed.extra.escaped = true
interp.feed.extra.escape_len = 0
interp.feed.extra.escape_max = 0
interp.feed.extra.escape_val = 0
}
func (interp *Interp) end_escape() {
interp.feed.extra.escaped = false
}
func (interp *Interp) handle_escape(c rune) error {
if c == EOF_RUNE {
goto stop_escaping
}
switch interp.feed.extra.escape_max {
case 8: // \UXXXXXXXX
fallthrough
case 4: // \uXXXX
fallthrough
case 2: // \xXX
if is_xdigit(c) {
interp.feed.extra.escape_val = interp.feed.extra.escape_val*16 + digit_to_code(c)
interp.feed.extra.escape_len++
} else {
goto stop_escaping
}
case 3: // \ooo
if c >= '0' && c <= '7' {
interp.feed.extra.escape_val = interp.feed.extra.escape_val*8 + digit_to_code(c)
interp.feed.extra.escape_len++
} else {
goto stop_escaping
}
case 0:
if c == 'x' {
interp.feed.extra.escape_max = 2
} else if c == 'u' {
interp.feed.extra.escape_max = 4
} else if c == 'U' {
interp.feed.extra.escape_max = 8
} else if c >= '0' && c <= '7' {
interp.feed.extra.escape_max = 3
return err_refeed
} else {
interp.add_rune_to_token(escape_single_rune(c))
interp.end_escape()
return nil
}
default:
goto stop_escaping
}
if interp.feed.extra.escape_len == interp.feed.extra.escape_max {
interp.add_rune_to_token(rune(interp.feed.extra.escape_val))
interp.end_escape()
}
return nil
stop_escaping:
if interp.feed.extra.escape_len == 0 {
c = escape_max_to_rune(interp.feed.extra.escape_max)
if c != '\x00' {
interp.add_rune_to_token(c)
}
} else {
interp.add_rune_to_token(rune(interp.feed.extra.escape_val))
}
interp.end_escape()
return err_refeed
}
// -------------------------------------------------------------------------------
func (interp *Interp) BeginFeed() {
for interp.feed != nil {
interp.pop_feed_struct()
}
interp.push_feed_struct(FEED_TOP)
interp.push_feed_struct(FEED_INIT)
}
func (interp *Interp) FeedRune(c rune) error {
var err error
start_over:
switch interp.feed.state {
case FEED_INIT:
err = interp.do_init(c)
case FEED_BRACKET:
err = interp.do_bracket(c)
case FEED_BRACE:
err = interp.do_brace(c)
case FEED_BRACED_TEXT:
err = interp.do_braced_text(c)
case FEED_DQUOTE:
err = interp.do_dquote(c)
case FEED_DQUOTED_TEXT:
err = interp.do_dquoted_text(c)
case FEED_DOLLAR:
err = interp.do_dollar(c)
case FEED_SEP:
err = interp.do_sep(c)
case FEED_WORD:
err = interp.do_word(c)
case FEED_EOL:
err = interp.do_eol(c)
default:
err = err_feed_state
}
if err == err_refeed {
goto start_over
}
return err
}
func (interp *Interp) FeedRunes(text []rune) error {
var (
c rune
err error
)
for _, c = range text {
err = interp.FeedRune(c)
if err != nil {
return err
}
}
return nil
}
func (interp *Interp) EndFeed() (*Cnode_t, error) {
var err error = interp.FeedRune(EOF_RUNE)
if err != nil {
return nil, err
}
if interp.feed == nil || interp.feed.state != FEED_TOP {
return nil, err_feed_state
}
return interp.feed.cnode_first, nil
}

366
interp/pcl.go Normal file
View File

@ -0,0 +1,366 @@
package interp
import (
"fmt"
"runtime"
"unsafe"
)
type error_t struct {
msg string
}
func (m *error_t) Error() string {
return m.msg
}
type Var struct {
next *Var
name string
val string
}
type rcode_t int
const (
R_OK rcode_t = iota
R_ERR
R_RETURN
R_BREAK
R_CONTINUE
)
const NULL_RUNE rune = '\u0000'
const EOF_RUNE rune = rune(^0)
type context_t struct {
count int
parent_ctx *context_t
parent_node *Cnode_t
}
type process_t struct {
interp *Interp
stack [16]unsafe.Pointer // value stack - TODO: change size
sp int
ctx *context_t
}
type call_frame_t struct {
vars *Var
parent *call_frame_t
}
type cnode_code_t int
const (
CNODE_INIT cnode_code_t = iota
CNODE_BRACKET
CNODE_BRACE
CNODE_DQUOTE
CNODE_VAR
CNODE_TEXT
CNODE_SEP
CNODE_EOL
CNODE_STMT
CNODE_JOIN // merge the two top elements off the stack
)
type feed_state_t int
const (
FEED_TOP feed_state_t = iota
FEED_INIT
FEED_BRACKET // container for []
FEED_BRACE // container for {}
FEED_BRACED_TEXT // uninterpreted text enclosed in {}
FEED_DQUOTE // container for ""
FEED_DQUOTED_TEXT // literal text inside ""
FEED_DOLLAR // variable reference
FEED_SEP // separator
FEED_WORD // unquoted word
FEED_EOL // end of line
)
type feed_struct_t struct {
parent *feed_struct_t
level int
state feed_state_t
token []rune
cnode_first *Cnode_t
cnode_last *Cnode_t
cnode_tmp_first *Cnode_t
cnode_tmp_last *Cnode_t
cnode_cont bool
extra struct {
brace_count int
var_braced bool
escaped bool
escape_len int
escape_max int
escape_val int32
}
}
type Cnode_t struct {
next *Cnode_t
child *Cnode_t // for container nodes
code cnode_code_t
seqno int
token []rune
}
type Interp struct {
strict bool
level int
max_level int
feed *feed_struct_t
call_frame *call_frame_t
result string
}
func NewInterp(max_level int, strict bool) (*Interp, error) {
var (
interp *Interp
)
interp = &Interp{
strict: strict,
level: 0,
max_level: max_level,
}
runtime.SetFinalizer(interp, func(interp *Interp) {
interp.Close()
})
interp.push_feed_struct(FEED_TOP)
interp.push_feed_struct(FEED_INIT)
return interp, nil
}
func (interp *Interp) Close() {
for interp.feed != nil {
interp.pop_feed_struct()
}
}
/*
func (interp *Interp) GetVar(name string, global bool) *Var {
var (
v *Var
f *CallFrame
)
if global || is_clone_prefixed(name) {
f = interp.call_frame
for f.parent != nil {
f = f.parent
}
v = f.vars
} else {
v = interp.call_frame.vars
}
// TODO: handle array
for v != nil {
// TODO: make the variable list can be hash map...
if v.name == name {
return v
}
}
return nil
}
func (interp *Interp) SetVar(name string, val string, global bool) {
var (
f *CallFrame
v *Var
)
f = interp.call_frame
v = interp.GetVar(name, global)
if v != nil {
//if v->val = nil { // IS THIS POSSIBLE
//
//}
} else {
// TODO: handle aray
if global || is_clone_prefixed((name)) {
// TODD avand name by two charactes if clone_prefixed
f = get_top_call_frame(f)
}
v = &Var{name: name}
v.next = f.vars
f.vars = v
}
v.val = val
}
func (interp *Interp) SetGlobalVar(name string, val string) {
}
func (interp *Interp) SetIntVar(name string, val int) {
}
func is_clone_prefixed(name string) bool {
return len(name) > 2 && name[0] == ':' && name[1] == ':'
}
func get_top_call_frame(f *CallFrame) *CallFrame {
for f.parent != nil {
f = f.parent
}
return f
}
*/
func (interp *Interp) Execute(node_head *Cnode_t) (*string, error) {
var (
node *Cnode_t
v *string
err error
)
v = new(string) // if there is no code the execute, the return value is an empty string
for node = node_head; node != nil; node = node.next {
if node.code != CNODE_INIT {
return nil, fmt.Errorf("non-init node")
}
if node.child == nil { // TODO: optmize the cnode tree that this check is not needed. the reader must not create an INIT node with empty
break
}
v, err = interp.eval_atom_node(node.child)
if err != nil {
return nil, err
}
}
return v, nil
}
func (interp *Interp) dump_cnodes(node *Cnode_t, nl bool) {
interp.__dump_cnodes(node, nl, 0)
}
func (interp *Interp) print_tabs(tabs int) {
var i int
for i = 0; i < tabs; i++ {
fmt.Printf(" ")
}
}
func (interp *Interp) __dump_cnodes(node *Cnode_t, nl bool, tabs int) {
for node != nil {
switch node.code {
case CNODE_INIT:
//fmt.Printf("[I]")
interp.__dump_cnodes(node.child, false, tabs)
//fmt.Printf("[E]")
fmt.Printf("\n")
case CNODE_STMT:
//fmt.Printf("[S]")
interp.print_tabs(tabs)
interp.__dump_cnodes(node.child, false, tabs)
//fmt.Printf("[E]")
if node.next != nil {
fmt.Printf("\n")
}
case CNODE_BRACKET: // this is a container
if node.child == nil {
fmt.Printf("[]")
} else if node.child.next == nil {
// only 1 item inside brackets
fmt.Printf("[")
interp.__dump_cnodes(node.child, false, 0)
fmt.Printf("]")
} else {
fmt.Printf("[\n")
interp.__dump_cnodes(node.child, false, tabs+1)
fmt.Printf("\n")
interp.print_tabs(tabs)
fmt.Printf("]")
}
case CNODE_BRACE: // this is a container
if node.child == nil {
fmt.Printf("{}")
} else if node.child.next == nil {
// only 1 item inside braces
fmt.Printf("{")
interp.__dump_cnodes(node.child, false, 0)
fmt.Printf("}")
} else {
fmt.Printf("{\n")
interp.__dump_cnodes(node.child, false, tabs+1)
fmt.Printf("\n")
interp.print_tabs(tabs)
fmt.Printf("}")
}
case CNODE_DQUOTE: // this is a container
fmt.Printf("\"")
interp.__dump_cnodes(node.child, false, 0)
fmt.Printf("\"")
case CNODE_VAR:
fmt.Printf("${%s}", string(node.token))
case CNODE_TEXT:
// TODO if text contains some specical characters while not
// contained inside other containers, some escaping is required
fmt.Printf("%s", string(node.token))
case CNODE_SEP:
fmt.Printf(" ")
case CNODE_EOL:
fmt.Printf("\n")
case CNODE_JOIN:
// do nothing
//fmt.Printf("<M>")
default:
fmt.Printf("UNKNOWN")
}
if node.code != CNODE_STMT {
if node.next != nil && node.next.next != nil && node.next.next.code == CNODE_JOIN {
// do nothing
} else if node.next != nil && node.next.code == CNODE_JOIN {
// do nothing
} else if node.next != nil {
fmt.Printf(" ")
}
}
node = node.next
}
//if nl {
// fmt.Printf("\n")
//}
}
func (interp *Interp) Dump(node *Cnode_t) {
interp.dump_cnodes(node, true)
}

76
interp/proc.go Normal file
View File

@ -0,0 +1,76 @@
package interp
import "fmt"
func proc_expr(p *process_t) error {
return nil
}
func proc_if(p *process_t) error {
var (
v *string
err error
)
if p.GetNumArgs() < 2 {
err = err_num_args
goto done
}
//v, err = p.interp.EvalText([]rune(*p.GetArg(0)))
v, err = p.interp.eval_arg(p, 0)
if err != nil {
goto done
}
if *v != "" {
//v, err = p.interp.eval_atom_node((*Cnode_t)(p.GetArg(1)))
v, err = p.interp.eval_arg(p, 1)
if err != nil {
goto done
}
p.Return(*v)
} else {
// TODO: if elseif else
}
done:
return err
}
func proc_puts(p *process_t) error {
var (
i int
nargs int
v *string
err error
)
//fmt.Printf("callee=%s\n", *(p.GetCalleeName()))
//fmt.Printf("callee=%s\n", *(p.GetArg(-1)))
//fmt.Printf("arg count = %d\n", p.GetNumArgs())
nargs = p.GetNumArgs()
for i = 0; i < nargs; i++ {
//fmt.Printf("%s", *p.GetArg(i))
v, err = p.interp.eval_arg_literally(p, i)
if err != nil {
return err
}
fmt.Printf("%s", *v)
}
if nargs >= 1 {
p.Return(*v)
} else {
p.Return("hello")
}
return nil
}
func proc_unknown(p *process_t) error {
fmt.Printf("Unknown command - %s\n", *(p.GetCalleeName()))
return nil
}