Initial release.
[robmyers:dogecode.git] / dogecode / translation.py
1 # translation.py - Translate between Brainfuck commands and Dogeparty tokens
2 # Copyright (C) 2014 Rob Myers rob@robmyers.org
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or 
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 ################################################################################
18 # Imports
19 ################################################################################
20
21 import csv, re
22
23
24 ################################################################################
25 # Token translation and formatting
26 ################################################################################
27
28 # BF symbols to Dogeparty token names
29 SYMBOLS_TO_TOKENS = {'>':'INCP', '<':'DECP', '+':'INCB', '-':'DECB', '.':'PUTB',
30                      ',':'GETB', '[':'JFOR', ']':'JBAK'}
31
32 # Dogeparty token names to BF symbols
33 TOKENS_TO_SYMBOLS = {v: k for k, v in SYMBOLS_TO_TOKENS.items()}
34
35 TOKENS = TOKENS_TO_SYMBOLS.keys()
36
37 # BF symbols and nothing else
38 TOKENS_RE = re.compile(r'[^><+\-.,[\]]+')
39
40 # Runs of BF symbols
41 TOKEN_RUN_RE = re.compile(r'(>+|<+|\++|-+|\.+|,+|\[+|]+)')
42
43 # An initial comment
44 INITIAL_COMMENT = re.compile(r'^\[[^\]]+]')
45
46
47 ################################################################################
48 # Parse source code and convert to token amounts
49 ################################################################################
50
51 def strip_non_token_characters(source):
52     """Return a copy of the sourc with all whitespace, newlines, letters,
53        numbers, and anything other than token characters removed from the
54        source."""
55     return re.sub(TOKENS_RE, '', source)
56
57 def remove_initial_comment(source):
58     """Remove the first [...] block from the code, it's just a comment"""
59     return re.sub(INITIAL_COMMENT, '', source)
60
61 def run_to_token(run):
62     """Convert a run of characters to a tokem count pair"""
63     return (SYMBOLS_TO_TOKENS[run[0]],
64             len(run))
65
66 def code_to_token_runs(source):
67     """Convert program source to a list of token/run length pairs"""
68     runs = re.findall(TOKEN_RUN_RE, source)
69     return [run_to_token(run) for run in runs]
70
71 def source_to_tokens(source, strip_initial_comment):
72     """Clean the source code and represent the program as pairs of
73        token/counts"""
74     source = strip_non_token_characters(source)
75     if strip_initial_comment:
76         source = remove_initial_comment(source)
77     return code_to_token_runs(source)
78
79 def write_transactions_csv(token_pairs, csvfile):
80     writer = csv.writer(csvfile)
81     for pair in token_pairs:
82         writer.writerow((pair[0], pair[1]))
83
84 def source_to_tokens_csv(source, csvfile, strip_initial_comment=True):
85     token_pairs = source_to_tokens(source, strip_initial_comment)
86     write_transactions_csv(token_pairs, csvfile)
87
88
89 ################################################################################
90 # Convert token amounts to source code
91 ################################################################################
92
93 def token_to_run(token_pair):
94     """Convert a token/count tuple to a string containting count instances of
95        token"""
96     return TOKENS_TO_SYMBOLS[token_pair[0]] * token_pair[1]
97
98 def token_runs_to_code(token_pairs):
99     """Convert a sequence of token/count tuples to a string of operator
100        symbols"""
101     code = ''
102     for pair in token_pairs:
103         code += token_to_run(pair)
104     return code
105
106 def json_token_amounts_to_pairs(tokens_json):
107     """Extract a list of (TOKEN,N) pairs from the json list of token sends"""
108     pairs = []
109     for send in tokens_json:
110         if send["status"] == "valid":
111             pairs.append((send["asset"], send["quantity"]))
112     return pairs
113
114
115 ################################################################################
116 # Compare token lists
117 ################################################################################
118
119 class TokenList(object):
120     """A class to keep track of a list of tokens"""
121
122     def __init__(self):
123         """Create a new empty list"""
124         self.tokens = []
125
126     def append(self, token, count):
127         """Append the token count to the list"""
128         self.tokens.append((token, int(count)))
129
130     def from_json(self, json):
131         """Initialize the token list from a json rpc query response"""
132         for send in json:
133             self.append(send["asset"], send["quantity"])
134
135     def equals(self, other):
136         """Compare the token list with another token list.
137            Return True if the list is equal, false if not."""
138         result = True
139         num_tokens = len(self.tokens)
140         if num_tokens != len(other.tokens):
141             result = False
142         else:
143             for i in range(0, num_tokens):
144                 if self.tokens[i] != other.tokens[i]:
145                     result = False
146                     break
147         return result
148
149     def is_subsequence_of(self, other):
150         """Check whether this is a proper subsequence of other.
151            An equal list is not a proper subsequence."""
152         result = True
153         num_tokens = len(self.tokens)
154         if num_tokens >= len(other.tokens):
155             result = False
156         else:
157             for i in range(0, num_tokens):
158                 if self.tokens[i] != other.tokens[i]:
159                     result = False
160                     break
161         return result