Tags

, , , , ,

Here’s the last of the oddball Little Programming Languages (for now). This one is a little like LPL-2 in using a three-part syntax (which turns out to be non-ideal). Unlike LPL-3, neither of these are particularly usable languages — more along the lines of being something a language designer amused himself with on a rainy afternoon.

A key goal in LPL-1 was to minimize the use of punctuation characters. No brackets or parenthesis to create syntax blocks. (Square brackets for array indexing and parentheses for expressions, but that’s it.)

The three-part syntax was a fun idea, but it’s hard to make it orthogonal. Some language parts fit it so nicely (which is why it’s so tempting). Other parts, not so much.

For example, for basic loops, it fits perfectly:

while  loop-condition  loop-block
until  loop-condition  loop-block

And it works out okay for variable definition:

var  name  [ initial-value ]

The initializing value can be absent, but that’s not a syntax hole, that’s syntax information. An absent initial-value means no initialization, which is often semantically meaningful in a language.

On the other hand, if your language supports a lot of type information about variables, then you have to decide whether it belongs with the name or the keyword, or is the third part. (But what about an initializer?)

Already the three-part syntax is showing the strain! It fits a plain If-Then statement fine:

if  test-expression  then-block
elseif  test-expression  elseif-block
else  NULL  else-block

But the Else poses real problems. The Else-If-Then middle clause supported by some languages has a three-part form, so it works okay. On the other hand, both  Else clauses present the problem of being optional syntax of the If statement.

That’s not a huge issue, but it’s contrary to the whole point of the three-part syntax: that you should always be able to grab the three parts — what you grab shouldn’t require checking for optional trailing parts. In this case, for instance, grabbing an If object means having to check for a possible Else object.

A Switch-Case structure works okay and gets around the optional syntax issue if the Case statements are in the switch-body. but with the same optional syntax issue as If statements and with the same problem that the final Else clause is naturally only two-part:

switch  switch-expression  switch-block
case  case-expression  case-block
default  NULL  default-block

For loops tend to go from bad to worse — you have to cram all the loop control stuff in one part:

for  (obj in list)  loop-block
for  (x=1 to 10 step 2)  loop-block
for  (x=0; x < 10; x++)  loop-block

On the other hand, some statements are naturally just keywords (break, continue) and return takes only one (often optional) parameter.

Function definitions also don’t fit the model too well:

function  name-and-args  function-body
call  name  args
name  passed-objects  returned-objects

One option is to put the function interface (passed and returned arguments) in the function-body part. If you decide defining a function creates a new keyword, you have to figure out how to handle the other two parts. A trick that does work kind of slick — the third example above — is using the two parts to, respectively, send and receive function arguments.

So, bottom line, three-part syntax… not so great. But it was worth a shot.

You’ll see how the problems with it make this language as goofy as the last one, but for whatever it was worth, here is:

Little Programming Language — 1

The single (three-part) syntax is:

statement := keyword target-list : value-list ;

As a general rule, the value-list provides a set of values for the target-list. Both are optional, but usually at least one is present (as the “three-parter problem” raises its head).

We declare variables (new objects) in a way that’s similar to LPL-2, but works the reverse. Here the variable names come first (which does seem more natural):

var  ix: 0;
var  txt: "Hello, World!"
var  x,y,z: 0;
var  a,b,c: 1,2,3;
var  a,b,c,x,y,z: 1,2,3,0;

Also as with LPL-2 the set keyword works the same way as var but requires the variable already exist. (And var throws an exception if it does exist.)

There is also a constant keyword for creating non-mutable objects:

constant  $CR, $LF, $EOL
:   string:0x0D;
,   string:0x0A;
,   string:0x0D,0x0A;
;

This example shows that variable names may contain some special characters. It also shows an anonymous value declaration (three, actually). The string keyword used with a target-list of names, binds string values to those names. Here it just creates anonymous string values that get bound to the names in the constant keyword’s target-list.

The example also shows the idiomatic way to write the syntax when it’s desirable to break lines. (LPL-1 is completely white-space insensitive.) Putting the commas at the start, rather than the end, of lines makes them easier to see, and removes the problem of the last comma in the list.

A simple If-Then statement looks like this:

if 10 < x, 10 < y
:   set x: 0;
,   set y: 0;
;

Comma-separated expressions form an implicit AND. An else keyword can replace a comma in the body of the if (or the previous else):

if a == 2
:   set x,y: 0;
,   set a: b * 2;
,   set b: 0;
else a==1
:   set x,y: -1,-1;
,   set a,b: 0;
else
:   incr: a,b;
;

A single final semi-colon (;) closes the chain. An else keyword with a target-list acts like an Else-If.

Basic loops, as mentioned in the introduction, fit the paradigm:

while  loop-expression : loop-body ;
until  loop-expression : loop-body ;
break:;
continue:;

The (parameter-less) break and continue keywords work as expected. (Perhaps the target-list could be employed as a conditional. The value-list might be used as a label. I haven’t given the possibilities much thought, but the break and continue keywords are slightly problematic in some regards.)

For fun, why not a select statement that evaluates an expression for an index into a list of statements to perform:

select  ix
:   set a: -1;        -- if ix = 1
,   set a:  0;        -- if ix = 2
,   set a: +1;        -- if ix = 3
;

Here’s what the Switch-Case statement looks like:

switch  switch-expression : switch-block ;
case  case-expression : case-block ;

The switch-body is a list of case statements. A case statement without a target-list acts like a default case. Only one such is allowed (likewise, only one default else is allowed in an if block).

And, finally, define and use a new function like this:

function  Swap, a, b
:   var tmp: b;
,   set b: a;
,   set a: tmp;
;
Swap: x,y;

The first name in the target-list is bound to the new function object, the rest are the names of formal parameters. They are bound to passed values at run time. Why not a conditional return statement:

return test-expression : return-value(s) ;
return:;

(Ha! A conditional return — combine an If and a Return. That might actually be handy. While we’re at it, bring on the conditional break and continue, too. Those are often preceded by an If!)

And that pretty much wraps it up except for the Fibonacci example:

function Fib n
:   if n < 2
    :   return: n;
    ;
,   var a: Fib: n-1; ;
,   var b: Fib: n-2; ;
,   return: a+b;
;

The point of these three posts was partially to document my little language doodles (copyright, me! 😀 ) but also to introduce the topic of parsing and provide some interesting and different parsing examples.