Using Prolog as the AST

I’ve been toying with emitting Prolog from Coil and its been a ton of fun!

Here’s the basic idea:

// coil code
let ten = 10

// existing coil ast

[
  {
    type: "let",
    assign_expr: { type: "id_assign", name: "ten" },
    expr: { type: "num", value: 10 }
  }
]
% generated prolog ast

id_assign(ten, nid_3, nid_2).
let(nid_2, nid_1).
line_and_col(nid_2, 1, 1).
line_and_col(nid_3, 1, 5).
num(10, nid_2).

% lets ignore metadata give better
% names to the nids (node ids)

id_assign(ten, _id_assign_ctx, let_ctx).
let(let_ctx, _top_level_ctx).
num(10, let_ctx).

The primary difference between these 2 approaches is that with prolog the relationships are (easily) preserved.

For example when I’m traversing the AST when I’m looking at the node { type: "num", value: 10 }, without doing extra work to go back up the tree I have no way to tell that it belongs to the name “ten”.

Ok, cool! But what can we do with that?

Queries

Prolog has a wonderful query system.

Here are some queries

// coil code
let ten = 10
let twenty = 20
% give me the name of the variable
% who has the value 10
?- id_assign(Name, _, Let), num(10, Let).
% Output:
Name = ten,
Let = nid_2.

% Give me names for variables who's value is a number
?- id_assign(Name, _, Let), num(Val, Let).
% Output:

% first result
Name = ten,
Let = nid_2,
Val = 10 ;

% second result
Name = twenty,
Let = nid_4,
Val = 20.

Prolog lets you build up helper predicates to take care of more complex queries:

let [a b] = [1 2]
% extract id names into node context
value(Id, Out) :-
  id_assign(Id, _, Parent),
  value(Parent, Out).
% pull out values from array deconstruction
value(Id, Out) :-
  array_deconstruction(Id, Idx, Array),
  index_of(Array, Idx, Out).

?- value(a, Out).
Out = 1.

After building up the helper predicates I’ve been able to do the following:

let object =
  { :name => "marcelle"
    :age => 26
    :hobbies => [:code :guitar] }
% find all object keys for the variable "object"

?- id_assign(object, _, Let),
  object_literal(Object, Let),
  object_key(Object, Key).

% first result
Let = nid_11,
Object = nid_13,
Key = keyword(name) ;

% second result
Let = nid_11,
Object = nid_13,
Key = keyword(age) ;

% third result
Let = nid_11,
Object = nid_13,
Key = keyword(hobbies).
let list = [1 2]
let list = 2
% lets find all times a variable has been
% redefined to a different type

?- value(Name, First),
  value(Name, Second),
  type(First, FirstType),
  type(Second, SecondType),
  FirstType \= SecondType.

% Output:
Name = list,
First = [1, 2],
Second = 2,
FirstType = array,
SecondType = number.

Conclusion

So now what? Well, mostly I think its cool.

My long term hopes is to build an end-user linter system for coil using prolog, but that’s the future, for now its just fun :)