Pepsi and Coke (No. 1)

I'm playing with the "Ian's stuff" last two days.
I'm learning by doing and the example project I
picked is to write Ruby in it.

  To do this, one will require to write:

- The mapping from the Ruby object model to the
  Coke one.  Since the Coke's model is malleable and
  the essential part of it is more or less
  instance-based, the class and module hierarchy
  (and instance-specific behavior) of Ruby should be
  easily mapped onto it.  A method definition of
  Ruby has some complex rules and allows variable
  arguments.  There should be some indirection so
  that such method can be represented a set of coke

- The grammar definition.  Alex has been working
  on a Meta-II like grammar processor (written in
  itself on top of Coke).

  Unfortunately, the latter is not readily usable
for platforms I use.  So, I'd do the former first
for the time being.

  At a glance, Coke syntax may look a bit like
Lisp.  But there is one fundamental difference:
"expressions are not cons pairs, but Expression
objects."  An Expression object is a subclass of
IdSt Array, which is fixed-width collection of
other objects.  For example, if you write '(1 2 3)
in Coke, it is an (Array-like) Expression object
whose length is 3.  In the other words, the
Lispy-looking "()-world" is already contaminated
by objects in a fundamental way.

BTW, an expert says: "The most accurate thing you
could say is that Coke expressions are parse tree
nodes and the semantics is that of C."

  There are also a few things to keep in mind:

- The primary data that Coke manipulates is
  "primitive" data.
- A quotation (more or less) gives you simple way
  to represent a simple object.

- With a pair of square brackets, you can send a
  message to an object.

  These are simple in essence, but can really
confuse you in different ways.  One is that the
SmallInteger object has a tagged representation,
but the interactive shell shows them as primitive

  In below, I'll get some executable examples.  I
use jolt in idst-5.7 distribution.  You I'd
recommend to download the source and built it by
yourself.  As you go, you'll want to modify the
source of jolt as well.  You should always give
"boot.k" as initial command line argument of
executable of jolt (called "main" or "main.exe")
and "-" to mean you are using it interactively:

bash$ ./main boot.k -

It should show some message and a prompt (".").

  Let us type "3" (one character for three not the
double quatation marks) into the interactive
shell, it means 3 in the primitive data world.
So, the interactive shell returns 3:

 => 3

  On the other hand, you can type "'3" to mean 3 in
the objects' world.  Note that the -eval and
-print part of the interactive shell (or
read-eval-print loop of the shell) doesn't know it
was an object so it prints out "7" (3<<1|1):

 => 7

Similarly, a string enclosed by double quotation
marks represents a primitive string (like C
string), but a single quotation makes it an

."abc" ; => C string
.'"abc" ; => String object

Under this shell, the number arithmetic can be

.(* 3 3)
 => 9     ; primitive data 3 times primitive data 3.
.(* '3 '3)
 => 49    ; (3<<1|1)*(3<<1|1).
.['3 *'3]
 => 19    ; (3*3)<<1|1
.[3 * 3]
 => 3     ; ((3>>1)*(3>>1)<<1|1)

The lispy version of function (prefix "*" at the
head of ()-list) interprets given arguments as
primitive numbers and multiply them.  The message
version of "*" interprets receiver and arguments
as objects and multiply them.  In both cases, the
printed results are given in the primitive data
representation.  What happens if you try to send
"*" message to a primitive data?  If fails rather

.[2 * 2]
     13 [main] main 308 _cygtls::handle_exceptions: Exception: STATUS_ACCESS_VIOLATION
    560 [main] main 308 open_stackdumpfile: Dumping stack trace to main.exe.stackdump

and crashes because the object version of multiply
cannot accept primitive data.  Booleans and
Characters in IdSt are simply mapped to the Coke
world; Any non-zero value is true and zero is
false.  And, Characters are integers.  (There
seems no notation for an IdSt Character object in

  Let us continue the experiment.  A good way is
to create your own test program file (let us call
it "tmp.k") and write your stuff into it.  To
invoke it from the interactive shell, you do:

bash$ ./main boot.k tmp.k -

  As an example function, let's define
"vector-map" function in Coke.  The vector-map
function accepts a function and an Array.  It
applies the function to each element of the Array
and stores the results into new Array and return
it.  First, you add a few lines to your test file
to "import" the pre-compiled IdSt objects into the
Coke-world and name them accordingly.  Also you
can define a utility method "print" for Object:

(define Object (import "Object"))
(define Array (import "Array"))
(define String (import "String"))
(define [Object print] [StdOut nextPutAll: [self printString]])

  Then, define vector-map (it took me quite a

(define vector-map
  (lambda (func list)
     (let ((s [list size])
           (ret [Array new: [list size]])
	   (idx '0)
	   (tmp 0))
	(while [idx < s]
	   (set tmp (func [list at: idx]))
	   [ret at: idx put: tmp]
	   (set idx [idx + '1]))

Or, one could write:

(define vector-map
  (lambda (func list)
     (let ((s [[list size] _integerValue])
           (ret [Array new: [list size]])
	   (idx 0)
	   (tmp 0))
	(while (< idx s)
	   (set tmp (func [list at: [SmallInteger value_: idx]]))
	   [ret at: [SmallInteger value_: idx] put: tmp]
	   (set idx (+ idx 1)))

to get the same effect.  In the former, "s" and
"idx" hold objects (for example, "[list size]"
returns a number object), and in the latter, they
hold primitive data.  Just see the usage of them
in the body of "while" changes from []-world to
()-world.  I haven't measured the performance, but
the former is simpler to read.  One thing you
would forget most is to put a quote in the
increment of "idx".  by the time you write a few
things in the ()-world, switching to []-world
requires some mind twist, especially "idx" already
holds and object so you don't have to quote it.

  The variable "tmp" is unnecessary, so you can

(define vector-map
  (lambda (func list)
     (let ((s [list size])
           (ret [Array new: [list size]])
	   (idx '0))
	(while [idx < s]
	   [ret at: idx put: (func [list at: idx])]
	   (set idx [idx + '1]))

I put "tmp" to signify that naked 0 is kind of nil
(in fact, it is NULL in the C sense) and good to
initialize a variable.  In either one, you can use

(define n '(1 2 3))
(define n (vector-map (lambda (x) ['3 * x]) n))
[n print]

  Now, since the objects are almost there for you
to manipulate, you might think that the following
would work:

(define n '(1 2 3))
[[n collect: (lambda (x) ['3 * x])] print]

  However, an expert of Coke told me that it
doesn't work because "collect:" expects a block in
IdSt world, but Coke's lambda is a function and
not a closure.  For the time being, we put up with
this kind of verbose description.

  The reason that the above code doesn't work is
not about mixing ()'s and []'s.  They are quite
tolerant as long as it makes sense.  For example,
you can write:

[((lambda (x) ['3 * x]) ['(1 2 3) first]) print]

The lambda takes an object and multiply it by 3 in
the object world.  You give the first element of
the list (an Expression array) and pass it to the
function as an argument.  To the returned object,
you send "print" message.  Unlike Smalltalk or
IdSt, you have to put "[]" to each message
sending.  For example, you can't write:

['(1 2 3) first print]

to mean the same thing.

  Okay, here is what I've learned so far.  If I
don't get bored, I'll keep going and write more