update atomically (like a bomb)
* Fix angle units for reverse trigonometric functions * Add a streaming option to capture the output in real-time as a series of bitmap images * Switch to Decimal due to the non-continuity of Ent() breaking assumptions about the fidelity of floats. Trigonometry functions are still computed as floats. * Fix the line rendering algorithm for edge cases and add a new test line-positive.txt that ensures basic patterns are all correct. * Use custom rules to generate text representation of decimal numbers as to better match the output of the fx-92. * Improve the test constants.txt to better evaluate this representation. The interpreter automatically checks that the representation matches the value and fails on error to avoid scratching heads. * Fix EQUAL not being treated as a relational symbol.
This commit is contained in:
parent
c52e7ccaa3
commit
f1cc4ace40
|
@ -40,34 +40,34 @@ Hexadecimal Description Feature BitcodeLexer TextLexer
|
|||
4A Pré-Rép - - -
|
||||
4C θ Yes Yes Yes
|
||||
60 ( Yes Yes Yes
|
||||
68 Abs( Yes Yes -
|
||||
69 Rnd( Yes Yes -
|
||||
6C sinh( Yes Yes -
|
||||
6D cosh( Yes Yes -
|
||||
6E tanh( Yes Yes -
|
||||
6F sinh⁻¹( Yes Yes -
|
||||
70 cosh⁻¹( Yes Yes -
|
||||
71 tanh⁻¹( Yes Yes -
|
||||
68 Abs( Yes Yes Yes
|
||||
69 Rnd( - Yes Yes
|
||||
6C sinh( Yes Yes Yes
|
||||
6D cosh( Yes Yes Yes
|
||||
6E tanh( Yes Yes Yes
|
||||
6F sinh⁻¹( Yes Yes asinh()
|
||||
70 cosh⁻¹( Yes Yes acosh()
|
||||
71 tanh⁻¹( Yes Yes atanh()
|
||||
72 e^ Yes - -
|
||||
73 10^ Yes - -
|
||||
74 √( Yes Yes -
|
||||
75 ln( Yes Yes -
|
||||
76 ³√( Yes Yes -
|
||||
77 sin( Yes Yes -
|
||||
78 cos( Yes Yes -
|
||||
79 tan( Yes Yes -
|
||||
7A Arcsin( Yes Yes -
|
||||
7B Arccos( Yes Yes -
|
||||
7C Arctan( Yes Yes -
|
||||
7D log( Yes Yes -
|
||||
74 √( Yes Yes sqrt()
|
||||
75 ln( Yes Yes log()
|
||||
76 ³√( Yes Yes cbrt()
|
||||
77 sin( Yes Yes Yes
|
||||
78 cos( Yes Yes Yes
|
||||
79 tan( Yes Yes Yes
|
||||
7A Arcsin( Yes Yes asin()
|
||||
7B Arccos( Yes Yes acos()
|
||||
7C Arctan( Yes Yes atan()
|
||||
7D log( - Yes log10()
|
||||
7E Pol - - -
|
||||
7F Rec - - -
|
||||
83 Ent( Yes Yes -
|
||||
84 EntEx( Yes Yes -
|
||||
87 RanInt#( Yes Yes -
|
||||
88 PGCD( - - -
|
||||
89 PPCM( - - -
|
||||
8A Arond( Yes Yes -
|
||||
83 Ent( Yes Yes Yes
|
||||
84 EntEx( Yes Yes Yes
|
||||
87 RanInt#( - Yes RanInt()
|
||||
88 PGCD( Yes Yes GCD()
|
||||
89 PPCM( Yes Yes LCM()
|
||||
8A Arond( - Yes Arond()
|
||||
A5 = Yes Yes Yes
|
||||
A6 + Yes Yes Yes
|
||||
A7 - Yes Yes Yes
|
||||
|
|
23
fx92.py
23
fx92.py
|
@ -1,5 +1,6 @@
|
|||
#! /usr/bin/python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import getopt
|
||||
|
||||
|
@ -23,9 +24,10 @@ Input mode (default is -s):
|
|||
-u Input file is a wes.casio.com URL ("https://...F908313200333500")
|
||||
|
||||
Output options:
|
||||
--quiet Do not show the SDL window
|
||||
--save=<file> Save a copy of the screen output in a bitmap file
|
||||
--scale=<n> Window scale up (default 4, max 16)
|
||||
--quiet Do not show the SDL window
|
||||
--save=<file> Save a copy of the screen output in a bitmap file
|
||||
--scale=<n> Window scale up (default 4, max 16)
|
||||
--stream=<dir> Capture rendering in real time, outputs a series of bitmaps
|
||||
""".format(sys.argv[0]).strip()
|
||||
|
||||
def usage(exitcode=None):
|
||||
|
@ -38,7 +40,7 @@ def main():
|
|||
# Read command-line arguments
|
||||
try:
|
||||
opts, args = getopt.gnu_getopt(sys.argv[1:], "hus",
|
||||
["help", "quiet", "save=", "scale=", "debug="])
|
||||
["help", "quiet", "save=", "scale=", "debug=", "stream="])
|
||||
opts = dict(opts)
|
||||
|
||||
if len(sys.argv) == 1 or "-h" in opts or "--help" in opts:
|
||||
|
@ -62,6 +64,7 @@ def main():
|
|||
quiet = "--quiet" in opts
|
||||
out = opts.get("--save", None)
|
||||
debug = opts.get("--debug", None)
|
||||
stream = opts.get("--stream", False)
|
||||
|
||||
scale = int(opts.get("--scale", "4"))
|
||||
if scale < 1:
|
||||
|
@ -98,7 +101,17 @@ def main():
|
|||
print_ast(ast, lang="ast")
|
||||
return 0
|
||||
|
||||
with Window(width=192, height=47, scale=scale, quiet=quiet) as w:
|
||||
# Create the folder for streaming capture
|
||||
if stream:
|
||||
try:
|
||||
os.mkdir(stream)
|
||||
except FileExistsError:
|
||||
print("error: folder {} already exists, avoiding overwrite".format(
|
||||
stream))
|
||||
return 1
|
||||
|
||||
with Window(width=192, height=47, scale=scale, quiet=quiet,
|
||||
stream=stream) as w:
|
||||
ctx = Context(w)
|
||||
try:
|
||||
ctx.run(ast)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# fx-92 Scientifique Collège+ language interpreter: AST definition
|
||||
|
||||
import enum
|
||||
from decimal import Decimal
|
||||
|
||||
def auto_builder():
|
||||
number = 0
|
||||
|
@ -95,17 +96,17 @@ class Node:
|
|||
arity = len(self.args)
|
||||
|
||||
if self.type == N.MUL and arity == 0:
|
||||
return Node(N.CONST, 1)
|
||||
return Node(N.CONST, Decimal(1))
|
||||
if self.type == N.MUL and arity == 1:
|
||||
return self.args[0]
|
||||
if self.type == N.MUL and self.constchildren():
|
||||
prod = 1
|
||||
prod = Decimal(1)
|
||||
for c in self.args:
|
||||
prod *= c.value
|
||||
return Node(N.CONST, prod)
|
||||
|
||||
if self.type == N.ADD and arity == 0:
|
||||
return Node(N.CONST, 0)
|
||||
return Node(N.CONST, Decimal(0))
|
||||
if self.type == N.ADD and arity == 1:
|
||||
return self.args[0]
|
||||
if self.type == N.ADD and self.constchildren():
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from sdl2 import *
|
||||
import math
|
||||
import os
|
||||
|
||||
class InterruptException(Exception):
|
||||
pass
|
||||
|
@ -14,11 +15,13 @@ class Window:
|
|||
BLACK = (0, 0, 0, 255)
|
||||
WHITE = (255, 255, 255, 255)
|
||||
|
||||
def __init__(self, width, height, scale, quiet=False):
|
||||
def __init__(self, width, height, scale, quiet=False, stream=False):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.scale = scale
|
||||
self.quiet = quiet
|
||||
self.stream = stream
|
||||
self.streamid = 1
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
|
@ -87,6 +90,7 @@ class Window:
|
|||
|
||||
x = int(round(x))
|
||||
y = int(round(y))
|
||||
|
||||
SDL_RenderDrawPoint(self.r, x, y)
|
||||
|
||||
def clear(self, color):
|
||||
|
@ -112,7 +116,7 @@ class Window:
|
|||
self._point(x, y)
|
||||
|
||||
if dx > dy:
|
||||
cumul = dx // 2
|
||||
cumul = (dx+1) // 2
|
||||
for i in range(dx):
|
||||
x += sx
|
||||
cumul += dy
|
||||
|
@ -126,7 +130,7 @@ class Window:
|
|||
y += sy
|
||||
self._point(x, y)
|
||||
else:
|
||||
cumul = dy // 2
|
||||
cumul = (dy+1) // 2
|
||||
for i in range(dy):
|
||||
y += sy
|
||||
cumul += dx
|
||||
|
@ -143,6 +147,8 @@ class Window:
|
|||
def linef(self, x, y, angle, dist, color):
|
||||
"""Draw a line forward."""
|
||||
|
||||
x, y, angle, dist = float(x), float(y), float(angle), float(dist)
|
||||
|
||||
cos = math.cos(angle * math.pi / 180)
|
||||
sin = math.sin(angle * math.pi / 180)
|
||||
|
||||
|
@ -192,6 +198,11 @@ class Window:
|
|||
def update(self):
|
||||
"""Push window contents on-screen."""
|
||||
|
||||
if self.stream:
|
||||
name = "{:04d}.png".format(self.streamid)
|
||||
self.save(os.path.join(self.stream, name))
|
||||
self.streamid += 1
|
||||
|
||||
# Target the window with scaling
|
||||
SDL_SetRenderTarget(self.r, None)
|
||||
SDL_RenderSetScale(self.r, self.scale, self.scale)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# fx-92 Scientifique Collège+ language interpreter: AST interpreter
|
||||
|
||||
from fx92.ast import N, Node
|
||||
from decimal import Decimal
|
||||
from fx92.util import decfun, degree2decimal, decimal2degree, decimal_repr
|
||||
import math
|
||||
|
||||
class Context:
|
||||
|
@ -18,9 +20,9 @@ class Context:
|
|||
self.vars = { name: 0 for name in "MABCDEF" }
|
||||
self.w = window
|
||||
|
||||
self.x = float(0)
|
||||
self.y = float(0)
|
||||
self.theta = float(0)
|
||||
self.x = Decimal(0)
|
||||
self.y = Decimal(0)
|
||||
self.theta = Decimal(0)
|
||||
self.pen = False
|
||||
|
||||
# TODO: Also specify the style!
|
||||
|
@ -52,8 +54,11 @@ class Context:
|
|||
self.w.linef(self.x, self.y, self.theta, dist, self.w.BLACK)
|
||||
self.w.update()
|
||||
|
||||
self.x += dist * math.cos(self.theta * math.pi / 180)
|
||||
self.y += dist * math.sin(self.theta * math.pi / 180)
|
||||
dx = dist * degree2decimal(math.cos)(self.theta)
|
||||
dy = dist * degree2decimal(math.sin)(self.theta)
|
||||
|
||||
self.x = round(self.x + dx, 9)
|
||||
self.y = round(self.y + dy, 9)
|
||||
|
||||
elif n.type == N.ROTATE:
|
||||
angle, = self.runs(n)
|
||||
|
@ -96,8 +101,14 @@ class Context:
|
|||
raise Exception("Input statement not supported yet x_x")
|
||||
|
||||
elif n.type == N.PRINT:
|
||||
val, = self.runs(n)
|
||||
print(val)
|
||||
x, = self.runs(n)
|
||||
s = decimal_repr(x)
|
||||
print(s)
|
||||
|
||||
diff = abs(Decimal(s) - x)
|
||||
approx = Decimal(1e-9) * abs(x)
|
||||
assert diff <= approx, \
|
||||
"Incorrect representation of {} as {}".format(x,s)
|
||||
|
||||
elif n.type == N.STYLE:
|
||||
# TODO: Support style
|
||||
|
@ -141,7 +152,7 @@ class Context:
|
|||
return x - y
|
||||
|
||||
elif n.type == N.MUL:
|
||||
prod = 1
|
||||
prod = Decimal(1)
|
||||
for x in self.runs(n):
|
||||
prod *= x
|
||||
return prod
|
||||
|
@ -177,27 +188,27 @@ class Context:
|
|||
f = {
|
||||
"Abs": abs,
|
||||
"Rnd": None,
|
||||
"sinh": math.sinh,
|
||||
"cosh": math.cosh,
|
||||
"tanh": math.tanh,
|
||||
"asinh": math.asinh,
|
||||
"acosh": math.acosh,
|
||||
"atanh": math.atanh,
|
||||
"sqrt": math.sqrt,
|
||||
"log": math.log,
|
||||
"sinh": decfun(math.sinh),
|
||||
"cosh": decfun(math.cosh),
|
||||
"tanh": decfun(math.tanh),
|
||||
"asinh": decfun(math.asinh),
|
||||
"acosh": decfun(math.acosh),
|
||||
"atanh": decfun(math.atanh),
|
||||
"sqrt": decfun(math.sqrt),
|
||||
"log": decfun(math.log),
|
||||
"cbrt": lambda x: x ** (1/3),
|
||||
"sin": math.sin,
|
||||
"cos": math.cos,
|
||||
"tan": math.tan,
|
||||
"asin": math.asin,
|
||||
"acos": math.acos,
|
||||
"atan": math.atan,
|
||||
"log10": math.log10,
|
||||
"Ent": lambda x: float(int(x)),
|
||||
"EntEx": lambda x: float(int(x)) - (x < 0),
|
||||
"sin": degree2decimal(math.sin),
|
||||
"cos": degree2decimal(math.cos),
|
||||
"tan": degree2decimal(math.tan),
|
||||
"asin": decimal2degree(math.asin),
|
||||
"acos": decimal2degree(math.acos),
|
||||
"atan": decimal2degree(math.atan),
|
||||
"log10": None,
|
||||
"Ent": lambda x: Decimal(int(x)),
|
||||
"EntEx": lambda x: Decimal(int(x)) - (x < 0),
|
||||
"RanInt": None,
|
||||
"GCD": lambda x, y: float(math.gcd(int(x), int(y))),
|
||||
"LCM": lambda x, y: x * y / math.gcd(int(x), int(y)),
|
||||
"GCD": lambda x, y: Decimal(math.gcd(int(x), int(y))),
|
||||
"LCM": lambda x, y: x*y/Decimal(math.gcd(int(x),int(y))),
|
||||
"Arond": None,
|
||||
}[n.args[0]]
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import math
|
||||
import re
|
||||
import enum
|
||||
from decimal import Decimal
|
||||
|
||||
#---
|
||||
# Token description
|
||||
|
@ -58,7 +59,6 @@ class T(enum.IntEnum):
|
|||
SEMI = 0x2C
|
||||
LPAR = 0x60
|
||||
RPAR = 0xD0
|
||||
EQUAL = 0xA5
|
||||
PLUS = 0xA6
|
||||
MINUS = 0xA7
|
||||
STAR = 0xA8
|
||||
|
@ -103,7 +103,7 @@ def str2float(integer, decimal, exponent, percent):
|
|||
m2 = ".0" if decimal == "." else (decimal or "")
|
||||
m3 = exponent or ""
|
||||
|
||||
f = float(m1 + m2 + m3)
|
||||
f = Decimal(m1 + m2 + m3)
|
||||
|
||||
if percent == "%":
|
||||
f /= 100
|
||||
|
@ -193,15 +193,19 @@ class BitcodeLexer(LexerBase):
|
|||
if code == 0xC0:
|
||||
code = 0xA7
|
||||
|
||||
# Equal symbol
|
||||
if h[p] == 0xA5:
|
||||
return Token(T.REL, "=")
|
||||
|
||||
try:
|
||||
return Token(T(code))
|
||||
except:
|
||||
pass
|
||||
|
||||
if code == 0x21:
|
||||
return Token(T.CONST, math.e)
|
||||
return Token(T.CONST, Decimal(math.e), "[e]")
|
||||
if code == 0x22:
|
||||
return Token(T.CONST, math.pi)
|
||||
return Token(T.CONST, Decimal(math.pi), "[pi]")
|
||||
|
||||
# Constants
|
||||
if code in range(0x30, 0x39+1) or code == 0x2E:
|
||||
|
@ -383,6 +387,10 @@ class TextLexer(LexerBase):
|
|||
self.code = c[len(r):]
|
||||
self.pending_param = True
|
||||
return Token(T.REL, r)
|
||||
if c[0] == "=":
|
||||
self.code = c[1:]
|
||||
self.pending_param = True
|
||||
return Token(T.REL, "=")
|
||||
|
||||
# Punctuation
|
||||
punct = {
|
||||
|
@ -392,7 +400,6 @@ class TextLexer(LexerBase):
|
|||
"?": T.QUEST,
|
||||
"(": T.LPAR,
|
||||
")": T.RPAR,
|
||||
"=": T.EQUAL,
|
||||
"+": T.PLUS,
|
||||
"-": T.MINUS,
|
||||
"*": T.STAR,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# fx-92 Scientifique Collège+ language interpreter: Syntactic analysis
|
||||
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from fx92.lexer import T, Token, BitcodeLexer
|
||||
from fx92.ast import N, Node
|
||||
|
||||
|
@ -227,7 +228,7 @@ class Parser:
|
|||
self.expect(T.PLUS)
|
||||
elif self.la.type == T.MINUS:
|
||||
self.expect(T.MINUS)
|
||||
factors.append(Node(N.CONST, -1))
|
||||
factors.append(Node(N.CONST, Decimal(-1)))
|
||||
|
||||
while 1:
|
||||
optional = len(factors) > 0
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import math
|
||||
from decimal import Decimal
|
||||
|
||||
def decfun(f):
|
||||
def g(x):
|
||||
return Decimal(f(float(x)))
|
||||
return g
|
||||
|
||||
def degree2decimal(f):
|
||||
def g(x):
|
||||
return Decimal(f(float(x) * math.pi / 180))
|
||||
return g
|
||||
|
||||
def decimal2degree(f):
|
||||
def g(x):
|
||||
return Decimal(f(float(x)) * 180 / math.pi)
|
||||
return g
|
||||
|
||||
def decimal_repr(x):
|
||||
# Remove trailing 0's
|
||||
x = x.normalize()
|
||||
|
||||
if x == 0:
|
||||
return "0"
|
||||
|
||||
# Keep only 10 digits for printing
|
||||
i, e = int(x._int), x._exp
|
||||
digits = len(str(i))
|
||||
overflow = digits - 10
|
||||
|
||||
if overflow > 0:
|
||||
i = (i + (10 ** overflow) // 2) // (10 ** overflow)
|
||||
e += overflow
|
||||
digits = 10
|
||||
|
||||
# Handle sign
|
||||
sign = "-" if x._sign else ""
|
||||
|
||||
# Exponent in engineering notation (true exponent of value)
|
||||
true_exp = e + digits - 1
|
||||
|
||||
# For integers up to 10 digits: print normally
|
||||
if e >= 0 and true_exp <= 9:
|
||||
return sign + str(i * 10 ** e)
|
||||
|
||||
# For larger integers, use scientific notation
|
||||
elif e >= 0:
|
||||
dg = str(i)
|
||||
dg = dg[0] + "." + dg[1:] + "e" + str(true_exp)
|
||||
return sign + dg
|
||||
|
||||
# Otherwise, if there are less than 10 digits overall: print normally
|
||||
elif true_exp >= -9:
|
||||
supplement = max(-true_exp, 0)
|
||||
dg = "0" * supplement + str(i)
|
||||
true_exp += supplement
|
||||
|
||||
dg = dg[:true_exp+1] + "." + dg[true_exp+1:]
|
||||
return sign + dg
|
||||
|
||||
# For very small numbers, use scientific notation again
|
||||
else:
|
||||
dg = str(i)
|
||||
dg = dg[0] + "." + dg[1:] + "e" + str(true_exp)
|
||||
return sign + dg
|
|
@ -1,4 +1,4 @@
|
|||
10.0
|
||||
0.0
|
||||
90.0
|
||||
160.0
|
||||
10
|
||||
0
|
||||
90
|
||||
160
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
173.0
|
||||
-374.0
|
||||
173
|
||||
838168876
|
||||
8381688765
|
||||
8.381688766e10
|
||||
8.381688766e11
|
||||
1230000000
|
||||
1.23e10
|
||||
-374
|
||||
37.947
|
||||
913390.0
|
||||
913390
|
||||
9.454
|
||||
0.034
|
||||
1445.0
|
||||
0.0
|
||||
100000.0
|
||||
1445
|
||||
0
|
||||
100000
|
||||
0.38
|
||||
-0.02434
|
||||
1.3597e-16
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
print 173
|
||||
print 838168876
|
||||
print 8381688765
|
||||
print 83816887657
|
||||
print 838168876572
|
||||
print 123e7
|
||||
print 123e8
|
||||
print -374
|
||||
print 37.947
|
||||
print 913.39e3
|
||||
|
@ -9,4 +15,5 @@ print .
|
|||
print 1.e5
|
||||
print 38%
|
||||
print -24.34e-1%
|
||||
print 135.97e-18
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
-2.0
|
||||
1.0
|
||||
1.0
|
||||
-1.0
|
||||
-2
|
||||
1
|
||||
1
|
||||
-1
|
||||
|
|
|
@ -1 +1 @@
|
|||
17.0
|
||||
17
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
|
@ -0,0 +1,12 @@
|
|||
setvar 0, A
|
||||
while A <= 5
|
||||
setvar 0, B
|
||||
while B <= 12
|
||||
penup
|
||||
goto 14*B-90, 7*A-20
|
||||
pendown
|
||||
goto 15*B-90, 8*A-20
|
||||
setvar B+1, B
|
||||
while_end
|
||||
setvar A+1, A
|
||||
while_end
|
Loading…
Reference in New Issue