git @ Cat's Eye Technologies Velo / master README.md
master

Tree @master (Download .tar.gz)

README.md @masterview rendered · raw · history · blame

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
Velo
====

<!--
Copyright (c) 2012-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license.  See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-Velo
-->

_Language version 0.1, distribution revision 2019.0326_

> "Jaws was never my scene, and I don't like Star Wars."  
> -- Queen, "Bicycle Race"

Velo is an object-oriented language, inspired somewhat by Ruby,
and sharing somewhat Robin's "radically goo-oriented" approach.

(I don't really like the term "scripting language", but it seems
to be fitting here, for whatever reason, so where I might normally
say "program" I will instead say "script".)

We shall introduce Velo with a series of simple examples in
Falderal format.  The design of Velo is still somewhat inchoate,
so do not expect all of these examples to be perfectly consistent
or even necessarily feasible.

    -> Tests for functionality "Interpret Velo Script"

First, the ubiquitous "Hello, world!":

    | extend IO
    | print {Hello, world!}
    = Hello, world!

This example demonstrates what are likely the two most outstanding
features of Velo:

*   Scripts are no different from object classes.  Thus, `extend`
    is used in the very same manner as an "import" or "require"
    construct would be in some other language; it is not dissimilar
    to Ruby's `include` method.
*   Literal strings are delimited by curly braces.  In fact, in Velo,
    strings are no different from code blocks.

Let those two ideas sink in for a moment, then we'll continue.

Strings as Blocks
-----------------

Conditional statements are implemented by a method called `if`.
This method is on the root object, called `Object`, from which all
objects, including your script, descend.

    | if ({X}.equals {X}), {IO.print {Yes}}, {IO.print {No}}
    = Yes

    | if ({X}.equals {Y}), {IO.print {Yes}}, {IO.print {No}}
    = No

To try to hammer this block-is-a-string thing home, you can just
as easily call this method with arguments that are string variables,
rather than string literals (that look like blocks of code.)

    | yes = {IO.print {Yes}}
    | no = {IO.print {No}} 
    | if ({X}.equals {Y}), yes, no 
    = No

You may be thinking that this is basically an implicit and
ubiquitous use of `eval` -- which would be accurate -- and that that
means that execution of Velo code inevitably suffers in efficiency --
which is not accurate.  When the string being `eval`ed is a literal
string, it can be transformed into the internal format for code as
early as possible, making it no less efficient than any other code.
It's only when you have to `eval` a string in a variable, where you
can't predict what it will be until you evaluate the surrounding code,
that you necessarily take a performance hit.

    | p = {extend IO; print }
    | yes = p.concat {{Yes}}
    | no = p.concat {{No}}
    | if ({X}.equals {X}), yes, no
    = Yes

(The above example would be even more persuasive if `yes` and `no`
were constructed from user input, or a random selection, but those
are more difficult to present as readable, automated test cases.)

Scripts as Classes
------------------

Classes can be defined within a script:

    | Jonkers = {
    |   IO.print {What?}
    | }.create new
    = What?

Note that `create` is a method on strings, and it takes a parameter,
which in this case is the result of calling the method `new` (to be
explained later.)  The class itself has a "body" of code which is run when
the class is defined, not unlike the situation in Ruby.  This code can be
used to set up class-level attributes:

    | Jonkers = {
    |   name = {Ulysses}
    | }.create new
    | IO.print Jonkers.name
    = Ulysses

Normally a class will also have some methods, but we'll cover that later.

The fact that classes can be defined in a script, and that scripts are
no different from classes, means that classes can be defined within a
class:

    | Jonkers = {
    |   Fordible = {
    |     extend IO
    |     print {Sure}
    |   }.create new
    | }.create new
    = Sure

Our demonstrations above show that a class has a "body" of code which
is run when it is defined with `create`.

The block used to define a class, of course, is just a string, and can be
a string variable.

    | a = {extend IO; print {What?}}
    | Jonkers = a.create new
    | Jonkers.new
    = What?

    | {extend IO; print {Yes!}}.create new
    = Yes!

What's happening here is actually this.

The `new` method, inherited from `Object`, creates a new, almost
featureless object; its only feature is that it inherits from `Object`.

    | a = new
    | a.IO.print {A new object inherits IO from Object.}
    = A new object inherits IO from Object.

The `create` method, inherited from `String`, runs its receiver, as a
script, on the object passed to it.  This may seem an odd usage of the
word "create", as in our examples above, it's actually `new` that
creates the object.  But in English, the word "create" does sometimes
have this meaning; for example, a royal subject can be "created a knight".
And, in Velo, there is nothing preventing you from passing an existing
object to `create`.

    | Jonkers = {foo = {123}}.create new
    | {bar = {456}}.create Jonkers
    | IO.print Jonkers.bar
    = 456

Aside on Syntax
---------------

We've been so busy describing these remarkable qualities of Velo that
we haven't said much about the basic properties of its syntax.  Some
may be obvious from the examples, but there are probably points worth
clarifying here.

A Velo script is simply a list of expressions, seperated by end-of-line
markers, with a few qualifications:

*   A sequence of linefeeds and carriage returns is an end-of-line
    marker.
*   The token `;` is also considered an end-of-line marker.
*   A series of end-of-line markers, possibly with intervening whitespace,
    is considered a single end-of-line marker.
*   An end-of-line marker can optionally occur after the tokens `(`,
    `=`, and `,`, without terminating the expression.
*   An end-of-line marker can optionally appear before any expression
    in a script, so that blank lines can appear at the start of a script.

Some examples of these properties follow.

    | IO.print {Hi}; IO.print {there}
    = Hi
    = there

    | 
    | 
    | IO.print {Hi}
    | 
    | 
    | IO.print {there}
    = Hi
    = there

    | IO.print (
    |   {Hi there})
    = Hi there

    | a =
    |   {Hi there}
    | IO.print a
    = Hi there

    | if {true},
    |   {IO.print {Yes}},
    |   {IO.print {No}}
    = Yes

A method call is followed by a list of arguments seperated by commas.
(You saw this above with the `if` method.)  Velo does not statically
record the arity of a method, so you can pass any number of arguments
that you want (but of course, the method may fail if it is not given the
number it expects.)  The parser tells when a method call ends by the
fact that there are no more commas (it instead ran into a `)` or an
end-of-line marker or the end of the file.)

Method calls can be chained:

    object.method.another.yetmore

Parentheses can be used to disambiguate when there are arguments in
the method calls in the chain:

    object.method a, object.method b, c
    object.method a, (object.method b, c)
    object.method a, (object.method b), c

Lines 1 and 2 above are equivalent, but line 3 is different.

Now, about those Methods
------------------------

Typically, a class will define some methods.  (For now, let's think of them
as class methods.)

    | Jonkers = {
    |   announce = {
    |     IO.print {This is }.concat {Maeve}
    |   }.method
    | }.create new
    | Jonkers.announce
    = This is Maeve

Which means a script can have methods.

    | announce = {
    |   IO.print {This is }.concat {Vern}
    | }.method
    | announce
    = This is Vern

Note that, unlike Ruby, this method is actually defined on the script.
(When a method is defined at the toplevel in Ruby, it is actually placed
in `Kernel`, and placed on `Object` as a private method.  There is probably
some historical or byzantine architectural reason for this, but it struck me
as quite bizarre when I learned about it.)

So, yeah, `method` is a method on strings too, just like `create`.  It takes
no arguments.

But, methods can have arguments, when called.  In their definition, the
first argument is referred to by `#1`, the second by `#2`, etc.

    | announce = {
    |   IO.print {This is }.concat #1
    | }.method
    | announce {Raina}
    = This is Raina

The block used to define a method is, of course, just a string.

    | a = {IO.print {This is }.concat #1}
    | announce = a.method
    | announce {Naoko}
    = This is Naoko

A method may be recursive.

    | count = {
    |   temp = #1
    |   if (temp.equals {XXXXXX}), { IO.print {Done!}}, {
    |     IO.print temp
    |     count temp.concat {X}
    |   }
    | }.method
    | count {X}
    = X
    = XX
    = XXX
    = XXXX
    = XXXXX
    = Done!

Note, however, that a method is not a Velo object, at least not in this
early version of Velo.  The only operation that is defined on the
result of calling the `method` method on a string, is assigning it to
an attribute of an object, from whence it can be called.  Trying to
do anything else to it (pass it to another method, for example) is not
defined.

Instantiation
-------------

Classes can be instantiated.  The most straightforward way to do this
is to use the `new` method we already discussed; in truth, it takes an
optional argument, and if this is given, the new object extends the object
passed to `new`.

    | Jonkers = {
    |   announce = {
    |     IO.print {This is }.concat #1
    |   }.method
    | }.create new
    | j = new Jonkers
    | j.announce {Jamil}
    | k = new Jonkers
    | k.announce {Brian}
    = This is Jamil
    = This is Brian

This usage of `new` is just a shortcut, because you can always `extend`
the new object yourself.

    | Jonkers = {
    |   announce = {
    |     IO.print {This is }.concat #1
    |   }.method
    | }.create new
    | j = new; j.extend Jonkers
    | j.announce {Jamil}
    = This is Jamil

Instances of classes have their own attributes, but obtain anything
they might be missing, from the class.

    | Jonkers = {
    |   name = {Cheryl}
    |   announce = {
    |     IO.print {This is }.concat name
    |   }.method
    | }.create new
    | 
    | j = new Jonkers
    | j.announce
    | k = new Jonkers
    | { name = {David} }.create k
    | k.announce
    = This is Cheryl
    = This is David

We said `{ name = {David} }.create k` above because, when the test was
written, we couldn't say simply `k.name = {David}` yet.  Now we can, so
let's try that:

    | Jonkers = {
    |   name = {James}
    |   announce = {
    |     IO.print {This is }.concat name
    |   }.method
    | }.create new
    | 
    | j = new Jonkers
    | j.announce
    | k = new Jonkers
    | k.name = {Joyce}
    | k.announce
    = This is James
    = This is Joyce

Given what you see above, you might be wondering exactly the difference
between classes and objects is.  Well...

Delegation
----------

We've been talking about classes as if they were a distinct language
construct, but really, a class is just a relationship between objects.

Velo uses prototype-based object-orientation.  Each object has a list
of parent objects; these are its classes.  But they're just objects.

When a method is called on an object, if that method is not defined
on that object, its parent objects are checked for that method; if any
are found, that method on the parent is called, but with the target
object as "self".

    | Jonkers = {
    |   extend IO
    |   announce = {
    |     print {This is }.concat #1
    |   }.method
    | }.create new
    | Jeepers = {
    |   extend IO
    |   greet = {
    |     print {Hello, }.concat #1
    |   }.method
    | }.create new
    | Jeepers.extend Jonkers
    | 
    | j = new Jeepers
    | j.announce {Luke}
    | j.greet {Luke}
    = This is Luke
    = Hello, Luke

(We had to say `Jeepers.extend Jonkers` in the above, instead of saying
`extend Jonkers` in the definition of Jeepers, because inside that
definition, Jonkers was not in scope.  This would be a nice thing to fix...)

When you say `extend`, you are just adding another object to the list
of parent objects for a class.

If a method is not found on an object, nor any of its parent objects,
it is looked for on the built-in object `Object`.  (If it is not there
either, an exception is thrown.)

In fact, `extend` is itself a method on `Object`.  When it is executed,
it evaluates its string parameter to obtain an object, and adds that
object to the list of parent objects of the current object.

Since scripts are no different from classes, a script can `extend`
a class that it defines:

    | Jonkers = {
    |   extend IO
    |   announce = {
    |     print {This is }.concat #1
    |   }.method
    | }.create new
    | extend Jonkers
    | announce {Ike}
    = This is Ike

The class doesn't even have to be given a name.

    | extend {extend IO; p = {print #1}.method}.create new
    | p {Hello!}
    = Hello!

Multiple Inheritance
--------------------

Because `extend` can be called as many times as you like on an
object, an object can inherit from (delegate to) as many classes
as you like.

For multiple inheritance, the method resolution order follows the
source code order; the objects added as parent objects by more
recently executed `extend`s are searched before those added by
earlier executed `extend`s.

    | Jonkers = {
    |   foo = { IO.print {fourteen} }.method
    | }.create new
    | Jeepers = {
    |   foo = { IO.print {twenty-nine} }.method
    | }.create new
    | 
    | Jeskers = {
    |   bar = { foo }.method
    | }.create new
    | Jeskers.extend Jonkers
    | Jeskers.extend Jeepers
    | 
    | j = new Jeskers; j.bar
    | 
    | Jofters = {
    |   bar = { foo }.method
    | }.create new
    | Jofters.extend Jeepers
    | Jofters.extend Jonkers
    | 
    | j = new Jofters; j.bar
    = twenty-nine
    = fourteen

`self`
------

As you've noticed, Velo has an "implicit self" -- invoking a method
just invokes it on the current self (which may be the script.)
But sometimes you need to explicitly refer to self, for example, to
pass it to some other method.

For this purpose, the `Object` object provides the method `self`
which simply returns the object it is called on.  Since all objects
effectively "inherit" (read: delegate to, when all other options
are exhausted) from `Object`, they can all use this "explicit self".

    | a = {X}
    | IO.print a.equals(a.self)
    = true

    | McTavish = {
    |   bar = { a = #1; a.hey }.method
    | }.create new
    | Jeskers = {
    |   bar = { a = #1; a.bar self }.method
    |   hey = { IO.print {Hey!} }.method
    | }.create new
    | Jeskers.bar McTavish
    = Hey!

Appendix
========

(This is all very slapdash right now.)

Summary of methods on `Object`
------------------------------

*   `extend` STRING
*   `self`
*   `new` ...
*   `Object`, `String`, `IO`, and all other predefined classes

Summary of methods on `String`
------------------------------

*   `if` STRING, STRING
*   `while` STRING
*   `create`
*   `method` STRING
*   `concat` STRING
*   `eval` STRING

Summary of methods on `IO`
--------------------------

*   `print` STRING
*   `input`

Grammar
-------

    Velo ::= {[EOL] Expr EOL}.
    Expr ::= Name "=" [EOL] Expr
           | Expr {"." [EOL] Name} [Expr {"," [EOL] Expr}]
           | Name
           | "(" [EOL] Expr ")"
           | StringLiteral
           .

Future Work
-----------

*   Unify scripts and strings.  (A script is just a string, after all.)
*   Unify methods and scripts.  (A method is just a script, after all.)
*   Unify scripts and modules.  (A module is just a script, after all.)
    Unfortunately, the Falderal format doesn't make this easy to
    illustrate; but there is no reason that we shouldn't be able to
    load external files as objects.