rbandrews: (Default)
[personal profile] rbandrews

Saturday evening I started making a language.

I had a working lexer (better: a lexer generator) by the time I went to sleep, and I knocked together a parser on Sunday. I added some more advanced features to it tonight, and it's to a point where it can be played with, or at least described.

Funny thing is, I had no real plan going in, and even less of one now. I was just noodling around; I don't even have a use for a new language. Both projects that I am working on will be perfectly fine with JSON/Script.

Keemun

The language is used to walk a tree of objects. You don't really write programs in it so much as you write paths. I was thinking of it more as a messaging protocol than a language, so it's designed around the idea of short single statements.

There is currently no way to build an object tree in-language, you have to build it in Script and then use Keemun to query it. Queries can have side-effects, also determined by the structure of the tree in Script.

Basics

Trees are made up of nodes. Each node has a key and a value. Keys are always strings. Paths are made up of dot-separated key names:

players.1.name

Key names can also have funny characters or multiple words in them if you quote them:

journalists.'Robert McX'.network

Internally in the object tree, some nodes can be functions. If a path reaches a function, then that function gets called with the rest of the path as parameters:

board.move.g1.f3

is equivalent to (in Script)

board['move']('g1','f3');

There are three other things to learn; subexpressions, lists (two ways to use them), and a special hook for dynamic object structuring.

Subexpressions

I said that paths can contain things other than keys. One possible thing is a subexpression:

players.send.(players.find.Joe).'Smack talk!'

The part in parentheses will be evaluated first, in the same environment as the outer expression, and its value will be plugged into that expression. If players.find.Joe evaluates to 3, say, then the outer expression players.send.3.'Smack talk!' gets evaluated. Subexpressions can be nested, of course.

Subexpressions are evaluated before function calls! players.send gets '3' and 'Smack talk!' as parameters, not the subexpression.

Lists

Lists are like subexpressions, but they use brackets ([ ]) instead of parens. Lists, unlike subexpressions, are not evaluated before function calls, they are evaluated like any other key. This means that one possible use for lists is to logically group function parameters:

people.find.[[name.'Callahan, Gary'].[occupation.'Senator']]

This is useful because it means you can easily have a function tell its arguments apart from the rest of the path (the first argument, in a list, is the function arguments, the rest something else). So, your function can return a value by recursively calling keemunEval:

function findFeed(criteria,_args_){
     var Feed=findWithCriteria(criteria);
     return keemunEval( unit, cdr(arguments) ); // first arg is environment, second is expression
}

Something like that would make this work:

findFeed.[name.'The Hole'].posts.latest

The posts.latest won't be evaluated until after the findFeed, and it will be evaluated on what findFeed wants it to.

Lists in paths

What happens if you give a list in a path and it's not evaluated by a function? Well, in that case, it splits the evaluation, and returns a list. This is a little hard to explain without an example, so imagine the object tree is this:

{
   x: {
         1: 'a',
	 2: 'b'
      },
   y: {
         1: 'c',
         2: 'd'
      }
};

Then, here are some expressions with lists:

[x.y].1 -> a, c
x.[2.1] -> b, a
[y.x].[1.2] -> c, d, a, b

Neat, huh? Of course, functions can return lists (they're normal Script arrays) and cause keemunEval to be called on them, so you can pretty quickly build up interesting constructions.

method_missing

There is one special key in every object, called ?. This key is a function that, if it exists, is called when you reference a member that doesn't exist (think Ruby's method_missing). It receives the entire rest of the path as arguments (including the missing element) so it can do what it likes with them, from print out an error message (boooring) to something like this:

function selfBuildingObject(){
    return {
       '?':function(name,_args_){
              this[name]=selfBuildingObject(); 
 
              if(arguments.length===1){
                 return null; // Stop here, no further to build
              }else{
                  keemunEval( this[name], cdr(arguments));
              }
         }
    };
}

With an object made with this, anything you reference will magically appear. You can use it to quickly build up an object tree, or to map out what some program expects your object tree to be, or whatever.

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

Profile

rbandrews: (Default)
rbandrews

July 2024

S M T W T F S
 123456
78910111213
14151617181920
212223242526 27
28293031   

Style Credit

Page generated Jun. 25th, 2025 10:29 pm
Powered by Dreamwidth Studios

Expand Cut Tags

No cut tags