summaryrefslogblamecommitdiffstats
path: root/examples/lisp-esque
blob: 6c9dc2ef9e3e708d224989e50be0211cf43dc3b3 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                                                















                                                                             

                                                                                    
                                           


































































































                                                          
# -*- python -*-

# This example reads an lisp-esque syntax configuration
# file to make it easier for non-programmers to use
# Blueshift. It will read a file with the same pathname
# just with ‘.conf’ appended (‘lisp-esque.conf’ in this
# case.) However, if the filename of this file ends with
# with ‘rc’, that part will be removed, for example, if
# you rename this script to ‘~/.blueshiftrc’ it will read
# ‘~/.blueshift.conf’ rather than ‘~/.blueshiftrc.conf’.


# Copyright © 2014  Mattias Andrée (maandree@member.fsf.org)
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


# Get the name of .conf file
conf = '%s.conf' % (config_file[:-2] if config_file.endswith('rc') else config_file)
# TODO it should be possible to change file


# Read .conf file
with open(conf, 'r') as file:
    conf = file.read()


def abort(text, returncode = 1):
    '''
    Abort the program
    
    @param   text:str        Error message
    @return  returncode:int  The programs return code
    '''
    printerr('\033[01;31m%s\033[00m' % text)
    sys.exit(returncode)


def parse(code):
    '''
    Parse the .conf file and return it as a tree
    
    @param   data:str      The .conf file content to parse
    @return  :list<↑|str>  The root node in the tree
    '''
    stack, stackptr = [], -1
    comment, escape, quote, buf = False, False, None, None
    col, char, line = 0, 0, 1
    
    for c in range(data):
        if comment:
            if c in '\n\r\f':
                comment = False
        elif escape:
            escape = False
            if   c == 'a':  buf += '\a'
            elif c == 'b':  buf += chr(8)
            elif c == 'e':  buf += '\033'
            elif c == 'f':  buf += '\f'
            elif c == 'n':  buf += '\n'
            elif c == 'r':  buf += '\r'
            elif c == 't':  buf += '\t'
            elif c == 'v':  buf += chr(11)
            elif c == '0':  buf += '\0'
            else:
                buf += c
        elif c == quote:
            quote = None
        elif (c in ';#') and (quote is None):
            if buf is not None:
                stack[stackptr].append(buf)
                buf = None
            comment = True
        elif (c == '(') and (quote is None):
            if buf is not None:
                stack[stackptr].append(buf)
                buf = None
            stackptr += 1
            if stackptr == len(stack):
                stack.append([])
            else:
                stack[stackptr] = []
        elif (c == ')') and (quote is None):
            if buf is not None:
                stack[stackptr].append(buf)
                buf = None
            if stackptr == 0:
                return stack[0]
            stackptr -= 1
            stack[stackptr].append(stack[stackptr + 1])
        elif (c in ' \t\n\r\f') and (quote is None):
            if buf is not None:
                stack[stackptr].append(buf)
                buf = None
        else:
            if buf is None:
                buf = ''
            if c == '\\':
                escape = True
            elif (c in '\'\"') and (quote is None):
                quote = c
            else:
                buf += c
        
        if c == '\t':
            col |= 7
        col += 1
        char += 1
        if c in '\n\r\f':
            line += 1
            col = 0
            char = 0
    
    abort('premature end of file')


# Parse .conf file
conf = parse(conf)