google / starlark-go

Starlark in Go: the Starlark configuration language, implemented in Go
BSD 3-Clause "New" or "Revised" License
2.31k stars 209 forks source link

SourceProgramOptions for parsing #561

Closed state-developer closed 1 week ago

state-developer commented 1 week ago

Currently, I am using SourceProgramOptions to validate a script that is passed in via an API. There are several "keywords" I want to use and make sure they exist to allow for parsing to pass and say the source is valid. I just want to validate the script is valid without executing it.

options := &syntax.FileOptions{
   GlobalReassign: true,
}

// Is this the correct way of handling this?
if !starlark.Universe.Has("USER_NAME") {
   starlark.Universe["USER_NAME"] = nil // Normally would add Builtin here
}

// ... additional external keywords ...

// IsPredeclared a better place?
_, _, err := starlark.SourceProgramOptions(options, "my code", some_script_string, func(name string) bool {
   return false
})
if err != nil {
   syntaxError, ok := err.(syntax.Error)
   if ok {
      return fmt.Errorf("script error (%s): %s", syntaxError.Pos, syntaxError.Msg)
   }

   resolveError, ok := err.(resolve.ErrorList)
   if ok {
      return fmt.Errorf("script error (%s): %s", resolveError[0].Pos, resolveError[0].Msg)
   }

  return err
}

Is there a better approach to accomplishing this?

Thanks and looking forward to your help.

adonovan commented 1 week ago

SourceProgramOptions is a reasonable way to validate a file, but you can make it more efficient by skipping the compilation step. All you need is to parse and resolve:

    isPredeclared := StringDict{"foo": nil}.Has // defines the additional name "foo"

    f, err := opts.Parse(filename, src, 0)
    if err != nil { ... } // syntax error

    err := resolve.File(f, isPredeclared, Universe.Has)
    if err != nil { ... } // semantic error