Defining new built-in operators
The standard GolfScript interpreter has a rarely used feature that allows interpolated Ruby code in double quoted string literals.
One reason why this feature isn't more commonly used is that, awkwardly, the interpolated code is executed at compile time, and the output is cached by the GolfScript interpreter so that the same string literal will thereafter always yield the same value, even inside string eval.
However, one thing this feature turns out to be good for is defining new GolfScript operators implemented in Ruby code. For example, here's how to define a new binary addition operator that works just like the standard built-in +
operator:
"#{var'add','gpush a+b'.cc2}";
It doesn't really matter where you put the definition in your code; the new operator gets defined as soon as the double-quoted string containing the Ruby code is parsed. The add
operator defined above works exactly like the built-in +
operator, and can be used in exactly the same way:
1 2 add # evaluates to 3
"foo" "bar" add # evaluates to "foobar"
Of course, defining a new addition operator is pretty useless, unless you've done something silly like erase the built-in +
operator. But you can use the same trick to define new operators that do things Golfscript cannot (easily) do natively such as, say, uniformly shuffling an array:
"#{var'shuf','gpush a.factory(a.val.shuffle)'.cc1}";
10,shuf # evaluates to 0,1,2,...,9 in random order
or printing the contents of the whole stack:
"#{var'debug','puts Garray.new($stack).ginspect'.cc}";
4,) ["foo" debug # prints ["" [0 1 2] 3 "foo"], leaving the stack untouched
or interactive input:
"#{var'gets','gpush Gstring.new(STDIN.gets)'.cc}";
]; { "> " print gets ~ ]p 1 } do # simple GolfScript REPL
or even web access:
"#{
require 'net/http'
require 'uri'
var'get','gpush Gstring.new(Net::HTTP.get_response(URI.parse(a.to_s)).body)'.cc1
}";
"http://example.com" get
Of course, a somewhat golfier (and riskier!) implementation of the latter would be e.g.:
"#{var'get','gpush Gstring.new(`curl -s #{a}`)'.cc1}";
While not particularly golfy in itself, this lets you extend the capabilities of GolfScript beyond what the built-in commands provide.
How does it work?
The authoritative reference on how to define new GolfScript operators in this way is, of course, the source code for the interpreter. That said, here's a few quick tips:
To define a new operator name
that runs the Ruby code code
, use:
var'name','code'.cc
Inside the code, use gpop
to read a value off the stack, and gpush
to push one back in. You can also access the stack directly via the array $stack
. For example, to push both a
and b
onto the stack, it's golfier to do $stack<<a<<b
than gpush a;gpush b
.
- The positions of the
[
array start markers are stored in the $lb
array. The gpop
function takes care of adjusting these markers down if the stack shrinks below their position, but manipulating the $stack
array directly does not.
The .cc
string method that compiles Ruby code in a string into a GolfScript operator is just a convenience wrapper around Gblock.new()
. It also has the variants .cc1
, .cc2
and .cc3
that make the operator automatically pop 1, 2 or 3 arguments off the stack and assign them to the variables a
, b
and c
. There's also an .order
method that works like .cc2
, except that it automatically sorts the arguments by type priority.
All values on the GolfScript stack are (and should be!) objects of type Gint
, Garray
, Gstring
or Gblock
. The underlying native integer or array, where needed, can be accessed via the .val
method.
- However, note that
Gstring.val
returns an array of Gint
s! To turn a Gstring
into a native Ruby string, call .to_s
on it instead (or use it in a context that does that automatically, like string interpolation). Calling .to_gs
on any GS value turns it into a Gstring
, so any GS value can be stringified with .to_gs.to_s
.
The gpush
function doesn't auto-wrap native Ruby numbers, strings or arrays into the corresponding GS types, so you'll often have to do it yourself by explicitly calling e.g. Gstring.new()
. If you push anything other than one of the GS value types onto the stack, any code that later tries to manipulate it is likely to crash.
The GS value types also have a .factory
method that calls the type's constructor, which can be useful e.g. for rewrapping arrays/strings after manipulating their contents. All the types also have a .coerce
method that performs type coercion: a.coerce(b)
returns a pair containing a
and b
coerced to the same type.
... x
en... [x]
? Le mieux que je puisse voir est[.;]
.