Cr-Wren: a binding to the Wren language

A binding to wren, a small, fast, class-based, concurrent scripting language.

The main goal is to make this binding as comfortable and easy to use as possible, and allow Crystal developers to make apps extensible using a nice, readable language.

The current status of the binding is that it works but it’s of course not very well testedm and you can find it at https://github.com/ralsina/cr-wren

Here is an example of creating a Wren VM and calling Wren code from Crystal and viceversa:

require "../src/wren.cr"

vm = Wren::VM.new "myvm"

# We can just tell the VM to interpret (run) code
vm.interpret "main", "System.print(\"Hello World!\")"

# This defines a function in Wren
vm.interpret "main", %(
  var add = Fn.new { |a,b|
    return a+b
  }
)

# And we can call it from Crystal
puts vm.call("main", "add", "call", [1, 2])     # => 3.0
puts vm.call("main", "add", "call", ["1", "2"]) # => "12"

# This fails with a runtime error even when the equivalent Wren code works
# probably a bug somewhere
# puts vm.call("main", "add", "call", [[1, 2], [3, 4]]) # => [1,2,3,4]


# Register a Crystal proc to add floats into the Wren VM
vm.register_function(
  "main", "Math", "add",
  Wren::VM.wrap("myvm", ->(a : Float64, b : Float64) : Float64 {
    a + b
  })
)

# Register a proc to add 3 floats. We can register the same proc more than
# once with the same name and different arity
vm.register_function(
  "main", "Math", "add",
  Wren::VM.wrap("myvm", ->(a : Float64, b : Float64, c : Float64) : Float64 {
    a + b + c
  })
)

# We also need to declare it as "foreign" in Wren.
vm.interpret "main", %(
  class Math {
    foreign static add(a,b)
    foreign static add(a,b,c)
  }
)

# And we can call it on Wren, which will use the Crystal code
vm.interpret "main", %(
  System.print("2+3.5=")
  System.print(Math.add(2,3.5))
  System.print("1+2+3=")
  // We can pass a string as argument here because cr-wren will cast it to Float64
  System.print(Math.add("1",2,3))
) # 2+3.5=5.5  ¨1"+2+3=6

5 Likes

Is the repository still private? I couldn’t access it.

I also wrote bindings but ran into memory issues that I never solved unfortunately.

Oops, my bad, fixed I never intended for it to be private :slight_smile:

I didn’t even try binding foreign classes, I am guessing that’s where you ran into the memory issues?

Just as a FYI, this is all it takes to give Crinja user-defined filters using Wren:

  vm = Wren::VM.new "vm"
  # Filters defined in Wren in template_extensions/filters/*.wren
  Dir.glob("template_extensions/filters/*.wren").each do |f|
    filter_name = Path[f].stem
    if !Env.filters.has_key? filter_name
      filter_code = File.read(f)
      vm.interpret filter_name, filter_code

      Env.filters[filter_name] = Crinja.filter() do
        args = [target.to_s] + arguments.to_h.keys.sort!.map { |k| arguments[k].to_s}
        p! args, arguments.kwargs
        r = vm.call(filter_name, "filter", "call", args).to_s
        Crinja::Value.new(r)
      end
    end
  end

And then the user can do things like

var filter = Fn.new { |target, greeting, is_super|
  var result = ""
  if (is_super=="true") {
    result = "Super "
  }
  return result + greeting + " " + target
}
1 Like