Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new gcs evaluator - attempt at fixing performance issue introduced via gcsl rework #1686

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c15d59b
initial
srliao Sep 2, 2023
2ba17ac
add test helper func
srliao Sep 2, 2023
7b78da3
ad fnstmt and fnexpr
srliao Sep 2, 2023
8054c2a
add basic call and ident expr
srliao Sep 5, 2023
53e6a63
change evalNext to eval until next action
srliao Sep 5, 2023
a7e1d80
minor action block optimization
srliao Sep 6, 2023
a69d6ec
add map expr
srliao Sep 6, 2023
7170bbd
call expr + bug fixes:
srliao Sep 6, 2023
bd7bfc1
eval to implement action.Evaluator
srliao Sep 6, 2023
e3a36ac
fix block stmt nil return bug
srliao Sep 6, 2023
da5cb68
add todos
srliao Sep 22, 2023
74062b8
refactor env to constructor
srliao Sep 22, 2023
a00f57e
fix linting (except unused - todo)
srliao Sep 22, 2023
fc51576
add let stmt
srliao Sep 22, 2023
c19ae28
fix expr_call lint
srliao Sep 23, 2023
a5a8cc5
add if else
srliao Sep 23, 2023
3552f96
refactor env to return bool isntead of error
srliao Sep 23, 2023
de46ff9
adjust put and add assign
srliao Sep 23, 2023
65964e5
add assign
srliao Sep 23, 2023
a1cdced
add while
srliao Sep 23, 2023
eb837ab
nolint otob for now
srliao Sep 23, 2023
141c91f
for stmt
srliao Sep 23, 2023
8988cb7
add conditionals
srliao Jan 2, 2024
dc0a32d
change cmd to use new eval
srliao Jan 2, 2024
3fa35d1
add back sysfuncs
k0l11 Jan 2, 2024
432bd70
Merge pull request #26 from k0l11/add-sysfuncs
srliao Jan 2, 2024
17d4055
add switch
srliao Jan 2, 2024
3993f68
move char check to simulation
srliao Jan 2, 2024
c6dba63
sort of working set_on_tick
srliao Jan 2, 2024
dac8455
fix char check in wrong place
srliao Jan 2, 2024
fb7a9f9
fix for no cond loop
srliao Jan 3, 2024
7858823
fix sample using wrong eval
srliao Jan 3, 2024
c820209
fix call expr ret val
srliao Jan 3, 2024
0539ece
improve binary expr error msg
srliao Jan 3, 2024
90b4f5c
add experimental is_target_dead sys func
k0l11 Jan 23, 2024
4d1354f
Merge pull request #27 from k0l11/add-target-dead
srliao Jan 23, 2024
dadf50b
Merge remote-tracking branch 'upstream/main' into srl-2024-09-01-new-…
srliao Jan 30, 2024
70119ae
Merge remote-tracking branch 'upstream/main' into srl-2023-09-01-new-…
srliao Jan 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/wasm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (

"github.com/genshinsim/gcsim/pkg/agg"
"github.com/genshinsim/gcsim/pkg/core/info"
"github.com/genshinsim/gcsim/pkg/gcs"
"github.com/genshinsim/gcsim/pkg/gcs/ast"
"github.com/genshinsim/gcsim/pkg/gcs/eval"
"github.com/genshinsim/gcsim/pkg/model"
"github.com/genshinsim/gcsim/pkg/simulation"
"github.com/genshinsim/gcsim/pkg/simulator"
Expand Down Expand Up @@ -133,7 +133,7 @@ func simulate(this js.Value, args []js.Value) (out interface{}) {
if err != nil {
return marshal(err)
}
eval, err := gcs.NewEvaluator(program, core)
eval, err := eval.NewEvaluator(program, core)
if err != nil {
return marshal(err)
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/gcs/ast/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,9 @@ func (s *SwitchStmt) String() string {

func (s *SwitchStmt) writeTo(sb *strings.Builder) {
sb.WriteString("switch ")
s.Condition.writeTo(sb)
if s.Condition != nil {
s.Condition.writeTo(sb)
}
sb.WriteString(" {\n")
for _, v := range s.Cases {
v.writeTo(sb)
Expand Down
6 changes: 5 additions & 1 deletion pkg/gcs/ast/parseCharacterAction.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

// parseAction returns a node contain a character action, or a block of node containing
// a list of character actions
func (p *Parser) parseAction() (Stmt, error) {
func (p *Parser) parseAction() (Node, error) {
char, err := p.consume(itemCharacterKey)
if err != nil {
// this really shouldn't happen since we already checked
Expand Down Expand Up @@ -88,6 +88,10 @@ Loop:
}
// check for optional flags

if len(actions) == 1 {
return actions[0], nil
}

// build stmt
b := newBlockStmt(char.pos)
for _, v := range actions {
Expand Down
48 changes: 48 additions & 0 deletions pkg/gcs/eval/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package eval

type Env struct {
parent *Env
varMap map[string]*Obj
}

func NewEnv(parent *Env) *Env {
return &Env{
parent: parent,
varMap: make(map[string]*Obj),
}
}

// put puts a value in the current env regardless of existence or not
//
//nolint:gocritic // non-pointer type for *Obj doesn't make sense
func (e *Env) put(s string, v *Obj) {
e.varMap[s] = v
}

// assign requires variable already exist and assign the value to first instance of variable going up the tree
//
//nolint:gocritic // non-pointer type for *Obj doesn't make sense
func (e *Env) assign(s string, v *Obj) bool {
_, ok := e.varMap[s]
if !ok {
//base case: no parent and does not exist
if e.parent == nil {
return false
}
return e.parent.assign(s, v)
}
e.varMap[s] = v
return true
}

//nolint:gocritic // non-pointer type for *Obj doesn't make sense
func (e *Env) v(s string) (*Obj, bool) {
v, ok := e.varMap[s]
if ok {
return v, true
}
if e.parent != nil {
return e.parent.v(s)
}
return nil, false
}
71 changes: 71 additions & 0 deletions pkg/gcs/eval/eval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package eval

import (
"errors"

"github.com/genshinsim/gcsim/pkg/core"
"github.com/genshinsim/gcsim/pkg/core/action"
"github.com/genshinsim/gcsim/pkg/gcs/ast"
)

type evalNode interface {
// execute the node; node should either return next action, or continue execution until the node is
// done. done should only be false if Obj is an action; otherwise must be true
nextAction() (Obj, bool, error)
}

type Evaluator struct {
Core *core.Core
base evalNode
env *Env
err error
}

func NewEvaluator(root ast.Node, core *core.Core) (*Evaluator, error) {
e := &Evaluator{
Core: core,
env: NewEnv(nil),
}
e.initSysFuncs()
e.base = evalFromNode(root, e.env)
if e.base == nil {
return nil, errors.New("invalid root node; no executor found")
}
return e, nil
}

func (e *Evaluator) NextAction() (*action.Eval, error) {
// base case: no more action
if e.base == nil {
return nil, e.err
}
res, done, err := e.base.nextAction()
if err != nil {
e.err = err
e.base = nil
return nil, err
}
if done {
e.base = nil
}
if v, ok := res.(*actionval); ok {
return v.toActionEval(), nil
}
return nil, nil
}

func (e *Evaluator) Continue() {}
func (e *Evaluator) Exit() error { return nil }
func (e *Evaluator) Err() error { return e.err }
func (e *Evaluator) Start() {}

func evalFromNode(n ast.Node, env *Env) evalNode {
switch v := n.(type) {
case ast.Expr:
return evalFromExpr(v, env)
case ast.Stmt:
return evalFromStmt(v, env)
default:
return nil
}
}
55 changes: 55 additions & 0 deletions pkg/gcs/eval/eval_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package eval

import "github.com/genshinsim/gcsim/pkg/gcs/ast"

func mapExprEval(n *ast.MapExpr, env *Env) evalNode {
m := &mapExprEvalNode{
root: n,
fields: make(map[string]Obj),
stack: make([]mapFieldWrapper, 0, len(n.Fields)),
}
// order really doesn't matter here
for k, v := range m.root.Fields {
m.stack = append(m.stack, mapFieldWrapper{
key: k,
node: evalFromExpr(v, env),
})
}
return m
}

type mapFieldWrapper struct {
key string
node evalNode
}

type mapExprEvalNode struct {
root *ast.MapExpr
stack []mapFieldWrapper
fields map[string]Obj
}

func (m *mapExprEvalNode) nextAction() (Obj, bool, error) {
if len(m.root.Fields) == 0 {
return &mapval{}, true, nil
}

for len(m.stack) > 0 {
idx := len(m.stack) - 1
res, done, err := m.stack[idx].node.nextAction()
if err != nil {
return nil, false, err
}
if done {
m.fields[m.stack[idx].key] = res
m.stack = m.stack[:idx]
}
if res.Typ() == typAction {
return res, false, nil // done is false b/c the node is not done yet
}
}

return &mapval{
fields: m.fields,
}, true, nil
}
37 changes: 37 additions & 0 deletions pkg/gcs/eval/eval_map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package eval

import (
"testing"

"github.com/genshinsim/gcsim/pkg/gcs/ast"
)

func TestEvalBasicMapExpr(t *testing.T) {
n := &ast.MapExpr{
Fields: map[string]ast.Expr{
"test": &ast.NumberLit{IntVal: 5},
},
}
val, err := runEvalReturnResWhenDone(evalFromNode(n, nil))
if err != nil {
t.Error(err)
t.FailNow()
}
v, ok := val.(*mapval)
if !ok {
t.Errorf("res is not a number, got %v", val.Typ())
}
res, ok := v.fields["test"]
if !ok {
t.Errorf("key test does not exist")
t.FailNow()
}
resv, ok := res.(*number)
if !ok {
t.Errorf("res from map is not a number, got %v", res.Typ())
t.FailNow()
}
if resv.ival != 5 {
t.Errorf("expecting result to be %v, got %v", 5, resv.ival)
}
}
16 changes: 16 additions & 0 deletions pkg/gcs/eval/eval_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package eval

import (
"errors"
)

func runEvalReturnResWhenDone(e evalNode) (Obj, error) {
if e == nil {
return nil, errors.New("invalid root node; no executor found")
}
val, _, err := e.nextAction()
if err != nil {
return nil, err
}
return val, nil
}
62 changes: 62 additions & 0 deletions pkg/gcs/eval/expr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package eval

import (
"strings"

"github.com/genshinsim/gcsim/pkg/gcs/ast"
)

func evalFromExpr(n ast.Expr, env *Env) evalNode {
switch v := n.(type) {
case *ast.NumberLit:
return numberLitEval(v)
case *ast.StringLit:
return stringLitEval(v)
case *ast.MapExpr:
return mapExprEval(v, env)
case *ast.BinaryExpr:
return binaryExprEval(v, env)
case *ast.UnaryExpr:
return unaryExprEval(v, env)
case *ast.FuncExpr:
// FuncExpr is only used for anon funcs, followed after a let stmt
return funcExprEval(v, env)
case *ast.Ident:
return identLitEval(v, env)
case *ast.CallExpr:
return callExprEval(v, env)
case *ast.Field:
return fieldsExprEval(v, env)
default:
return nil
}
}

func numberLitEval(n *ast.NumberLit) evalNode {
return &number{
isFloat: n.IsFloat,
ival: n.IntVal,
fval: n.FloatVal,
}
}

func stringLitEval(n *ast.StringLit) evalNode {
return &strval{
str: strings.Trim(n.Value, "\""),
}
}

// funcLitEval is never called directly, but is used by either FuncExpr or FuncStmt, both of which
// are essentially wrappers around FuncLit
func funcLitEval(n *ast.FuncLit, env *Env) evalNode {
return &funcval{
Args: n.Args,
Body: n.Body,
Signature: n.Signature,
Env: NewEnv(env),
}
}

func funcExprEval(n *ast.FuncExpr, env *Env) evalNode {
return funcLitEval(n.Func, env)
}
Loading
Loading