TILInterpreter Using TXL

Software Transformation Systems
TXL solution to TIL Chairmarks #5.1: A complete Tiny Imperative Language interpreter implemented as a standalone TXL source transformation. No other libraries or support modules are required, the entire interpreter is here.

-- JamesCordy - 10 Oct 2005

File "til.Txl"

Notes:

(1) Because the transformation is named "til.Txl", TXL will infer that it is the default transformation for source files named ending in ".til". This is convenient for an interpreter!

(2) Because it's small, we've put all the source code in one file. Normally a real interpreter would have the VM module, statement module and expression module separated into include files.

% Tiny Imperative Language interpreter
% Jim Cordy, August 2005

% This program is a simple interpreter that directly executes TIL programs
% by transforming them to their meaning statement-by-statement.

% Some details not well defined in the TIL grammar are assumed:
%
% - It is not clear whether TIL is scoped or not.  This interpreter assumes
%   all declarations are in one scope.  The change to traditional nested 
%   scoping, if required, is trivial.
%
% - The comment for the "for" statement in the grammar says it is a 
%   "declaring for".  We assume this means the for automatically declares 
%   its control variable, with the same scope as a variable declaration.
%
% - As a consequence of the above, it is assumed that redeclaration is not
%   an error, rather simply uninitializes the variable.
%
% - It is not clear whether TIL write statements output a whole line or a 
%   partial line.  We assume whole.
%
% All these decisions are easy to change.

% Begin with the TIL base grammar, with priority parse
#define PRIORITY
include "TIL.Grm"

% Marker for detecting uninterpretable statements
redefine statement
        ...
    |  'null;
end redefine

% VM (virtual machine) memory cells 
define memory_cell
    [id] [literal]
end define

% Execute the program statement by statement
function main
    % The VM memory
    export Memory [memory_cell*]
        _ % Initially empty
    replace [program]
        Statements [statement*]
    by
        Statements [executeStatements]
end function

% Consume each statement we execute
rule executeStatements
    replace [statement*]
        S [statement]
        Rest [statement*]
    construct Execution [statement]
        S [executeStatement]
    by
        Rest 
end rule

% It must be one of the statement forms we know (all of those in TIL).
% Each statement interpreted is transformed into the special statement 
% "null;" so that we can tell when interpretation of a statement fails 
% for some reason and give an error message
function executeStatement
    replace [statement]
        S [statement]
    by
        S [executeDeclaration]
          [executeAssignment]
          [executeIf]
          [executeWhile]
          [executeFor]
          [executeRead]
          [executeWrite]
          [failure]
end function

% If the statement was not transformed to "null;" by one of the 
% statement interpretations, then we were unable to interpret it 
% so give a message and halt
function failure
    match [statement]
        S [statement]
    deconstruct not S
        'null;
    construct ErrorMessage [stringlit]
        _ [+ "*** Error: unable to interpret statement: '"]
          [quote S] [+ "'"] 
          [print] 
          [quit 101]
end function

% Interpret a declaration statement by adding an entry for the variable 
% to the VM memory.  If a previous one of the same name is already in 
% the memory, remove it
function executeDeclaration
    replace [statement]
        'var Id [id] ;
    import Memory [memory_cell*]
    export Memory
        Id "--UNDEFINED--"
        Memory [removeOldDeclaration Id]
    by
        'null;
end function

function removeOldDeclaration Id [id]
    replace * [memory_cell*]
        Id _ [literal]
        RestOfMemory [memory_cell*]
    by
        RestOfMemory
end function

% Interpret an assignment statement by evaluating the expression and
% setting the VM memory cell for the variable to that value
function executeAssignment
    replace [statement]
        Id [id] := Expn [expression] ;
    construct ExpnValue [expression]
        Expn [evaluateExpression]
    deconstruct ExpnValue
        Value [literal]
    import Memory [memory_cell*]
    export Memory
        Memory [checkDefined Id]
               [setValue Id Value]
    by
        'null;
end function

% Interpret an if statement by evaluating the expression and
% executing the appropriate branch
function executeIf
    replace [statement]
        'if Expn [expression] 'then
            S [statement*]
        OptElse [opt else_statement]
        'end
    construct ExpnValue [expression]
        Expn [evaluateExpression]
    deconstruct ExpnValue
        Value [literal]
    construct TruePart [statement*]
        S [executeThen Value]
    construct FalsePart [opt else_statement]
        OptElse [executeElse Value]
    by
        'null;
end function

function executeThen Value [literal]
    deconstruct not Value
        0
    replace [statement*]
        S [statement*]
    by
        S [executeStatements]
end function

function executeElse Value [literal]
    deconstruct Value
        0
    replace [opt else_statement]
        'else
            S [statement*]
    by
        'else
            S [executeStatements]
end function

% Interpret a while statement by transforming to its recursive meaning, 
% that is:  while E do S end  =>  if E then S while E do S end end
function executeWhile
    replace [statement]
        WhileStatement [statement]
    deconstruct WhileStatement
        'while Expn [expression] 'do
            S [statement*]
        'end
    construct IfStatement [statement]
        'if Expn 'then
            S [. WhileStatement]
        'end
    by
        IfStatement [executeStatement]
end function

% Interpret a for statement by transforming to its while meaning, that is:
% for I := E1 to E2 do S end  =>  
% var I; I := E1; var I_Upper; I_Upper := E2; while I - I_Upper do S end
function executeFor
    replace [statement]
        'for Id [id] := E1 [expression] 'to E2 [expression] 'do
            S [statement*]
        'end
    construct UpperId [id]
        Id [_ 'Upper] [!]
    construct InitialStatements [statement*]
        'var Id;
        Id := E1;
        'var UpperId;
        UpperId := (E2) + 1;
    construct IterateStatement [statement]
        Id := Id + 1;
    construct WhileStatement [statement]
        'while Id - UpperId 'do
            S [. IterateStatement]
        'end
    construct Initialize [statement*]
        InitialStatements [executeStatements]
    by
        WhileStatement [executeStatement]
end function

% Interpret a read statement by reading an input value and transforming
% to an assignment to the variable
function executeRead
    replace [statement]
        'read Id [id] ;
    construct Input [opt literal]
        _ [getp "read: "]
    deconstruct Input 
        Value [literal]
    construct Assignment [statement]
        Id := Value;
    by
        Assignment [executeStatement]
end function

% Interpret a write statement by evaluating the expression and outputting
% it to the terminal
% Note: not clear whether items are output as a whole a line (assume yes)
function executeWrite
    replace [statement]
        'write Expn [expression] ;
    construct ExpnValue [expression]
        Expn [evaluateExpression]
             [writeString]
             [writeInteger]
    by
        'null;
end function

function writeString
    match [expression]
        S [stringlit]
    construct Output [stringlit]
        S [print]
end function

function writeInteger
    match [expression]
        I [integernumber]
    construct Output [integernumber]
        I [print]
end function

% Utility functions for the VM
function checkDefined Id [id]
    match [memory_cell*]
        Memory [memory_cell*]
    deconstruct not * [id] Memory
        Id 
    construct ErrorMessage [stringlit]
        _ [+ "*** Error: undeclared variable : '"]
          [quote Id] [+ "'"] 
          [print] 
          [quit 102]
end function

function setValue Id [id] Value [literal]
    replace * [memory_cell]
        Id _ [literal]
    by
        Id Value
end function

% Expression evaluation
rule evaluateExpression
    replace [expression]
        E [expression]
    construct NewE [expression]
        E [evaluateParens]
          [evaluatePrimaries]
          [evaluateMultiplications]
          [evaluateDivisions]
          [evaluateAdditions]
          [evaluateSubtractions]
          [evaluateEquals]
          [evaluateNotEquals]
    deconstruct not NewE
        E
    by
        NewE
end rule

rule evaluateParens
    replace [primary]
        ( Expn [expression] )
    construct ExpnValue [expression]
        Expn [evaluateExpression]
    deconstruct Expn
        Value [literal]
    by
        Value
end rule

rule evaluatePrimaries
    replace [primary]
        Id [id]
    import Memory [memory_cell*]
    construct Check [memory_cell*]
        Memory [checkDefined Id]
    deconstruct * [memory_cell] Memory
        Id Value [literal]
    by
        Value
end rule

rule evaluateEquals
    replace [expression]
        I1 [integernumber] = I2 [integernumber]
    by
        I1 [- I2] [toBoolean] [+ 1] [rem 2]
end rule

rule evaluateNotEquals
    replace [expression]
        I1 [integernumber] != I2 [integernumber]
    by
        I1 [- I2] [toBoolean]
end rule

function toBoolean
    replace [integernumber]
        N [integernumber]
    deconstruct not N
        0
    by
        1
end function

rule evaluateAdditions
    replace [term]
        I1 [integernumber] + I2 [integernumber]
    by
        I1 [+ I2]
end rule

rule evaluateSubtractions
    replace [term]
        I1 [integernumber] - I2 [integernumber]
    by
        I1 [- I2]
end rule

rule evaluateMultiplications
    replace [factor]
        I1 [integernumber] * I2 [integernumber]
    by
        I1 [* I2]
end rule

rule evaluateDivisions
    replace [factor]
        I1 [integernumber] / I2 [integernumber]
    by
        I1 [div I2]
end rule

Example run:

<linux> cat factors.til
// Factor an input number
var n;
write "Input n please";
read n;
write "The factors of n are";
var f;
f := 2;
while n != 1 do
    while (n / f) * f = n do
        write f;
        n := n / f;
    end
    f := f + 1;
end
<linux> txl factors.til
TXL v10.4a (15.6.05) (c)1988-2005 Queen's University at Kingston
Compiling til.Txl ... 
Parsing factors.til ...
Transforming ...
Input n please
read: 76
The factors of n are
2
2
19
<linux>