-
-
Save thomasmf/031db24f0e63a3459e62 to your computer and use it in GitHub Desktop.
TML
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python | |
manual = """ | |
TML | |
Copyright Thomas Mork Farrelly, 2015 | |
License: MIT | |
-------------------------------------------------------------------------------- | |
User Manual | |
----------- | |
TML is tokenized by splitting on whitespace. Whitespace between words and | |
symbols are therefore required. | |
Syntax: [ word definition ] | |
Associates 'word' with 'definition' in the current context. | |
A context in TML is a dictionary of words associated with their definitions. | |
A context may inherit a parent context. | |
If a context, upon request for the definition of a word, fails to find that | |
word in its own dictionary, it will forward the request to its parent context. | |
Syntax: word | |
Causes the definition of 'word' to be evaluated in the current context. | |
Syntax: < expression > | |
Outputs 'expression'. | |
Syntax: { | |
Outputs '<'. | |
Syntax: } | |
Outputs '>'. | |
Notice that '{' and '}' are essentially escaped quotes. | |
Syntax: ( expression ) | |
First evaluate expression in a new context. | |
The parent context of the new context is the old context. Parenthesized | |
expressions function similarly to code blocks in most other languages. TML has | |
dynamic scope. | |
When evaluation of the expression is finished the created context is discarded. | |
Lastly the output of the expression is evaluated in the old context. | |
-------------------------------------------------------------------------------- | |
Examples | |
-------- | |
Quine: | |
[ q < < [ q > { q } < ] ( q ) > > ] ( q ) | |
Hello world: | |
< Hello world !!! > | |
-------------------------------------------------------------------------------- | |
Usage | |
----- | |
This interpreter uses a working directory during evaluation. The working | |
directory is the root context. Filenames are words and the definitions are the | |
contents of the files. It is read only. This is useful for working with | |
libraries. | |
Show this message and complain: | |
tml | |
Start in interactive mode in a work directory: | |
rlwrap tml work_directory | |
Evaluate script: | |
tml script | |
When evaluating a script, the scripts directory is the working directory. | |
Most script errors are handled badly or not at all. Prepare to see python | |
tracebacks. | |
-------------------------------------------------------------------------------- | |
""" | |
import os | |
import sys | |
def error( string ) : | |
print 'Error: ' + string + "." | |
exit( 0 ) | |
def warning( string ) : | |
print 'Warning: ' + string + "." | |
def lex( string ) : | |
return string.split() | |
def read_file( filename ) : | |
value = '' | |
try : | |
value = open( filename ).read() | |
except IOError : | |
warning( 'File \'' + filename + '\' not found' ) | |
return lex( value ) | |
# | |
# Frame | |
# | |
class Frame ( object ) : | |
def __init__( self, parent = None ) : | |
self.parent = parent | |
self.definitions = {} | |
def __getitem__( self, key ) : | |
if key in self.definitions : | |
return self.definitions[ key ] | |
else : | |
return self.parent[ key ] | |
def __setitem__( self, key, value ) : | |
self.definitions[ key ] = value | |
class RootFrame ( Frame ) : | |
def __init__( self ) : | |
pass | |
def __getitem__( self, key ) : | |
return read_file( working_directory + '/' + key ) | |
# | |
# Phrase | |
# | |
class Phrase ( object ) : | |
def __init__( self, tokens ) : | |
self.tokens = tokens + [ ')' ] | |
def next( self ) : | |
t = self.tokens[ 0 ] | |
self.tokens = self.tokens[ 1 : ] | |
return t | |
def evaluate( self, frame ) : | |
output = [] | |
self.consume_expression( frame, output ) | |
return output | |
def consume_definition ( self, frame ) : | |
name = self.next() | |
output = [] | |
balance = 1 | |
send_balance = 0 | |
while balance != 0 : | |
t = self.next() | |
if send_balance == 0 : | |
if t == ']' : | |
balance -= 1 | |
elif t == '[' : | |
balance += 1 | |
if t == '<' : | |
send_balance += 1 | |
elif t == '>' : | |
send_balance -= 1 | |
if balance != 0 : | |
output.append( t ) | |
frame[ name ] = output | |
def consume_send ( self, output ) : | |
balance = 1 | |
while balance != 0 : | |
t = self.next() | |
if t == '>' : | |
balance -= 1 | |
elif t == '<' : | |
balance += 1 | |
if balance != 0 : | |
output.append( t ) | |
def consume_expression ( self, frame, output ) : | |
while len( self.tokens ) > 0 : | |
t = self.next() | |
if t == ')' : | |
return | |
elif t == '(' : | |
expression_output = [] | |
self.consume_expression( Frame( frame ), expression_output ) | |
Phrase( expression_output ).consume_expression( frame, output ) | |
elif t == '<' : | |
self.consume_send( output ) | |
elif t == '[' : | |
self.consume_definition( frame ) | |
elif t == '{' : | |
output.append( '<' ) | |
elif t == '}' : | |
output.append( '>' ) | |
else : | |
Phrase( frame[ t ] ).consume_expression( frame, output ) | |
error( 'unexpected end of expression' ) | |
# | |
# Main | |
# | |
if len( sys.argv ) == 1 : | |
print manual | |
error( 'No working directory or scriptfile specified' ) | |
frame = Frame( RootFrame() ) | |
filename = os.path.realpath( sys.argv[ 1 ] ) | |
if os.path.isdir( filename ) : | |
working_directory = filename | |
print 'Working directory:', working_directory | |
while True : | |
sys.stdout.write( '> ' ) | |
value = ' '.join( Phrase( lex( sys.stdin.readline() ) ).evaluate( frame ) ) | |
if value != '' : | |
print value | |
else : | |
working_directory = os.path.dirname( os.path.realpath( filename ) ) | |
print ' '.join( Phrase( read_file( filename ) ).evaluate( frame ) ) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment