Giter Club home page Giter Club logo

risor's Introduction

Risor logo Risor

CircleCI Apache-2.0 license Go.Dev reference Go Report Card Releases

Risor is a fast and flexible scripting language for Go developers and DevOps.

Its modules integrate the Go standard library, making it easy to use functions that you're already familiar with as a Go developer.

Scripts are compiled to bytecode and then run on a lightweight virtual machine. Risor is written in pure Go.

Documentation

Documentation is available at risor.io.

You might also want to try evaluating Risor scripts from your browser.

Syntax Example

Here's a short example of how Risor feels like a hybrid of Go and Python. This demonstrates using Risor's pipe expressions to apply a series of transformations:

array := ["gophers", "are", "burrowing", "rodents"]

sentence := array | strings.join(" ") | strings.to_upper

print(sentence)

Output:

GOPHERS ARE BURROWING RODENTS

Getting Started

You might want to head over to Getting Started in the documentation. That said, here are tips for both the CLI and the Go library.

Risor CLI and REPL

If you use Homebrew, you can install the Risor CLI as follows:

brew install risor

Having done that, just run risor to start the CLI or risor -h to see usage information.

Execute a code snippet directly using the -c option:

risor -c "time.now()"

Start the REPL by running risor with no options.

Build and Install the CLI from Source

Build the CLI from source as follows:

git clone [email protected]:risor-io/risor.git
cd risor/cmd/risor
go install -tags aws,k8s,vault .

Go Library

Use go get to add Risor as a dependency of your Go program:

go get github.com/risor-io/[email protected]

Here's an example of using the risor.Eval API to evaluate some code:

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/risor-io/risor"
)

func main() {
	ctx := context.Background()
	script := "math.sqrt(input)"
	result, err := risor.Eval(ctx, script, risor.WithGlobal("input", 4))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("The square root of 4 is:", result)
}

Built-in Functions and Modules

30+ built-in functions are included and are documented here.

Modules are included that generally wrap the equivalent Go package. For example, there is direct correspondence between base64, bytes, filepath, json, math, os, rand, regexp, strconv, strings, and time Risor modules and the Go standard library.

Risor modules that are beyond the Go standard library currently include aws, pgx, uuid, vault, and k8s.

Go Interface

It is trivial to embed Risor in your Go program in order to evaluate scripts that have access to arbitrary Go structs and other types.

The simplest way to use Risor is to call the Eval function and provide the script source code. The result is returned as a Risor object:

result, err := risor.Eval(ctx, "math.min([5, 2, 7])")
// result is 2, as an *object.Int

Provide input to the script using Risor options:

result, err := risor.Eval(ctx, "input | strings.to_upper", risor.WithGlobal("input", "hello"))
// result is "HELLO", as an *object.String

Use the same mechanism to inject a struct. You can then access fields or call methods on the struct from the Risor script:

type Example struct {
    Message string
}
example := &Example{"abc"}
result, err := risor.Eval(ctx, "len(ex.Message)", risor.WithGlobal("ex", example))
// result is 3, as an *object.Int

Dependencies and Build Options

Risor is designed to have minimal external dependencies in its core libraries. You can choose to opt into various add-on modules if they are of value in your application. The modules are present in this same Git repository, but must be installed with go get as separate dependencies:

Name Path Go Get Command
aws modules/aws go get github.com/risor-io/risor/modules/[email protected]
bcrypt modules/bcrypt go get github.com/risor-io/risor/modules/[email protected]
carbon modules/carbon go get github.com/risor-io/risor/modules/[email protected]
cli modules/cli go get github.com/risor-io/risor/modules/[email protected]
image modules/image go get github.com/risor-io/risor/modules/[email protected]
jmespath modules/jmespath go get github.com/risor-io/risor/modules/[email protected]
k8s modules/kubernetes go get github.com/risor-io/risor/modules/[email protected]
pgx modules/pgx go get github.com/risor-io/risor/modules/[email protected]
s3fs os/s3fs go get github.com/risor-io/risor/os/[email protected]
sql modules/sql go get github.com/risor-io/risor/modules/[email protected]
template modules/template go get github.com/risor-io/risor/modules/[email protected]
uuid modules/uuid go get github.com/risor-io/risor/modules/[email protected]
vault modules/vault go get github.com/risor-io/risor/modules/[email protected]

These add-ons are included by default when using the Risor CLI. However, when building Risor into your own program, you'll need to opt-in using go get as described above and then add the modules as globals in Risor scripts as follows:

import (
    "github.com/risor-io/risor"
    "github.com/risor-io/risor/modules/aws"
    "github.com/risor-io/risor/modules/image"
    "github.com/risor-io/risor/modules/pgx"
    "github.com/risor-io/risor/modules/uuid"
)

func main() {
    source := `"nice modules!"`
    result, err := risor.Eval(ctx, source,
        risor.WithGlobals(map[string]any{
            "aws":   aws.Module(),
            "image": image.Module(),
            "pgx":   pgx.Module(),
            "uuid":  uuid.Module(),
        }))
    // ...
}

Syntax Highlighting

A Risor VSCode extension is already available which currently only offers syntax highlighting.

You can also make use of the Risor TextMate grammar.

Contributing

Risor is intended to be a community project. You can lend a hand in various ways:

  • Please ask questions and share ideas in GitHub discussions
  • Share Risor on any social channels that may appreciate it
  • Open GitHub issue or a pull request for any bugs you find
  • Star the project on GitHub

Contributing New Modules

Adding modules to Risor is a great way to get involved with the project. See this guide for details.

Discuss the Project

Please visit the GitHub discussions page to share thoughts and questions.

There is also a #risor Slack channel on the Gophers Slack.

Credits

Check CREDITS.md.

License

Released under the Apache License, Version 2.0.

Copyright Curtis Myzie / github.com/myzie.

risor's People

Contributors

abadfox233 avatar alexaandru avatar applejag avatar chenrui333 avatar dependabot[bot] avatar edhemphill avatar jakegut avatar knockoutez avatar luisdavim avatar myzie avatar raff avatar rprtr258 avatar runsisi avatar skurfuerst avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

risor's Issues

How to embed Risor but remove certain built-ins

I was wondering if you could provide some brief guidance on this issue:

Let's say we are using risor for a glue language inside of another application. There are certain built-ins we definitely do not want called - a good example being os.exit. Ideally we would pick the specific built-ins we could provide.

Recently it looks like the changes for the risor.cfg.RisorConfig struct have become an internal-only module. What then would be the best way to custom tailor the language for such a use case without forking our own version?

Thanks!

Please publish the VS Code extension to the Open VSX Registry (an alternative to the MS VSC marketplace)

Hi Risor Team,

Please publish the risor VS Code extension to the Open VSX marketplace. As a VSCodium user with no access to the MS marketplace, I'd love to see the extension available there. Thank you!

Context

Unfortunately, as Microsoft prohibits usages of the Microsoft marketplace by any other products or redistribution of .vsix files from it, in order to use VS Code extensions in non-Microsoft products, we kindly ask that you take ownership of the VS Code extension namespace in Open VSX and publish this extension on Open VSX.

What is Open VSX? Why does it exist?

Open VSX is a vendor neutral alternative to the MS marketplace used by most other derivatives of VS Code like VSCodium, Gitpod, OpenVSCode, Theia-based IDEs, and so on.

You can read on about Open VSX at the Eclipse Foundation's Open VSX FAQ.

How can you publish to Open VSX?

The docs to publish an extension can be found here. This process is straightforward and shouldn't take too long. Essentially, you need an authentication token and to execute the ovsx publish command to publish your extension. There's also a doc explaining the whole process with an example GitHub Action workflow.

Cannot install v0.17.0 from source

$ go install -x github.com/risor-io/risor/cmd/[email protected]
# get https://proxy.golang.org/github.com/@v/v0.17.0.info
# get https://proxy.golang.org/github.com/risor-io/@v/v0.17.0.info
# get https://proxy.golang.org/github.com/risor-io/risor/cmd/risor/@v/v0.17.0.info
# get https://proxy.golang.org/github.com/risor-io/risor/cmd/@v/v0.17.0.info
# get https://proxy.golang.org/github.com/risor-io/risor/cmd/risor/@v/v0.17.0.info: 404 Not Found (0.120s)
# get https://proxy.golang.org/github.com/risor-io/risor/cmd/@v/v0.17.0.info: 404 Not Found (0.120s)
# get https://proxy.golang.org/github.com/@v/v0.17.0.info: 404 Not Found (0.206s)
# get https://proxy.golang.org/github.com/risor-io/@v/v0.17.0.info: 404 Not Found (0.206s)
# go: github.com/risor-io/risor/cmd/[email protected]: module github.com/risor-io/[email protected] found, but does not contain package github.com/risor-io/risor/cmd/risor
Go Version Details
go version go1.20.4 darwin/arm64

GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/adam/Library/Caches/go-build"
GOENV="/Users/adam/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/adam/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/adam/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/nix/store/rhmf86rrq5sksqhg1544dq6hvxrr5cvg-go-1.20.4/share/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/nix/store/rhmf86rrq5sksqhg1544dq6hvxrr5cvg-go-1.20.4/share/go/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.20.4"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/adam/tmp/risor-test/go.mod"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/rg/284c6b7s3bl6mxdzlczyqf8m0000gp/T/go-build1938590258=/tmp/go-build -gno-record-gcc-switches -fno-common"

This is likely due to a combination of the following factors:

Potential fix: follow the steps here to remove the package from the index, then re-cache it?

Obligatory edit: ๐Ÿ’ฏ ๐Ÿ˜Ž

Kubernetes package

I think it would be interesting to have a k8s package to fetch objects from the API.

f-strings in closures don't have access to outer scope

For example:

func foo(a, b, c) {
	return func(d) {
		return '{a} {b}'
	}
}
foo("hello")("go")

Results in a panic: runtime error: invalid memory address or nil pointer dereference

However, when I bring the closure's variables into scope, I get the correct string:

func foo(a) {
	return func(b) {
		a := a
		return '{a} {b}'
	}
}
foo("hello")("go")

No Benchmark

I keep seeing fast every now and then, including on the website, but there's no benchmark to prove this. Could you please add a benchmark to the site or readme?

Add `defer` keyword

Another parity keyword added from Go.

I suggest the exactly same semantics, meaning:

  • Must followed by a function call
  • The function and arguments are captured at the point of the defer statement
  • The deferred function is called at the end of the function (and not at the end of the block)

Edge cases

Outside any function

When called in the main file outside any function, the deferred call should be executed at the end of the entire program.

// example-1.risor

defer print("world")

print("Hello")
$ risor example-1.risor
Hello
world

Use in REPL

When used in the Risor REPL, it should be called when exiting. If it isn't already, the REPL needs to start listening for Ctrl+C and react to it, instead of just exiting.

$ risor
Risor

>>> defer print("hello world")
>>> ^C
hello world

Inside imports

To reduce hidden behavior, when called outside any function inside an imported file, it should be called at the end of import.

// otherfile.risor

defer print("deferred print inside otherfile.risor")

print("print inside otherfile.risor")
// example-2.risor

import otherfile

print("print inside example-2.risor")
$ risor example-2.risor
print inside otherfile.risor
deferred print inside otherfile.risor
print inside example-2.risor

Motivation

Useful when working with io.Closer types, such as os.create():

f := os.create("foo.txt")
defer f.close()

f.write("hello world")

Alternatives

Just to get some perspective. I'm personally in favor with the proposal I made above.

Python with statement

Python's with statement is quite enticing, but I've a personal reluctant feeling for it just because of the extra code block, and the fact that the variable name is at the end.

On the other hand, a benefit is that you get full control over when the scope ends.

with open("foo.txt", "w") as f:
  f.write("hello world")

With Risor, it would probably look something like this:

with f := os.create("foo") {
  f.write("hello world")
}

Downside is that you can then only use this on io.Closer types.

C# using statement

C# has a using var ... declaration that is a simplified using () { } statement. It calls Dispose() on the object at the end of the block.

The nice thing about this shorter version is that it doesn't add extra indentation, similar to Go's defer keyword.

void Foo() {
  using var file = File.Create("foo.txt");

  var text = new UTF8Encoding(true).GetBytes("hello world");
  file.Write(text, 0, text.Length);
}

In Risor, it could look something like this:

using f := os.create("foo.txt")

f.write("hello world")

multiple variables assignment error

example 1

func main() {
	ctx := context.Background()
	script := `
a, b := [2, 3]
a, b = [3, 4]
[a, b]`
	result, err := risor.Eval(ctx, script)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("result is:", result)
}
parse error: unexpected = while parsing declaration statement (expected :=)

example 2

func main() {
	ctx := context.Background()
	script := `
a, b := [2, 3]
a, b := [3, 4]
[a, b]`
	result, err := risor.Eval(ctx, script)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("result is:", result)
}
compile error: variable "b" already exists

i think the assignment op in the first example should be allowed?

Print from Stdin

I am looking to know if the following is capable?

Printing from STDIN Python

 echo test | python -c "import sys; print(sys.stdin.read().strip())"
test

Printin from STDIN in risor

 echo test |  risor -c `...` # what goes here

Printin from STDIN in risor

 echo test |  risor -c `...` # what goes here

Other MISC examples

โฏ echo test | risor -c 'print("hello")
hello

New module: GitHub API

Using Risor could be a nice contestant to using Bash in GH Actions.

Not talking about releasing to GitHub, but instead to use the go-github package: https://pkg.go.dev/github.com/google/go-github/v57/github

It's such a huge package that not everything needs to be there. But common tasks used in GH actions, like:

All of this is already doable, but having them wrapped in easy-to-use first-class module would be nice.

Alternatively, this could also be a third-party module you'll have to import. What's the decision like for what goes into the "standard library" and what not?

Edit: I first now saw that you're making use of GitHub discussions, so maybe I should've written that there. Will use it for future ideas and questions

HTTP module

Hi, I know there's a fetch module already that provides some support for http requests but I'm looking into an embedded DSL for http api testing.
Something like https://docs.racket-lang.org/riposte/index.html?=read-line&=read-line

I've looked into the following:

I think something like this would be a nice addition to risor, if you agree, I could look into creating a new module for risor that would allow this use case.

Risor project logo

@luisdavim @applejag @abadfox233 @KnockOutEZ - I'm working on Risor logo options. I'd appreciate any feedback on any of these options. A couple look good to me but I'm curious what other people think.

I can work with the designer(s) to iterate on it once I have a leader picked out.

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13

let <assignment> requires a semicolon or newline to terminate, while <assignment> doesn't

I am playing with a REPL application where I read lines and run them through exec.Execute.

I found out that the following let statement throws an error when executing the line:

> let a = 10
Error: parse error: unterminated let statement

While the same assignment without let succeeds:

> a = 20
(*object.Integer) 20

I would have expected both statements to work the same (and actually everything else I tried does work without ; or newline).

FR: os.getenv alias

It's not a big deal but I think would fit into product's paradigm just fine.

some exec/arguments issues

  • first, exec.command has signature with variadic arguments:
command(name string, args ...string) command

but user functions cannot have variadic arguments, nor there is spread operator. So, I cannot write function like:

func myexec(cmd, args) {
  c := exec.command(cmd, args) // type error: expected a string (list given)
  exit_code := try(c.run, 1)
  if exit_code != 0 {
    print("COMMAND FAILED:", cmd, args)
    print("STDOUT:", c.stdout)
    print("STDERR:", c.stderr)
  }
}
  • second, it would be nice to print stacktrace when error has occured, because raw error message says nearly nothing
  • third, it seems shebang shenninghans for cli package doesn't work for me.
    • first issue is that shebang passes arguments like so:
    $SHEBANG $SCRIPT $ARGS
    so, when shebang is #!/usr/bin/env risor -- it gives
    /usr/bin/env: โ€˜risor --โ€™: No such file or directory
    /usr/bin/env: use -[v]S to pass options in shebang lines
    
    If we fix shebang to #!/usr/bin/env -S risor --, following program is being executed:
    /usr/bin/env -S risor -- script.risor $ARGS
    which in turn calls
    risor -- script.risor $ARGS
    BUT, as I understood, risor for some reason requires script to be in form risor script.risor -- $ARGS so actual behavior now is open risor repl.
    Though, for now this can be sidestepped with following shebang:
    #!/usr/bin/awk BEGIN{for (i=2; i<ARGC; i++) { subslice = subslice ARGV[i] " " }; system("risor "ARGV[1]" -- "subslice)}
    • second issue is that when we finally were able to call risor correctly, os.args gives array of strings from $ARGS we passed along. BUT it should also include the script, such that os.args() == ["./script.rasor", $ARGS...]. Because of that, I cannot pass os.args() to cli.app.run() because the latter requires executable to be the first argument, which again can be sidestepped for now with .run(["vahui"] + os.args())
  • one more issue is with running exec: there is no way to catch exit code. The only way to not fail script and handle failed command for now goes like
c := exec.command(cmd, args)
exit_code := try(c.run, 1) // 1 is returned instead of actual non-zero code
if exit_code != 0 {
  print("COMMAND FAILED:", cmd, args)
  print("STDOUT:", c.stdout)
  print("STDERR:", c.stderr)
}

such wrapper can be useful for e.g. printing failed command, printing it's stderr to get reason of failure, instead of just

exit status 1

which is not informative at all.

Possible hang in the parser

The following incorrect risor code seems to hang the parser.

//print("test: " + SLACK_URL_POST_MESSAGE)
print("test: ")

func post_to_slack() {
//    url := SLACK_URL_POST_MESSAGE
    url := ""
    return fetch(url, {
        headers: {
            'Content-Type': 'application/json',
        },
        method: 'POST',
        data: {
        	blocks: [
            {
                type: "section",
                text: {
                    type: "mrkdwn",
                    text: "Hello, this is a test.\n\n *Hi you have a new nothing:*",
                },
            },
       		{
       			type: "divider"
	        },
            
        }
        // data: {
        //     text: 'Hello, world!',
        // },
    })
}

post_to_slack()

Note the lack of closing ] for blocks

Pardon the commented out stuff - left it in for context.

I found this while working on code using risor embedded, but it does hang on the CLI also.

MacOS 14.3 / go version go1.22.0 darwin/arm64

Here is a stack trace dumped via a kill SIGQUIT on risor compiling it:

 ~/go/bin/risor ~/ubuntu-work/publicweb-test/hula-config/test.risor


SIGQUIT: quit
PC=0x100f64764 m=0 sigcode=0

goroutine 1 gp=0x140000021c0 m=0 mp=0x109256420 [running]:
github.com/risor-io/risor/parser.(*Parser).nextToken(0x14000556fc8?)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:175 +0x24 fp=0x14000905220 sp=0x14000904f20 pc=0x100f64764
github.com/risor-io/risor/parser.(*Parser).parseMapOrSet(0x14000556fc8)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:1436 +0x8fc fp=0x14000905700 sp=0x14000905220 pc=0x100f7030c
github.com/risor-io/risor/parser.(*Parser).parseMapOrSet-fm()
	<autogenerated>:1 +0x28 fp=0x14000905720 sp=0x14000905700 pc=0x100f71f98
github.com/risor-io/risor/parser.(*Parser).parseNode(0x14000556fc8, 0x1)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:483 +0xc8 fp=0x14000905880 sp=0x14000905720 pc=0x100f677e8
github.com/risor-io/risor/parser.(*Parser).parseExpression(0x14000556fc8, 0x1054cb583?)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:503 +0x28 fp=0x140009059f0 sp=0x14000905880 pc=0x100f67a88
github.com/risor-io/risor/parser.(*Parser).parseKeyValue(0x14000556fc8)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:1488 +0x6c fp=0x14000905a40 sp=0x140009059f0 pc=0x100f7050c
github.com/risor-io/risor/parser.(*Parser).parseMapOrSet(0x14000556fc8)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:1426 +0x820 fp=0x14000905f20 sp=0x14000905a40 pc=0x100f70230
github.com/risor-io/risor/parser.(*Parser).parseMapOrSet-fm()
	<autogenerated>:1 +0x28 fp=0x14000905f40 sp=0x14000905f20 pc=0x100f71f98
github.com/risor-io/risor/parser.(*Parser).parseNode(0x14000556fc8, 0x1)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:483 +0xc8 fp=0x140009060a0 sp=0x14000905f40 pc=0x100f677e8
github.com/risor-io/risor/parser.(*Parser).parseNodeList(0x14000556fc8, {0x105dd7a60, 0x1})
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:1240 +0x298 fp=0x14000906240 sp=0x140009060a0 pc=0x100f6e328
github.com/risor-io/risor/parser.(*Parser).parseCall(0x14000556fc8?, {0x106cf25a8?, 0x140007af7c0})
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:1319 +0x80 fp=0x140009064f0 sp=0x14000906240 pc=0x100f6ef70
github.com/risor-io/risor/parser.(*Parser).parseCall-fm({0x106cf25a8?, 0x140007af7c0?})
	<autogenerated>:1 +0x3c fp=0x14000906520 sp=0x140009064f0 pc=0x100f7253c
github.com/risor-io/risor/parser.(*Parser).parseNode(0x14000556fc8, 0x1)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:493 +0x234 fp=0x14000906680 sp=0x14000906520 pc=0x100f67954
github.com/risor-io/risor/parser.(*Parser).parseExpression(0x14000556fc8, 0x100834f84?)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:503 +0x28 fp=0x140009067f0 sp=0x14000906680 pc=0x100f67a88
github.com/risor-io/risor/parser.(*Parser).parseReturn(0x14000556fc8)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:415 +0x178 fp=0x14000906d20 sp=0x140009067f0 pc=0x100f66d98
github.com/risor-io/risor/parser.(*Parser).parseStatement(0x14000556fc8?)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:287 +0x1d4 fp=0x14000906d50 sp=0x14000906d20 pc=0x100f658d4
github.com/risor-io/risor/parser.(*Parser).parseBlock(0x14000556fc8)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:983 +0xe8 fp=0x14000906f70 sp=0x14000906d50 pc=0x100f6c2b8
github.com/risor-io/risor/parser.(*Parser).parseFunc(0x14000556fc8)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:1011 +0x194 fp=0x140009071a0 sp=0x14000906f70 pc=0x100f6c674
github.com/risor-io/risor/parser.(*Parser).parseFunc-fm()
	<autogenerated>:1 +0x28 fp=0x140009071c0 sp=0x140009071a0 pc=0x100f71d58
github.com/risor-io/risor/parser.(*Parser).parseNode(0x14000556fc8, 0x1)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:483 +0xc8 fp=0x14000907320 sp=0x140009071c0 pc=0x100f677e8
github.com/risor-io/risor/parser.(*Parser).parseExpressionStatement(0x14000556fc8)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:458 +0x2c fp=0x14000907490 sp=0x14000907320 pc=0x100f675dc
github.com/risor-io/risor/parser.(*Parser).parseStatement(0x14000556fc8?)
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:300 +0x230 fp=0x140009074c0 sp=0x14000907490 pc=0x100f65930
github.com/risor-io/risor/parser.(*Parser).Parse(0x14000556fc8, {0x106cf1a80, 0x1092bbe40})
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:215 +0x104 fp=0x14000907550 sp=0x140009074c0 pc=0x100f64d44
github.com/risor-io/risor/parser.Parse({0x106cf1a80, 0x1092bbe40}, {0x1400015a700?, 0x1008f714c?}, {0x0, 0x0, 0x0})
	/Users/edhemphill/ubuntu-work/risor/parser/parser.go:34 +0xa0 fp=0x14000907590 sp=0x14000907550 pc=0x100f625c0
github.com/risor-io/risor.Eval({0x106cf1a80, 0x1092bbe40}, {0x1400015a700, 0x32e}, {0x1400087cc00?, 0x400000000001be?, 0x151b90148?})
	/Users/edhemphill/ubuntu-work/risor/risor.go:19 +0x60 fp=0x140009075f0 sp=0x14000907590 pc=0x100fdc720
main.init.func2(0x109238a00, {0x140000c3360?, 0x4?, 0x1054bb753?})
	/Users/edhemphill/ubuntu-work/risor/cmd/risor/root.go:323 +0x1204 fp=0x14000907cc0 sp=0x140009075f0 pc=0x1054b7844
github.com/spf13/cobra.(*Command).execute(0x109238a00, {0x140000b20d0, 0x1, 0x1})
	/Users/edhemphill/go/pkg/mod/github.com/spf13/[email protected]/command.go:944 +0x63c fp=0x14000907de0 sp=0x14000907cc0 pc=0x1009fc73c
github.com/spf13/cobra.(*Command).ExecuteC(0x109238a00)
	/Users/edhemphill/go/pkg/mod/github.com/spf13/[email protected]/command.go:1068 +0x320 fp=0x14000907ea0 sp=0x14000907de0 pc=0x1009fcec0
github.com/spf13/cobra.(*Command).Execute(...)
	/Users/edhemphill/go/pkg/mod/github.com/spf13/[email protected]/command.go:992
main.main()
	/Users/edhemphill/ubuntu-work/risor/cmd/risor/main.go:58 +0x1c4 fp=0x14000907f40 sp=0x14000907ea0 pc=0x1054b81c4
runtime.main()
	/usr/local/go/src/runtime/proc.go:271 +0x28c fp=0x14000907fd0 sp=0x14000907f40 pc=0x10085937c
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x14000907fd0 sp=0x14000907fd0 pc=0x10088f394

goroutine 2 gp=0x14000002c40 m=nil [force gc (idle)]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000eef90 sp=0x140000eef70 pc=0x1008597a8
runtime.goparkunlock(...)
	/usr/local/go/src/runtime/proc.go:408
runtime.forcegchelper()
	/usr/local/go/src/runtime/proc.go:326 +0xb8 fp=0x140000eefd0 sp=0x140000eef90 pc=0x100859638
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000eefd0 sp=0x140000eefd0 pc=0x10088f394
created by runtime.init.6 in goroutine 1
	/usr/local/go/src/runtime/proc.go:314 +0x24

goroutine 3 gp=0x14000003500 m=nil [GC sweep wait]:
runtime.gopark(0x1?, 0x0?, 0x0?, 0x0?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000ef760 sp=0x140000ef740 pc=0x1008597a8
runtime.goparkunlock(...)
	/usr/local/go/src/runtime/proc.go:408
runtime.bgsweep(0x14000120000)
	/usr/local/go/src/runtime/mgcsweep.go:318 +0x108 fp=0x140000ef7b0 sp=0x140000ef760 pc=0x100845518
runtime.gcenable.gowrap1()
	/usr/local/go/src/runtime/mgc.go:203 +0x28 fp=0x140000ef7d0 sp=0x140000ef7b0 pc=0x1008396d8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000ef7d0 sp=0x140000ef7d0 pc=0x10088f394
created by runtime.gcenable in goroutine 1
	/usr/local/go/src/runtime/mgc.go:203 +0x6c

goroutine 4 gp=0x140000036c0 m=nil [GC scavenge wait]:
runtime.gopark(0x10000?, 0x105dd7968?, 0x0?, 0x0?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000eff60 sp=0x140000eff40 pc=0x1008597a8
runtime.goparkunlock(...)
	/usr/local/go/src/runtime/proc.go:408
runtime.(*scavengerState).park(0x109254000)
	/usr/local/go/src/runtime/mgcscavenge.go:425 +0x5c fp=0x140000eff90 sp=0x140000eff60 pc=0x100842eac
runtime.bgscavenge(0x14000120000)
	/usr/local/go/src/runtime/mgcscavenge.go:658 +0xac fp=0x140000effb0 sp=0x140000eff90 pc=0x10084346c
runtime.gcenable.gowrap2()
	/usr/local/go/src/runtime/mgc.go:204 +0x28 fp=0x140000effd0 sp=0x140000effb0 pc=0x100839678
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000effd0 sp=0x140000effd0 pc=0x10088f394
created by runtime.gcenable in goroutine 1
	/usr/local/go/src/runtime/mgc.go:204 +0xac

goroutine 5 gp=0x14000003c00 m=nil [finalizer wait]:
runtime.gopark(0x140000ee618?, 0xa7?, 0x0?, 0x0?, 0x10086a818?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000ee580 sp=0x140000ee560 pc=0x1008597a8
runtime.runfinq()
	/usr/local/go/src/runtime/mfinal.go:194 +0x108 fp=0x140000ee7d0 sp=0x140000ee580 pc=0x1008387a8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000ee7d0 sp=0x140000ee7d0 pc=0x10088f394
created by runtime.createfing in goroutine 1
	/usr/local/go/src/runtime/mfinal.go:164 +0x80

goroutine 6 gp=0x14000003dc0 m=nil [sleep]:
runtime.gopark(0x7fb12de827f76?, 0x0?, 0x0?, 0x0?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000f0740 sp=0x140000f0720 pc=0x1008597a8
time.Sleep(0x6fc23ac00)
	/usr/local/go/src/runtime/time.go:195 +0xfc fp=0x140000f0780 sp=0x140000f0740 pc=0x10088bf2c
sigs.k8s.io/controller-runtime/pkg/log.init.0.func1()
	/Users/edhemphill/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/log/log.go:63 +0x30 fp=0x140000f07d0 sp=0x140000f0780 pc=0x105139790
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000f07d0 sp=0x140000f07d0 pc=0x10088f394
created by sigs.k8s.io/controller-runtime/pkg/log.init.0 in goroutine 1
	/Users/edhemphill/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/log/log.go:62 +0x24

goroutine 18 gp=0x140003b7340 m=nil [GC worker (idle)]:
runtime.gopark(0x7fb12e11ba139?, 0x3?, 0xeb?, 0x21?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000ea730 sp=0x140000ea710 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x140000ea7d0 sp=0x140000ea730 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000ea7d0 sp=0x140000ea7d0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 7 gp=0x1400017e000 m=nil [GC worker (idle)]:
runtime.gopark(0x7fb12e11ba73f?, 0x3?, 0x68?, 0x42?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000f0f30 sp=0x140000f0f10 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x140000f0fd0 sp=0x140000f0f30 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000f0fd0 sp=0x140000f0fd0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 34 gp=0x14000184380 m=nil [GC worker (idle)]:
runtime.gopark(0x7fb12e119fe9b?, 0x0?, 0x0?, 0x0?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x1400019a730 sp=0x1400019a710 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x1400019a7d0 sp=0x1400019a730 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x1400019a7d0 sp=0x1400019a7d0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 35 gp=0x14000184540 m=nil [GC worker (idle)]:
runtime.gopark(0x7fb12e11ba7e6?, 0x3?, 0xd9?, 0x52?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x1400019af30 sp=0x1400019af10 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x1400019afd0 sp=0x1400019af30 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x1400019afd0 sp=0x1400019afd0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 36 gp=0x14000184700 m=nil [GC worker (idle)]:
runtime.gopark(0x7fb12e11a71d7?, 0x0?, 0x0?, 0x0?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x1400019b730 sp=0x1400019b710 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x1400019b7d0 sp=0x1400019b730 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x1400019b7d0 sp=0x1400019b7d0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 19 gp=0x140003b7500 m=nil [GC worker (idle)]:
runtime.gopark(0x1092bdb20?, 0x1?, 0x82?, 0x3d?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000eaf30 sp=0x140000eaf10 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x140000eafd0 sp=0x140000eaf30 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000eafd0 sp=0x140000eafd0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 37 gp=0x140001848c0 m=nil [GC worker (idle)]:
runtime.gopark(0x7fb12e11ba7bc?, 0x1?, 0x1d?, 0xa?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x1400019bf30 sp=0x1400019bf10 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x1400019bfd0 sp=0x1400019bf30 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x1400019bfd0 sp=0x1400019bfd0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 20 gp=0x140003b76c0 m=nil [GC worker (idle)]:
runtime.gopark(0x7fb12e11ba645?, 0x3?, 0x1e?, 0x6c?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x140000eb730 sp=0x140000eb710 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x140000eb7d0 sp=0x140000eb730 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x140000eb7d0 sp=0x140000eb7d0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 38 gp=0x14000184a80 m=nil [GC worker (idle)]:
runtime.gopark(0x7fb12e11b507f?, 0x3?, 0xc9?, 0x24?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x1400019c730 sp=0x1400019c710 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x1400019c7d0 sp=0x1400019c730 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x1400019c7d0 sp=0x1400019c7d0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

goroutine 39 gp=0x14000184c40 m=nil [GC worker (idle)]:
runtime.gopark(0x1092bdb20?, 0x1?, 0xcb?, 0x77?, 0x0?)
	/usr/local/go/src/runtime/proc.go:402 +0xc8 fp=0x1400019cf30 sp=0x1400019cf10 pc=0x1008597a8
runtime.gcBgMarkWorker()
	/usr/local/go/src/runtime/mgc.go:1310 +0xd8 fp=0x1400019cfd0 sp=0x1400019cf30 pc=0x10083b7c8
runtime.goexit({})
	/usr/local/go/src/runtime/asm_arm64.s:1222 +0x4 fp=0x1400019cfd0 sp=0x1400019cfd0 pc=0x10088f394
created by runtime.gcBgMarkStartWorkers in goroutine 1
	/usr/local/go/src/runtime/mgc.go:1234 +0x28

New module: Google Cloud SDK

I noticed that there is support for the AWS SDK. I would love support for the Google Cloud SDK. This might be the tooling I would need to expedite some of my needs. Thanks!

Bug: panic inside range

The following risor code panics when run:

for i := range 1 {
  try(func() {
    print("hello")
    error("kaboom")
  })
}
hello
panic: interface conversion: *object.NilType is not object.Iterator: missing method Entry

Assigning the return value of print(...) to a local variable fixes the problem

for i := range 1 {
  try(func() {
    x := print("hello")
    error("kaboom")
  })
}

hello

The bug seems to be related to the implicit nil return value from print(...) which pollutes the stack. The same behaviour is observed more clearly with the following code:

for i := range 1 {
  try(func() {
    "hello"
    error("kaboom")
  })
}

panic: interface conversion: *object.String is not object.Iterator: missing method Entry

The string "hello" is implicitly returned and is popped from the stack instead of the expected IntIter when resuming. Unfortunately i don't have any more insight on why this may be happening.

Possibly related to #173

Support Go proxied struct methods returning non-maps

Using embedded risor, I'd like the method of a proxied Go struct to return either:

a. A struct with additional methods to call in risor:

Example A
package main

import (
	"context"
	"flag"
	"fmt"
	"os"

	"github.com/fatih/color"
	"github.com/risor-io/risor"
	"github.com/risor-io/risor/object"
)

type State struct {
	Name    string
	Running bool
}

type Service struct {
	name       string
	running    bool
	startCount int
	stopCount  int
}

func (s *State) IsRunning() bool {
	return s.Running
}

func (s *Service) Start() error {
	if s.running {
		return fmt.Errorf("service %s already running", s.name)
	}
	s.running = true
	s.startCount++
	return nil
}

func (s *Service) Stop() error {
	if !s.running {
		return fmt.Errorf("service %s not running", s.name)
	}
	s.running = false
	s.stopCount++
	return nil
}

func (s *Service) SetName(name string) {
	s.name = name
}

func (s *Service) GetName() string {
	return s.name
}

func (s *Service) PrintState() {
	fmt.Printf("printing state... name: %s running %t\n", s.name, s.running)
}

func (s *Service) GetState() State {
	return State{
		Name:    s.name,
		Running: s.running,
	}
}

const defaultExample = `
svc.SetName("My Service")
svc.Start()
state := svc.GetState()
state.IsRunning()
`

var red = color.New(color.FgRed).SprintfFunc()

func main() {
	var code string
	flag.StringVar(&code, "code", defaultExample, "Code to evaluate")
	flag.Parse()

	ctx := context.Background()

	// Initialize the service
	svc := &Service{}

	// Create a Risor proxy for the service
	proxy, err := object.NewProxy(svc)
	if err != nil {
		fmt.Println(red(err.Error()))
		os.Exit(1)
	}

	// Build up options for Risor, including the proxy as a variable named "svc"
	opts := []risor.Option{
		risor.WithDefaultBuiltins(),
		risor.WithBuiltins(map[string]object.Object{"svc": proxy}),
	}

	// Run the Risor code which can access the service as `svc`
	if _, err = risor.Eval(ctx, code, opts...); err != nil {
		fmt.Println(red(err.Error()))
		os.Exit(1)
	}
}

b. An object.Object (that would really be an *object.Proxy) so that I can use the methods of the returned struct

Example B (this one stack overflows)
package main

import (
	"context"
	"flag"
	"fmt"
	"os"

	"github.com/fatih/color"
	"github.com/risor-io/risor"
	"github.com/risor-io/risor/object"
)

type State struct {
	Name    string
	Running bool
}

type Service struct {
	name       string
	running    bool
	startCount int
	stopCount  int
}

func (s *State) IsRunning() bool {
	return s.Running
}

func (s *Service) Start() error {
	if s.running {
		return fmt.Errorf("service %s already running", s.name)
	}
	s.running = true
	s.startCount++
	return nil
}

func (s *Service) Stop() error {
	if !s.running {
		return fmt.Errorf("service %s not running", s.name)
	}
	s.running = false
	s.stopCount++
	return nil
}

func (s *Service) SetName(name string) {
	s.name = name
}

func (s *Service) GetName() string {
	return s.name
}

func (s *Service) PrintState() {
	fmt.Printf("printing state... name: %s running %t\n", s.name, s.running)
}

func (s *Service) GetState() object.Object {
	state := &State{
		Name:    s.name,
		Running: s.running,
	}
	stateProxy, _ := object.NewProxy(state)
	return stateProxy
}

const defaultExample = `
svc.SetName("My Service")
svc.Start()
state := svc.GetState()
state.IsRunning()
`

var red = color.New(color.FgRed).SprintfFunc()

func main() {
	var code string
	flag.StringVar(&code, "code", defaultExample, "Code to evaluate")
	flag.Parse()

	ctx := context.Background()

	// Initialize the service
	svc := &Service{}

	// Create a Risor proxy for the service
	proxy, err := object.NewProxy(svc)
	if err != nil {
		fmt.Println(red(err.Error()))
		os.Exit(1)
	}

	// Build up options for Risor, including the proxy as a variable named "svc"
	opts := []risor.Option{
		risor.WithDefaultBuiltins(),
		risor.WithBuiltins(map[string]object.Object{"svc": proxy}),
	}

	// Run the Risor code which can access the service as `svc`
	if _, err = risor.Eval(ctx, code, opts...); err != nil {
		fmt.Println(red(err.Error()))
		os.Exit(1)
	}
}

Of course, I could alter the underlying Go structs to get around this. However, I was wondering if it would be possible to do either A or B (or another option that's already supported) to get around this?

Recursively allow accessing structs

Hey,

I am right now trying to access a nested struct; but this does not seem to work because the proxies are not built recursively.

Is this correct, and would this be changeable? :-) I think that would be very useful.

All the best,
and keep up the good work,
Sebastian

Capture comments in the Risor AST

Discussed in #195

Originally posted by alexaandru February 28, 2024
At a quick glance I could not find them in there, which makes sense, the VM doesn't need them.

However, the AST would also be very valuable for writing a tool for auto formatting, and currently, such a tool would lose the comments information, which obviously would be unacceptable.

Escape hex and octal codes

The tokenizer is only considering \n, \r, \t, \\

risor/lexer/lexer.go

Lines 495 to 510 in e940fd0

// Handle \n, \r, \t, \", etc
if l.ch == '\\' {
l.readChar()
if l.ch == rune('n') {
l.ch = '\n'
}
if l.ch == rune('r') {
l.ch = '\r'
}
if l.ch == rune('t') {
l.ch = '\t'
}
if l.ch == rune('\\') {
l.ch = '\\'
}
}

Want to be able to do the following:

  • Same sequences as Go: https://go.dev/ref/spec#String_literals
    • \u65e5
    • \U00008a9e
    • \xff
  • Also the \033 octal one, commonly used in ANSI color codes (e.g printf '\033[31mred text\033[0m\n')
  • Additional hardcoded sequences:
    • \e alias for \033/\x1B, sometimes used in ANSI color codes (e.g in Bash echo -e '\e[31mred text\e[0m')

I'm mostly listing different escape sequences just to make it more consistent, but the ANSI color codes is the most important one to me so that I can do the following in Risor:

# my-risor-file.risor
c_reset := "\033[0m"
c_red := "\033[31m"

func log_error(err) {
  print('{c_red}error:{c_reset} {err}')
}

New module: SSH client

i think ssh support is a must for devops, it is possible for risor?

thank you for your amazing work :)

Bug: too many nested for loops start stepping on each other

I've a hard time getting this down to as small of an example as possible. Here's what I got:

my_list := ['a', 'banana', 'c', 'd']

func get_match(c) {
	for _, s := range my_list {
		if strings.has_prefix(s, c) {
			return s
		}
	}
}

func do_thing_with_string(s) {
	for _, c := range s {
		print('char: "{c}"')

		_ = get_match(c)
	}
}

for _, s := range my_list {
	print('for my_list start: {s}')
	do_thing_with_string(s)
	print('for my_list end: {s}')
}
$ risor bug.risor
for my_list start: a
char: "a"
char: "banana"
char: "c"
char: "d"
for my_list end: a

The behavior here is just super weird. Don't know what's really going on.

allow map[...] syntax on Result objects

Hey,

I just discovered Tamarin - great project which I like a lot :)

I naively wanted to do the following:

x := json.unmarshal(msg.Data)
x["a"] = 42
return x

This did not work, because the result of json.unmarshal is a Result object. At other places, Result is magically unwrapped.

What worked is the following (as a workaround):

x := json.unmarshal(msg.Data).unwrap()
x["a"] = 42
return x

Can this unwrapping be somehow done transparently on map access?

All the best, and keep up the great work,
Sebastian

Single quotes generate a strange error

While it seems that single quotes are not part of the grammar, the generated error is not really intuitive (i.e. if not supported I would expect an error on the single quote character):

> a = '10'
Error: eval error: name error: 10 is not defined

REPL should error if no tty

Example:

risor > file.txt

This blocks until I press Ctrl+D. The content of file.txt is then also this:

Risor

>>> ^M>>> o^M>>> oh^M>>> oh ^M>>> oh n^M>>> oh no^M>>> oh no ^M>>> oh no i^M>>> oh no im^M>>> oh no im ^M>>> oh no im s^M>>> oh no im st^M>>> oh no im stu^M>>> oh no im stuc^M>>> oh no im stuck

(the ^M is actually the CR escape code in my terminal, shown as I copied the result from cat file.txt instead of the file's raw content)

The case where this trips me up is when I want to run something via risor, but forget the file as argument, pipe the result into something else, but then its just stuck.

Suggest an error like:

$ risor > file.txt
cannot show repl: stdout or stderr is not a tty

panic occurs while accessing proxy object field

type Service struct {
	Name *string
}

func main() {
	code := `
	print(service.Name)
`

	_, err := risor.Eval(context.Background(), code, risor.WithGlobals(map[string]any{
		"service": Service{Name: nil},
	}))
	if err != nil {
		fmt.Println(err)
	}
}

the code above panics:

panic: reflect: call of reflect.Value.Interface on zero Value

maybe we should add IsNil check on FieldByName(name) and return object.Nil if it is true 1?

Footnotes

  1. https://github.com/risor-io/risor/blob/v1.5.2/object/proxy.go#L84 โ†ฉ

defer in for loop panic

the following code panics:

func main() {
	code := `
func() {
	defer func() {
		print("ok")
	}()
}()

func() {
	for _, v := range [1, 2, 3] {
		func(v) {
			// panic here
			defer func() {}()
			print(v)
		}(v)
	}
}()
`

	_, err := risor.Eval(context.Background(), code)
	if err != nil {
		fmt.Println(err)
	}
}

output:

ok
1
panic: interface conversion: *object.NilType is not object.Iterator: missing method Entry

Variables within closures return wrong values

Given:

func foo(a, b){
    return func(c){
        return [a, b, c]
    }
}

foo("hello", "world")("risor")

I expected ["hello", "world", "risor"], instead I get: ["world","hello","risor"]. Reproduced using the code editor on the home page.

I'd be curious to look into this myself and see if I can PR a fix.

CallFunc isn't concurrent-safe

I have multiple risor functions that I would like to execute a number of concurrently. I use the CallFunc that's with the context to achieve this. When I do this, I get inconsistent error messages. When I execute them sequentially I don't run into any errors.

A workaround might be to create multiple VMs (on-demand? or pool?), with the understanding that the risor function will not be able to have any side-effects.

exec error: attribute "noe" not found on module object

I'm getting an error: exec error: attribute "noe" not found on module object when I run the Risor as a REPL and make a typo like so:

>>> time.noe()
exec error: attribute "noe" not found on module object

This error is expected, but from then on I get the exact same error, no matter if I fix it:

>>> time.now()
exec error: attribute "noe" not found on module object

Have to close the REPL and restart.

Add code generator for writing Risor Go modules

Writing the Go modules for Risor is quite easy, but there's so much boilerplate. What if you wrote the functions as normal Go functions, and then generate the mapping functions to it?

Such as:

//risor:export
func Repeat(str string, count int) string {
	return strings.Repeat(str, count)
}

And it generates a file with this:

func gen_Repeat(ctx context.Context, args ...object.Object) object.Object {
	numArgs := len(args)
	if numArgs < 2 {
		return object.Errorf("type error: strings.repeat() takes 2 arguments (%d given)", len(args))
	}
	param1, err := object.AsString(args[0])
	if err != nil {
		return err
	}
	param2, err := object.AsInt(args[1])
	if err != nil {
		return err
	}
	return object.NewString(Repeat(param1, int(param2)))
}

func Module() *object.Module {
	return object.NewBuiltinsModule("strings", map[string]object.Object{
		"repeat": object.NewBuiltin("strings.repeat", gen_Repeat),
	})
}

This would not replace the docs generation and parsing proposed by #118 (comment) but instead complement it. One does not exclude the other

backtick doesn't seem to work properly

I am not sure if you are using the backtick for raw strings or for command execution, but it seems that parsing the closing backtick doesn't work properly.

This throws an error:

> a = `ls -l`
Error: parse error: no prefix parse function for ` found around line 1

This actually hangs the application:

a = `hello there
signal: interrupt

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.