Tags
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
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.