Skip to content

Commit

Permalink
int("")==0; adding trim(), trim_left() and trim_right(); new …
Browse files Browse the repository at this point in the history
…`-no-register` option to disable register optimizations (#272)

* Allow leanier string to int conversion

* Don't break the hex/octal/binary

* Adding trim, trim_left, trim_right

* linter fix

* Added -no-register flag/option - makes advent 11 faster yet ... still too slow

* Adding advent day 11 code
  • Loading branch information
ldemailly authored Dec 11, 2024
1 parent 225f9ba commit 7a7ec8e
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 8 deletions.
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ all: generate lint check test run

GO_BUILD_TAGS:=no_net,no_pprof

#GROL_FLAGS:=-no-register

run: grol
# Interactive debug run: use logger with file and line numbers
LOGGER_IGNORE_CLI_MODE=true GOMEMLIMIT=1GiB ./grol -panic -parse -loglevel debug
LOGGER_IGNORE_CLI_MODE=true GOMEMLIMIT=1GiB ./grol -panic -parse -loglevel debug $(GROL_FLAGS)

GEN:=object/type_string.go ast/priority_string.go token/type_string.go

Expand Down Expand Up @@ -82,10 +84,10 @@ unit-tests:
CGO_ENABLED=0 go test -tags $(GO_BUILD_TAGS) ./...

examples: grol
GOMEMLIMIT=1GiB ./grol -panic examples/*.gr
GOMEMLIMIT=1GiB ./grol -panic $(GROL_FLAGS) examples/*.gr

grol-tests: grol
GOMEMLIMIT=1GiB ./grol -panic -shared-state tests/*.gr
GOMEMLIMIT=1GiB ./grol -panic -shared-state $(GROL_FLAGS) tests/*.gr

check: grol
./check_samples_double_format.sh examples/*.gr
Expand Down
14 changes: 10 additions & 4 deletions eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ func (s *State) applyFunction(name string, fn object.Object, args []object.Objec
before := s.env.GetMisses()
res := s.Eval(newBody) // Need to have the return value unwrapped. Fixes bug #46, also need to count recursion.
after := s.env.GetMisses()
cantCache := s.env.CantCache()
// restore the previous env/state.
s.env = curState
s.Out = oldOut
Expand All @@ -662,8 +663,10 @@ func (s *State) applyFunction(name string, fn object.Object, args []object.Objec
}
if after != before {
log.Debugf("Cache miss for %s %v, %d get misses", function.CacheKey, args, after-before)
// A miss here is a miss upstack
s.env.TriggerNoCache()
// Propagate the can't cache
if cantCache {
s.env.TriggerNoCache()
}
return res
}
// Don't cache errors, as it could be due to binding for instance.
Expand Down Expand Up @@ -714,7 +717,7 @@ func (s *State) extendFunctionEnv(
// By definition function parameters are local copies, deref argument values:
pval := object.Value(args[paramIdx])
needVariable := true
if pval.Type() == object.INTEGER {
if !s.NoReg && pval.Type() == object.INTEGER {
// We will release all these registers just by returning/dropping the env.
_, nbody, ok := setupRegister(env, param.Value().Literal(), pval.(object.Integer).Value, newBody)
if ok {
Expand Down Expand Up @@ -846,7 +849,7 @@ func (s *State) evalForInteger(fe *ast.ForExpression, start *int64, end int64, n
var newBody ast.Node
var register object.Register
newBody = fe.Body
if name != "" {
if name != "" && !s.NoReg {
var ok bool
register, newBody, ok = setupRegister(s.env, name, int64(startValue), fe.Body)
if !ok {
Expand All @@ -855,6 +858,9 @@ func (s *State) evalForInteger(fe *ast.ForExpression, start *int64, end int64, n
ptr = register.Ptr()
}
for i := startValue; i < endValue; i++ {
if s.NoReg && name != "" {
s.env.Set(name, object.Integer{Value: int64(i)})
}
if ptr != nil {
*ptr = int64(i)
}
Expand Down
1 change: 1 addition & 0 deletions eval/eval_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type State struct {
Context context.Context //nolint:containedctx // we need a context for callbacks from extensions and to set it without API change.
Cancel context.CancelFunc
PipeVal []byte // value to return from pipe() function
NoReg bool // don't use registers.
}

func NewState() *State {
Expand Down
28 changes: 28 additions & 0 deletions examples/advent_2024_day11.gr
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// https://adventofcode.com/2024/day/11

func applyRules(v, depth) {
if depth == 0 {
1
} else if v == 0 {
applyRules(1, depth-1)
} else {
s := str(v)
l := len(s)
depth = depth - 1
if l%2 == 1 {
applyRules(2024*v, depth)
} else {
left := int(s[0:l/2])
right := int(trim_left(s[l/2:l], "0"))
applyRules(left, depth) + applyRules(right, depth)
}
}
}

n :=25
println(applyRules(125,n)+applyRules(17,n))

// Despite auto memoization, this is still too slow even with the new -no-register

// n = 75
// println(applyRules(125,n)+applyRules(17,n))
49 changes: 48 additions & 1 deletion extensions/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ func createJSONAndEvalFunctions(c *Config) {
}
}

const DefaultTrimSet = " \r\n\t"

func createStrFunctions() { //nolint:funlen // we do have quite a few, yes.
strFn := object.Extension{
MinArgs: 1,
Expand Down Expand Up @@ -457,6 +459,46 @@ func createStrFunctions() { //nolint:funlen // we do have quite a few, yes.
return object.String{Value: newStr}
}
MustCreate(strFn)
strFn.Name = "trim"
strFn.Help = "trims leading and trailing spaces or characters"
strFn.ArgTypes = []object.Type{object.STRING, object.STRING}
strFn.MinArgs = 1
strFn.MaxArgs = 2
strFn.Callback = func(_ any, _ string, args []object.Object) object.Object {
inp := args[0].(object.String).Value
trim := DefaultTrimSet
if len(args) == 2 {
trim = args[1].(object.String).Value
}
return object.String{Value: strings.Trim(inp, trim)}
}
MustCreate(strFn)
strFn.Name = "trim_left"
strFn.Help = "trims leading spaces or characters"
strFn.ArgTypes = []object.Type{object.STRING, object.STRING}
strFn.MaxArgs = 2
strFn.Callback = func(_ any, _ string, args []object.Object) object.Object {
inp := args[0].(object.String).Value
trim := DefaultTrimSet
if len(args) == 2 {
trim = args[1].(object.String).Value
}
return object.String{Value: strings.TrimLeft(inp, trim)}
}
MustCreate(strFn)
strFn.Name = "trim_right"
strFn.Help = "trims trailing spaces or characters"
strFn.ArgTypes = []object.Type{object.STRING, object.STRING}
strFn.MaxArgs = 2
strFn.Callback = func(_ any, _ string, args []object.Object) object.Object {
inp := args[0].(object.String).Value
trim := DefaultTrimSet
if len(args) == 2 {
trim = args[1].(object.String).Value
}
return object.String{Value: strings.TrimRight(inp, trim)}
}
MustCreate(strFn)
}

func createMisc() {
Expand Down Expand Up @@ -519,7 +561,12 @@ func createMisc() {
}
return object.Integer{Value: r}
case object.STRING:
i, serr := strconv.ParseInt(o.(object.String).Value, 0, 64)
str := o.(object.String).Value
if str == "" {
return object.Integer{Value: 0}
}
// Supports hex, octal, decimal, binary.
i, serr := strconv.ParseInt(str, 0, 64)
if serr != nil {
return s.Error(serr)
}
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func Main() (retcode int) { //nolint:funlen // we do have quite a lot of flags a
// Use 0 (unlimited) as default now that you can ^C to stop a script.
maxDuration := flag.Duration("max-duration", 0, "Maximum duration for a script to run. 0 for unlimited.")
shebangMode := flag.Bool("s", false, "#! script mode: next argument is a script file to run, rest are args to the script")
noRegister := flag.Bool("no-register", false, "Don't use registers")

cli.ArgsHelp = "*.gr files to interpret or `-` for stdin without prompt or no arguments for stdin repl..."
cli.MaxArgs = -1
Expand Down Expand Up @@ -106,6 +107,7 @@ func Main() (retcode int) { //nolint:funlen // we do have quite a lot of flags a
AllParens: *allParens,
MaxDuration: *maxDuration,
ShebangMode: *shebangMode,
NoReg: *noRegister,
}
if hookBefore != nil {
retcode = hookBefore()
Expand Down Expand Up @@ -144,6 +146,7 @@ func Main() (retcode int) { //nolint:funlen // we do have quite a lot of flags a
}
options.All = true
s := eval.NewState()
s.NoReg = *noRegister
if options.ShebangMode {
script := flag.Arg(0)
// remaining := flag.Args()[1:] // actually let's also pass the name of the script as arg[0]
Expand Down
7 changes: 7 additions & 0 deletions object/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Environment struct {
ids *trie.Trie
numSet int64
getMiss int64
cantCache bool
function *Function
registers [NumRegisters]int64
numReg int
Expand Down Expand Up @@ -261,6 +262,7 @@ func (e *Environment) Get(name string) (Object, bool) {
// Meant to be used by extensions that for instance return random numbers or change state.
func (e *Environment) TriggerNoCache() {
log.Debugf("TriggerNoCache() GETMISS called at %d %v", e.depth, e.cacheKey)
e.cantCache = true
e.getMiss++
}

Expand All @@ -269,6 +271,11 @@ func (e *Environment) GetMisses() int64 {
return e.getMiss
}

// Can't cache is if we called a non cacheable extension (like rand()).
func (e *Environment) CantCache() bool {
return e.cantCache
}

// Defines constant as all CAPS (with _ ok in the middle) identifiers.
// Note that we use []byte as all identifiers are ASCII.
func Constant(name string) bool {
Expand Down
3 changes: 3 additions & 0 deletions repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Options struct {
AllParens bool // Show all parens in parse tree (default is to simplify using precedence).
MaxDuration time.Duration
ShebangMode bool // Whether to run in #! script mode (not making a difference here, used in main.go).
NoReg bool // Disable registers.
}

func AutoLoad(s *eval.State, options Options) error {
Expand Down Expand Up @@ -179,6 +180,7 @@ func EvalString(what string) (string, []string, string) {
// AutoLoad, AutoSave, Compact.
func EvalStringWithOption(ctx context.Context, o Options, what string) (res string, errs []string, formatted string) {
s := eval.NewState()
s.NoReg = o.NoReg
if o.MaxDepth > 0 {
s.MaxDepth = o.MaxDepth
}
Expand Down Expand Up @@ -217,6 +219,7 @@ func extractHistoryNumber(input string) (int, bool) {
func Interactive(options Options) int { //nolint:funlen // we do have quite a few cases.
options.NilAndErr = true
s := eval.NewState()
s.NoReg = options.NoReg
s.MaxDepth = options.MaxDepth
s.MaxValueLen = options.MaxValueLen // 0 is unlimited so ok to copy as is.
term, err := terminal.Open(context.Background())
Expand Down
5 changes: 5 additions & 0 deletions tests/conversions.gr
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ Assert("int() is int", int(3.7) == 3)
Assert("int(true) is 1", int(true) == 1)
Assert("int(false) is 0", int(false) == 0)
Assert("int(nil) is 0", int(nil) == 0)
Assert("int(\"\") is 0", int("") == 0)
Assert("int() with trim() whitespaces work", int(trim("\n 123\n\t")) == 123)
Assert("int(\"0\") is 0", int("0") == 0)
Assert("int(\"0xa\") is 10", int("0xa") == 10) // hex still working despite leading 0 handling
Assert("int(\"0755\") octal is 493", int("0755") == 493) // octal still working despite leading 0 handling

0 comments on commit 7a7ec8e

Please sign in to comment.