OSDN Git Service

Fixes #40352 write() argument must be str, not bytes
[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 #TODO: explain the details of float lits and strings.
44 FLOAT = r'-?\d+\.\d*'
45 INT = r'-?\d+'
46 SYMBOL = r'[•\w!@$%^&*()_+<>?|\/;:`~,.=-]+'
47 BRACKETS = r'\[|\]'
48 STRING_DOUBLE_QUOTED = r'"(?:[^"\\]|\\.)*"'
49 STRING_SINGLE_QUOTED = r"'(?:[^'\\]|\\.)*'"
50 BLANKS = r'\s+'
51
52
53 class Symbol(str):
54   '''A string class that represents Joy function names.'''
55   __repr__ = str.__str__
56
57
58 def text_to_expression(text):
59   '''Convert a string to a Joy expression.
60
61   When supplied with a string this function returns a Python datastructure
62   that represents the Joy datastructure described by the text expression.
63   Any unbalanced square brackets will raise a ParseError.
64
65   :param str text: Text to convert.
66   :rtype: stack
67   :raises ParseError: if the parse fails.
68   '''
69   return _parse(_tokenize(text))
70
71
72 class ParseError(ValueError):
73   '''Raised when there is a error while parsing text.'''
74
75
76 def _tokenize(text):
77   '''Convert a text into a stream of tokens.
78
79   Converts function names to Symbols.
80
81   Raise ParseError (with some of the failing text) if the scan fails.
82   '''
83   tokens, rest = _scanner.scan(text)
84   if rest:
85     raise ParseError(
86       'Scan failed at position %i, %r'
87       % (len(text) - len(rest), rest[:10])
88       )
89   return tokens
90
91
92 def _parse(tokens):
93   '''
94   Return a stack/list expression of the tokens.
95   '''
96   frame = []
97   stack = []
98   for tok in tokens:
99     if tok == '[':
100       stack.append(frame)
101       frame = []
102       stack[-1].append(frame)
103     elif tok == ']':
104       try:
105         frame = stack.pop()
106       except IndexError:
107         raise ParseError('Extra closing bracket.')
108       frame[-1] = list_to_stack(frame[-1])
109     else:
110       frame.append(tok)
111   if stack:
112     raise ParseError('Unclosed bracket.')
113   return list_to_stack(frame)
114
115
116 _scanner = Scanner([
117   (FLOAT, lambda _, token: float(token)),
118   (INT, lambda _, token: int(token)),
119   (SYMBOL, lambda _, token: Symbol(token)),
120   (BRACKETS, lambda _, token: token),
121   (STRING_DOUBLE_QUOTED, lambda _, token: token[1:-1].replace('\\"', '"')),
122   (STRING_SINGLE_QUOTED, lambda _, token: token[1:-1].replace("\\'", "'")),
123   (BLANKS, None),
124   ])