Skip to content

Instantly share code, notes, and snippets.

@thomasmf

thomasmf/tml Secret

Last active March 13, 2016 19:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomasmf/031db24f0e63a3459e62 to your computer and use it in GitHub Desktop.
Save thomasmf/031db24f0e63a3459e62 to your computer and use it in GitHub Desktop.
TML
#! /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