OSDN Git Service

Working on bug #15
[joypy/Thun.git] / joy / parser.py
1 # -*- coding: utf-8 -*-
2 #
3 #    Copyright © 2014, 2015, 2016, 2017 Simon Forman
4 #
5 #    This file is part of Thun.
6 #
7 #    Thun is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU General Public License as published by
9 #    the Free Software Foundation, either version 3 of the License, or
10 #    (at your option) any later version.
11 #
12 #    Thun is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU General Public License for more details.
16 #
17 #    You should have received a copy of the GNU General Public License
18 #    along with Thun.  If not see <http://www.gnu.org/licenses/>.
19 #
20 '''
21 This module exports a single function for converting text to a joy
22 expression as well as a single Symbol class and a single Exception type.
23
24 The Symbol string class is used by the interpreter to recognize literals
25 by the fact that they are not Symbol objects.
26
27 A crude grammar::
28
29     joy = term*
30     term = int | float | string | '[' joy ']' | symbol
31
32 A Joy expression is a sequence of zero or more terms.  A term is a
33 literal value (integer, float, string, or Joy expression) or a function
34 symbol.  Function symbols are unquoted strings and cannot contain square
35 brackets.   Terms must be separated by blanks, which can be omitted
36 around square brackets.
37
38 '''
39 from re import Scanner
40 from .utils.stack import list_to_stack
41
42
43 BRACKETS = r'\[|\]'
44 BLANKS = r'\s+'
45 WORDS = r'[^[\]\s]+'
46
47
48 token_scanner = Scanner([
49     (BRACKETS, lambda _, token: token),
50     (BLANKS, None),
51     (WORDS, lambda _, token: token),
52     ])
53
54
55 class Symbol(str):
56     '''A string class that represents Joy function names.'''
57     __repr__ = str.__str__
58
59
60 def text_to_expression(text):
61     '''Convert a string to a Joy expression.
62
63     When supplied with a string this function returns a Python datastructure
64     that represents the Joy datastructure described by the text expression.
65     Any unbalanced square brackets will raise a ParseError.
66
67     :param str text: Text to convert.
68     :rtype: stack
69     :raises ParseError: if the parse fails.
70     '''
71     return _parse(_tokenize(text))
72
73
74 class ParseError(ValueError):
75     '''Raised when there is a error while parsing text.'''
76
77
78 def _tokenize(text):
79     '''Convert a text into a stream of tokens.
80
81     Converts function names to Symbols.
82
83     Raise ParseError (with some of the failing text) if the scan fails.
84     '''
85     tokens, rest = token_scanner.scan(text)
86     if rest:
87         raise ParseError(
88             'Scan failed at position %i, %r'
89             % (len(text) - len(rest), rest[:10])
90             )
91     return tokens
92
93
94 def _parse(tokens):
95     '''
96     Return a stack/list expression of the tokens.
97     '''
98     frame = []
99     stack = []
100     for tok in tokens:
101         if tok == '[':
102             stack.append(frame)
103             frame = []
104         elif tok == ']':
105             v = frame
106             try: frame = stack.pop()
107             except IndexError:
108                 raise ParseError('Extra closing bracket.')
109             frame.append(list_to_stack(v))
110         elif tok == 'true':
111             frame.append(True)
112         elif tok == 'false':
113             frame.append(False)
114         else:
115             try:
116                 thing = int(tok)
117             except ValueError:
118                 thing = Symbol(tok)
119             frame.append(thing)
120     if stack:
121         raise ParseError('Unclosed bracket.')
122     return list_to_stack(frame)