computer language, language design, little programming language, LPL, programming language, syntax
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
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
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
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.
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
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
Basic loops, as mentioned in the introduction, fit the paradigm:
while loop-expression : loop-body ; until loop-expression : loop-body ; break:; continue:;
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
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
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
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.