Skip to content
nasser edited this page Jun 4, 2011 · 9 revisions

Internally, all user code is run through the Ruby VM, making it difficult to label Zajal as a completely new language on its own. Instead, like Processing is to Java, Zajal is based on Ruby and largely compatible with it, but deviates in a few important areas.

The interpreter is written as an openFrameworks application in C++ to benefit from OF's event loop. The API is written mostly in C to interface with the Ruby VM and wrap OF's objects and functions. Experimental and complex parts of the API are implemented in Ruby with plans to re-implement them in C if performance becomes an issue.

Note: This page only describes features that are particular to Zajal and assumes a knowledge of Ruby. For a more complete overview without such assumptions, see Syntax.

Program Structure

Open Question

  • Should animation in reduced mode be removed?

Zajal programs are not runnable as Ruby code. Feeding a Zajal program to a stock Ruby interpreter will cause it to complain that setup and update are not defined. This is because, in an effort to keep user code as focused and streamlined as possible, calls like require "zajal" that you may expect are not needed. Details like that are taken care of by the Zajal interpreter.

Programs are structured much as they are Processing, albeit without the Java cruft.

t = 0

setup do
  size 200, 200
  smoothing true
end

update do
  t += 0.1
end

draw do
  circle width/2 + cos(t) * 10, height/2 + sin(t) * 10, 5
end

Alternatively, programs can be written without any event blocks (setup, update and the like) and will run in a kind of reduced-mode to render a single frame.

circle 10, 20, 10
circle 50, 20, 5
circle 100, 20, 1

Inspired by a Processing feature, this allows for the simplest programs possible. Programs need-not be trivial, however. Using loops and the full Zajal API complex sketches can be coded without every setting foot in a draw event.

size 400, 400

smoothing true

matrix {
  rotate 45
  translate -width/2, -height/2
  
  width.to_i.times do |n|
    x0, y0 = n*5, height/2 + sin(frame * 0.1 + n/3) * 50
    x1, y1 = (n+1)*5, height/2 + sin(frame * 0.1 + (n+1)/2) * 10
    
    line x0, y0, x1, y1 
  end
}

The above sketch is cheating. It is using the frame method, which increments after each frame is drawn as varying value and actually animating. This is not something Processing can do, not what this reduced mode was designed for (it is meant as a means to draw a single frame).

Top Level Globals

TODO

  • Implement shadowing in methods/blocks

Variables declared outside of any block or method, in the top-level scope, are made global. The availability of easy-to-define variables that are accessible from anywhere in the code is a standard feature from Processing and openFrameworks.

# this is a local variable with top level scope
k = 0

# top level local in event block:
# always works, because this is a closure
update do
  k += 1
end

# top level local in method:
# does not work in ruby, because method calls change scope
# made to work in zajal 
def somefunction n
  return n / k
end

This introduces a currently unaddressed ambiguity, however: what do you do when a block or method uses a parameter with the same name as a top level global?

k = 0

# top level local with same name as method parameter:
# currently errors out.
def otherfunction k
  k += 10
  return k ** 2
end

Currently, the code will throw a syntax error because of the way globalization works. The plan is to have the global variable shadowed by the local one, in compliance to Ruby 1.9's treatment of block parameters and the local variables in their scope, possibly issuing a warning.

Public Instance Variables

TODO

  • attr_* should disable publicization
  • implement attr_private to make all variables private without a standard attr_*

Favoring rapid prototyping over encapsulation, Zajal makes all instance variables of a class public by default.

# normal class definition, but attr_accessor and
# friends can be left out
class Car
  def initialize
    @speed = 10
    @weight = 52.75
    @name = "VW Polo"
  end
end

polo = Car.new

# instance variables can be set
update do
  polo.speed -= 0.1
end

# instance variables can read
draw do
  text polo.name
  text polo.speed
end

The rationale is that in creative coding, the convenience of not having to type attr_accessor :a, :b, :c weighs heavier than the benefits of encapsulation. This assumption is true enough to be a sensible default. For the other times, this feature is planned to be disabled by the use of any of the standard attr_* methods.

# attr_* methods set class to default Ruby behavior
class Magazine
  attr_accessor :current_page
  
  def initialize
    @current_page = 0
    @name = "The New Yorker"
  end
end

m = Magazine.new

# works
m.current_page += 1 

# doesn't work, name= method not defined
m.name = "Wired"

A new method, attr_private, is also planned to make all instance variables private and restore default Ruby behavior.

# explicitly set class to default Ruby behavior
class Lamp
  attr_private

  def initialize
    @wattage = 60
  end
end

# will result in error, no wattage= method defined
l = Lamp.new
l.wattage += 10

Promiscuous Keyboard Events

Open Question

  • Should number keys compare positively to their integer counterparts? i.e. KeyEvent("4") == 4
  • How does this affect comparisons to key codes? Does one replace the other or can they coexist? Overlap only occurs with Enter, Delete and Tab keys.
  • How important is key code comparison? Can we do away with it?

Keyboard events return a special KeyEvent instance, which abstracts away from the integer key code that openFrameworks hands back. Its main purpose is to facilitate comparisons in conditionals and case...when constructs.

# after pressing the 'a' key, all of the following conditional code runs
key_down do |key|
  if key == "a" then
    puts "Pressed A (String Comparison)"
  end

  if key == :a then
    puts "Pressed A (Symbol Comparison)"
  end
  
  if key =~ /a/ then
    puts "Pressed A (Regular Expression Comparison)"
  end
  
  if key == 97 then # this might be changed, see open questions
    puts "Pressed A (Keycode Comparison)"
  end
end
# after pressing the left arrow key, all of the following conditional code runs
key_down do |key|
  # no sensible way to compare against a string or a regular expression
  
  if key == :left then
    puts "Pressed Left Arrow Key (Symbol Comparison)" 
  end
  
  if key == 356 then # this might be changed, see open questions
    puts "Pressed Left Arrow Key (Keycode Comparison)"
  end
end
# after pressing the '4' key, all of the following conditional code runs
key_down do |key|
  if key == "4" then
    puts "Pressed 4 Key (String Comparison)"
  end
  
  if key == :'4' then 
    puts "Pressed 4 Key (Symbol Comparison)"
  end
  
  if key == 52 then # this might be changed, see open questions
    puts "Pressed 4 Key (Keycode Comparison)"
  end
end
# case-when constructs work as expected
key_down do |key|
  case key
  when "a" then
    puts "Pressed a"
  when :left, :right then
    puts "Pressed left or right"
  when /[A-Z]/ then # ...
    puts "Pressed a capital letter"
  end
end

The idea is that it's hard to represent all the different keys on the keyboard in a coherent, readable way. if key == 73 then is not readable, as 73 is effectively a magic number. The printable keys are easy, as they have ASCII representations and could be strings. Non-printable keys lend selves better to symbols, however. Representing all keys as symbols breaks on number keys, as :4 is not a valid symbol, and is instead represented as :'4', which sucks.

To this end, the KeyEvent class was written that Just Works. Although it clearly violates the transitivity of equality (key == 4 and key == "4" but 4 != "4"), it serves its purpose and makes keyboard event code much more readable.

Clone this wiki locally