summaryrefslogtreecommitdiffstats
path: root/examples/lisp-esque
blob: 14042aa60e2338d13f2dbabb3bc2adb3873e4876 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# -*- 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’.


# 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)