Skip to content

Commit 2cbd30c

Browse files
authored
Merge pull request #10 from fakefloordiv/megacalc
Megacalc
2 parents edd2775 + d1512df commit 2cbd30c

File tree

13 files changed

+160
-75
lines changed

13 files changed

+160
-75
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[![wakatime](https://wakatime.com/badge/user/b4a2ea9a-a721-41c8-b704-79b9b8cec646/project/3e68a432-deec-4dab-ba8a-a5f424e91eed.svg)](https://wakatime.com/badge/user/b4a2ea9a-a721-41c8-b704-79b9b8cec646/project/3e68a432-deec-4dab-ba8a-a5f424e91eed)
22
# pycalc
3-
Simple calculator on python, written in academic purposes. Uses Sorting Station Algorithm for building reverse polish notation stack. Supports all kinds of operations python supports (except bool operations like or, not, etc. but they will be implemented as a functions of std-library), functions defining, variables declarations, etc.
3+
Simple calculator on python, written in academic purposes. TURING-COMPLETE. Uses Sorting Station Algorithm for building reverse polish notation stack. Supports all kinds of operations python supports (except bool operations like or, not, etc. but they will be implemented as a functions of std-library), functions defining, variables declarations, etc.
44

55
# How to install?
66
```bash

docs/stdlib.md

+28
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,34 @@ float(".5") == .5
7272

7373
---
7474

75+
## `str`
76+
Semantic:
77+
```
78+
str(15.5)
79+
```
80+
Returns: string
81+
82+
Examples:
83+
```
84+
str(.5) == "0.5"
85+
```
86+
87+
---
88+
89+
## `strjoin`
90+
Semantic:
91+
```
92+
strjoin(separator, mem)
93+
```
94+
Returns: single string
95+
96+
Examples:
97+
```
98+
strjoin(".", "123") == "1.2.3"
99+
```
100+
101+
---
102+
75103
## `range`
76104
Semantic:
77105
```

examples/arrays.calc

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ arrMake(size, bytesize) = malloc(size*bytesize)
88
arrGet(arr, index, bytesize) =
99
reduce(
1010
(x, y) = x + (y<<8),
11-
slice(arr, index*bytesize, bytesize)
11+
slice(arr, index*bytesize, index*bytesize+bytesize)
1212
)
1313

1414
arrSet(arr, index, bytesize, value) =

examples/breakinggates.calc

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
print("1: ")
2-
src = input()
3-
print("2: ")
4-
modified = input()
1+
src = input("1: ")
2+
modified = input("2: ")
53

6-
count(mem, char) =
4+
count(string, char) =
75
len(filter(
86
(x) = x==char,
9-
mem
7+
string
108
))
119

1210
extra = 0
@@ -15,4 +13,4 @@ map(
1513
if(count(src, x) != count(modified, x), () = extra=x),
1614
modified
1715
)
18-
println(chr(extra))
16+
println(extra)

examples/turingmachine.calc

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
rulesMap(func, rules) =
2+
map(
3+
(x) = func(slice(rules, x, x+5)),
4+
range(0, len(rules), 5)
5+
)
6+
7+
step(tape, rules, state, position) =
8+
char = get(tape, position);
9+
selectedRule = 0;
10+
rulesMap(
11+
(rule) = if(
12+
(get(rule, 0) == state) + (get(rule, 1) == char) == 2,
13+
() = selectedRule = rule
14+
),
15+
rules
16+
);
17+
set(tape, position, get(selectedRule, 3));
18+
moveHead = get(selectedRule, 4);
19+
branch(
20+
moveHead == 2,
21+
() = position = position + 1,
22+
moveHead == 1,
23+
() = position = position - 1,
24+
);
25+
mallocfor(get(selectedRule, 2), position)
26+
27+
28+
rules = mallocfor(
29+
1, ord("0"), 1, ord("1"), 2,
30+
1, ord("1"), 1, ord("0"), 2,
31+
1, ord("*"), 2, ord("*"), 1
32+
)
33+
tape = map(ord, "010011001*")
34+
println("tape was: ", strjoin("", map(chr, tape)))
35+
36+
state = 1
37+
position = 0
38+
while(
39+
() = state != 2,
40+
() =
41+
result = step(tape, rules, state, position);
42+
state = get(result, 0);
43+
position = get(result, 1)
44+
)
45+
46+
println("tape became: ", strjoin("", map(chr, tape)))

pycalc/interpreter/interpret.py

+13-11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from pycalc.tokentypes.tokens import Token, Tokens, Function
99
from pycalc.tokentypes.types import (TokenKind, TokenType, Stack, Namespace, Number,
1010
NamespaceValue, ArgumentsError, NameNotFoundError,
11-
InvalidSyntaxError, NoCodeError)
11+
InvalidSyntaxError, ExternalFunctionError,
12+
PyCalcError, NoCodeError)
1213

1314

1415
Value = Union[Number, Function]
@@ -107,7 +108,10 @@ def interpret(self, code: str, namespace: Namespace) -> Value:
107108
tokens = self.tokenizer.tokenize(code)
108109
stacks = self.stackbuilder.build(tokens)
109110
namespaces = NamespaceStack()
110-
namespaces.add_namespace(namespace)
111+
# empty namespace especially for global namespace
112+
# because default one must not be overridden by
113+
# global namespace of code
114+
namespaces.add_namespaces(namespace, {})
111115

112116
return self._interpreter(stacks, namespaces)
113117

@@ -164,14 +168,15 @@ def _interpret_line(self, expression: Stack[Token], namespaces: NamespaceStack)
164168
call_result = func(*(arg.value for arg in args))
165169
except ArgumentsError as exc:
166170
raise ArgumentsError(str(exc), token.pos) from None
171+
except PyCalcError as exc:
172+
raise exc from None
173+
except Exception as exc:
174+
raise ExternalFunctionError(str(exc), token.pos)
167175

168176
stack.append(self._token(call_result, token.pos))
169177
elif token.type == TokenType.FUNCDEF:
170-
func_namespace = namespaces.copy()
171-
func_namespace.add_namespace({})
172-
173178
func = self._spawn_function(
174-
namespace=func_namespace,
179+
namespace=namespaces.copy(),
175180
name=token.value.name,
176181
fargs=[tok.value for tok in token.value.args],
177182
body=token.value.body
@@ -201,9 +206,6 @@ def _interpret_line(self, expression: Stack[Token], namespaces: NamespaceStack)
201206

202207
return result.value
203208

204-
def _import(self, path: str):
205-
...
206-
207209
def _spawn_function(self,
208210
namespace: NamespaceStack,
209211
name: str,
@@ -212,7 +214,7 @@ def _spawn_function(self,
212214
def real_function(*args) -> Number:
213215
if not fargs and args:
214216
raise ArgumentsError("function takes no arguments", (-1, -1))
215-
if len(fargs) != len(args):
217+
elif len(fargs) != len(args):
216218
text = (
217219
"not enough arguments",
218220
"too much arguments"
@@ -229,7 +231,7 @@ def real_function(*args) -> Number:
229231
return self._interpret_line(body, namespace)
230232

231233
return Function(
232-
name=f"{name}({','.join(fargs)})",
234+
name=f"{name or '<lambda>'}({','.join(fargs)})",
233235
target=real_function
234236
)
235237

pycalc/lex/tokenizer.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,13 @@ def _parse_unary(self, tokens: Tokens) -> Tokens:
231231
output.append(token)
232232

233233
if buffer:
234-
raise InvalidSyntaxError(
235-
"unexpected operator in the end of the expression",
236-
buffer[-1].pos
237-
)
234+
if len(buffer) == 1 and buffer[0].type == TokenType.OP_SEMICOLON:
235+
output.append(buffer.pop())
236+
else:
237+
raise InvalidSyntaxError(
238+
"unexpected operator in the end of the expression",
239+
buffer[-1].pos
240+
)
238241

239242
return output
240243

pycalc/stack/builder.py

+37-34
Original file line numberDiff line numberDiff line change
@@ -128,50 +128,53 @@ def _build_line(self, tokens: Tokens) -> Stack:
128128

129129
def _count_args(self, tokens: Tokens) -> List[int]:
130130
result = []
131-
skip_rparens = 0
132131

133-
for i, token in enumerate(tokens[1:]):
134-
if skip_rparens:
135-
if token.type == TokenType.RPAREN:
136-
skip_rparens -= 1
132+
for funccall in self.__find_funccalls(tokens):
133+
result.append(0)
134+
waitforcomma = False
135+
parens = 0
137136

138-
continue
137+
for token in funccall:
138+
if parens:
139+
if token.type == TokenType.LPAREN:
140+
parens += 1
141+
elif token.type == TokenType.RPAREN:
142+
parens -= 1
139143

140-
if token.type == TokenType.LPAREN and tokens[i].type == TokenType.VAR:
141-
counters = self.__argscounter(tokens[i+2:])
142-
result.extend(counters)
143-
skip_rparens = len(counters)
144+
continue
145+
elif token.type == TokenType.LPAREN:
146+
parens += 1
147+
result[-1] += not waitforcomma
148+
waitforcomma = True
149+
elif waitforcomma:
150+
waitforcomma = token.type != TokenType.OP_COMMA
151+
else:
152+
result[-1] += 1
153+
waitforcomma = True
144154

145155
return result
146156

147-
def __argscounter(self, tokens: Tokens) -> List[int]:
148-
result = [0]
149-
skip_rparens = 0
150-
151-
waitforcomma = False
157+
def __find_funccalls(self, tokens: Tokens) -> List[Tokens]:
158+
funcs: List[Tokens] = []
159+
parens = 0
152160

153-
for i, token in enumerate(tokens):
154-
if skip_rparens:
155-
if token.type == TokenType.RPAREN:
156-
skip_rparens -= 1
157-
158-
continue
159-
elif token.type == TokenType.RPAREN:
160-
return result
161+
for i, token in enumerate(tokens[1:], start=1):
162+
if parens:
163+
if token.type == TokenType.LPAREN:
164+
parens += 1
165+
elif token.type == TokenType.RPAREN:
166+
parens -= 1
161167

162-
if waitforcomma:
163-
if token.type == TokenType.OP_COMMA:
164-
waitforcomma = False
165-
elif token.kind != TokenKind.PAREN:
166-
result[0] += 1
167-
waitforcomma = True
168+
if not parens:
169+
funcs.extend(self.__find_funccalls(funcs[-1]))
170+
continue
168171

169-
if token.type == TokenType.LPAREN:
170-
counters = self.__argscounter(tokens[i+1:])
171-
result.extend(counters)
172-
skip_rparens = len(counters)
172+
funcs[-1].append(token)
173+
elif token.type == TokenType.LPAREN and tokens[i - 1].type == TokenType.VAR:
174+
parens = 1
175+
funcs.append([])
173176

174-
return result
177+
return funcs
175178

176179
@staticmethod
177180
def _expr_divider(expr: Tokens) -> Iterator[Tuple[Tokens, Tuple[int, int]]]:

pycalc/tokentypes/types.py

+4
Original file line numberDiff line numberDiff line change
@@ -181,5 +181,9 @@ class UnknownTokenError(PyCalcError):
181181
pass
182182

183183

184+
class ExternalFunctionError(PyCalcError):
185+
pass
186+
187+
184188
class NoCodeError(Exception):
185189
pass

repl.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ def script_exec_mode(filename: str):
9393
except NoCodeError:
9494
pass
9595
except Exception as exc:
96-
print(f"{fd.name}:?:?: internal interpreter error: {exc.__class__.__name__}({repr(exc)})")
96+
print(f"{fd.name}:?:?: internal interpreter error:")
97+
raise exc
9798

9899

99100
if __name__ == '__main__':

std/stdlibrary.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ def decorator(a: Callable, b: Iterable) -> list:
1818
"cbrt": lambda a: a ** (1/3),
1919
"int": int,
2020
"float": float,
21+
"str": str,
22+
"strjoin": str.join,
2123
"range": range,
2224
"inv": lambda a: ~a,
2325
"pi": pi,
@@ -35,12 +37,14 @@ def decorator(a: Callable, b: Iterable) -> list:
3537
"set": stdmem.mem_set,
3638
"slice": stdmem.slice_,
3739
"len": len,
38-
"call": lambda func: func(),
3940

4041
"map": _as_list(map),
4142
"filter": _as_list(filter),
4243
"reduce": reduce,
4344
"while": stdstatements.while_,
4445
"if": stdstatements.if_else,
4546
"branch": stdstatements.branch,
47+
48+
"nop": lambda: 0,
49+
"call": lambda func: func(),
4650
}

std/stdmem.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ def mem_set(mem: List[int], offset: int, value: int) -> int:
2424
return 0
2525

2626

27-
def slice_(mem: List[int], begin: int, offset: int) -> List[int]:
28-
return mem[begin:begin+offset]
27+
def slice_(mem: List[int], begin: int, end: int) -> List[int]:
28+
return mem[begin:end]

tests/testcases.py

+9-13
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def test_declare_var(self):
150150
self.assertEqual(evaluate("a=5+5"), 10)
151151

152152
def test_get_declared_var(self):
153-
self.assertEqual(evaluate("a"), 10)
153+
self.assertEqual(evaluate("a=10 \n a"), 10)
154154

155155

156156
class TestFunctions(TestCase):
@@ -177,12 +177,10 @@ def test_funcdef(self):
177177
self.assertEqual(func_c.name, "c(x,y)")
178178

179179
def test_def_func_call(self):
180-
evaluate("f(x,y)=x*y")
181-
self.assertEqual(evaluate("f(2,5)"), 10)
180+
self.assertEqual(evaluate("f(x,y)=x*y \n f(2,5)"), 10)
182181

183182
def test_def_func_argexpr(self):
184-
evaluate("f(x,y)=x*y")
185-
self.assertEqual(evaluate("f(2+5, 3*2)"), 42)
183+
self.assertEqual(evaluate("f(x,y)=x*y \n f(2+5, 3*2)"), 42)
186184

187185
def test_funcdef_argexpr(self):
188186
with self.assertRaises(InvalidSyntaxError):
@@ -205,16 +203,14 @@ def test_funcdef_no_body(self):
205203

206204
class TestLambdas(TestCase):
207205
def test_assign_to_var(self):
208-
func = evaluate("a=(x)=x+1")
209-
self.assertIsInstance(func, Function)
210-
self.assertEqual(evaluate("a(1)"), 2)
206+
self.assertEqual(evaluate("a=(x)=x+1 \n a(1)"), 2)
211207

212208
def test_lambda_as_argument(self):
213-
sum_func = evaluate("sum(mem)=reduce((x,y)=x+y, mem)")
214-
range_func = evaluate("range(begin, end) = i=begin-1; map((x)=i=i+1;x+i, malloc(end-begin))")
215-
self.assertIsInstance(sum_func, Function)
216-
self.assertIsInstance(range_func, Function)
217-
self.assertEqual(evaluate("sum(range(0,5))"), 10)
209+
self.assertEqual(evaluate("""
210+
sum(mem)=reduce((x,y)=x+y, mem)
211+
range(begin, end) = i=begin-1; map((x)=i=i+1;x+i, malloc(end-begin))
212+
sum(range(0,5))
213+
"""), 10)
218214

219215
def test_missing_brace_in_arglambda(self):
220216
with self.assertRaises(InvalidSyntaxError):

0 commit comments

Comments
 (0)