WooScript 2.1

So it’s that time when I change the syntax and break all the old scripts… again…

I’ve changed the way that rules are called within the language. That’s involved the removal of “call” and the replacement of “repeat”. I’ve also taken the opportunity to fix scoping within the language which has involved replacing the “push” and “pop” functions. And to add a bit more fun, I’ve implemented the if() statement to get rid of “branch”.

First up lets talk about the call method. If you’ve tried the scripting language you’ll know that subrules are called using the following syntax:

rule main {
call(r1)
}

rule r1 {}

This makes the language easy to parse in a single pass, because when the scripting engine reads through the script, it doesn’t need to know that “r1″ is a rule when it encounters the “call” function. However, it prevents you doing cutesy things like creating words out of objects etc. using simple syntax…

rule main {
//call(d) call(o) call(m) call(space) call(r) ....
d o m space r u l e s
}

So first up is to remove the call function completely. That means that the language parser needs to understand that a string is the name of a rule when parsing the program. In languages like C++ this is done by providing a prototype for all rules in the header so that the language understands that a string is a function at parse time.

prototype rule r1

rule main {
r1
}

rule r1 {}

But this is ugly. Much nicer if the parser just finds all the rule definitions using a pre-parse of the program. Although that does make the language parser run twice. :( Still, seeing as we’re not bothered about parsing performance because todays processors are as powerful as God, we can get away with it. Which means no requirements for prototypes, simply call a rule, and if it’s defined in the same program it’ll work fine.

rule main {
r1
}

rule r1 {}

This also raises the question of whether or not you should have brackets on all function calls. It’s a standard thing to do in Java/C/C#/etc. but not so standard in languages like basic/logo/ML/etc. Generally speaking I like using brackets to delineate a function call versus a variable, but when there’s no arguments the additional brackets just add syntax for little gain, so I’m allowing function calls without brackets in the syntax, where there are no arguments. User defined rules with arguments might come along later!

Having fixed one calling method, the next one to take a look at is the repeat function. In the past, building a 2D grid of objects would involve code a bit like this..

rule main {
repeat(r1, 4)
}

rule r1 {
push()
repeat(r2, 4)
pop()
pos.y += 1
}

rule r2 {
call(box)
pos.x += 1
}

Yuck. The repeat function forces you to define the inner code in a new rule, which means that you can have trouble working out what code is being called in the repeat statement. So instead of repeating a rule, the repeat statement now repeats the following code block. The code above now looks like…

rule main {
 repeat (4) {
  push()
  repeat (4) {
   pos.x += 1
   box
  }
  pop()
  pos.y += 1
 }
}

Neater, although still quite lengthy given the need for additional curly braces.

If you’ve read many of these posts you may have noticed that I’ve struggled with how to implement scope for changes to the program state. This is a bit of a tough one to explain so bear with me. Imagine you want to create one object which builds a sequence of cubes in a stack. Once this is complete you then want to go back to the beginning, step along one, and build another stack.

The code sample above shows this where push() and pop() are used to save and restore the program state (position etc.). However, for some reason that made sense at the time, the call function automatically adds a push() and pop() inside the function call, meaning any state changes in a rule call are lost. Unless you use repeat, in which case the state changes are preserved. This leads to some very strange syntax rules.

For example, take a look at the following three ways of calling a rule.

// return state to normal
call (r1)
// leave state after the call
repeat (r1, 1)
// return state to normal
push()
repeat (r1, 1)
pop()

Lets face it, this is a bit of a mess. So from now on, all state changes will affect the global state unless you explicitly preserve the state. And instead of using push/pop you just have to use curly braces.

// call a rule and change global state
r1
// call a rule and return state to normal
{ r1 }

This is probably the biggest syntax change of the lot, and has required quite a few changes to existing sample code.

And on to the final change, the introduction of if statements instead of the slightly strange branch() statement that previously existed. First off, a quick example of what you could do with branch…

branch(r1, 0.5, r2, 0.5)

This simple code sample allows you to call one of two rules 50% of the time depending on a random number variable. What “branch” doesn’t let you do is do conditional statements dependent on the program state. For example, imagine I’m building a structure, but I want to terminate the objects if the position goes below zero on the y axis. Previously there was no way to do this, but now you can simply write.

if (pos.y>0.0) { r1 }

And to reproduce the 50/50 split shown above you’d write something like…

if (0:1 < 0.5) { r1 }
else { r2 }

So, hopefully these changes all make basic sense, and they've all been done to improve the functionality of the language moving forwards. I'll be going through the site upgrading all the help and old articles to use this new syntax, so let me know if you think that's a bad idea... quickly!

Of course, it's also time for the obligatory image to go with this post. This one shows off a new distance estimated primitive I've been working on; the fragmented sphere shell. Lit using an interior sphere light.

fragmented sphere

Fragmented Sphere

And here's the code to go with it. Note that you can still specify a distance function using a string, but it's also now possible to reference a shader which assigns a value to "distance". This shader type is also going to be extended to support material settings, but I'll talk about that in a separate post.

rule main {
scale = vec(8,8,8)
diff = vec(1,0.5,0)

distanceminimum=0.001
distanceiterations=2000
distancefunction(DE)
distance

// create the light
pos.y += 0.5
scale = vec(2,2,2)
diff = vec(100,100,100)
spherelight
}

shader DE {
distance =
   max(
        max(neg(sphere(pos, vec(0,0,0), 0.9)), // subtract little sphere from ...
                sphere(pos, vec(0,0,0), 1.0)), // ... big sphere
        box(repxyz(pos,vec(0.2,0.2,0.2)), // fold space
               vec(0.07,0.07,0.07), vec(0,0,0))) // with a box in it 0.07 big
}

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

Spam Protection *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>