algebrite
Version:
Computer Algebra System in Coffeescript
1,112 lines (862 loc) • 17.4 kB
text/coffeescript
###
Prints in "2d", e.g. instead of 1/(x+1)^2 :
1
----------
2
(1 + x)
Note that although this looks more natural, a) it's not parsable and
b) it can be occasionally be ambiguous, such as:
1
----
2
x
is 1/x^2 but it also looks a little like x^(1/2)
###
#-----------------------------------------------------------------------------
#
# Examples:
#
# 012345678
# -2 .........
# -1 .........
# 0 ..hello.. x=2, y=0, h=1, w=5
# 1 .........
# 2 .........
#
# 012345678
# -2 .........
# -1 ..355....
# 0 ..---.... x=2, y=-1, h=3, w=3
# 1 ..113....
# 2 .........
#
#-----------------------------------------------------------------------------
YMAX = 10000
class glyph
c: 0
x: 0
y: 0
# will contain glyphs
chartab = []
for charTabIndex in [0...YMAX]
chartab[charTabIndex] = new glyph()
yindex = 0
level = 0
emit_x = 0
expr_level = 0
display_flag = 0
# this is not really the translated version,
# the original is in window.cpp and is
# rather more complex
printchar_nowrap = (character) ->
accumulator = ""
accumulator += character
return accumulator
printchar = (character) ->
return printchar_nowrap character
print2dascii = (p) ->
h = 0
w = 0
y = 0
save()
yindex = 0
level = 0
emit_x = 0
emit_top_expr(p)
# if too wide then print flat
[h,w,y] = get_size(0, yindex)
if (w > 100)
printline(p)
restore()
return
beenPrinted = print_glyphs()
restore()
return beenPrinted
emit_top_expr = (p) ->
if (car(p) == symbol(SETQ))
emit_expr(cadr(p))
__emit_str(" = ")
emit_expr(caddr(p))
return
if (istensor(p))
emit_tensor(p)
else
emit_expr(p)
will_be_displayed_as_fraction = (p) ->
if (level > 0)
return 0
if (isfraction(p))
return 1
if (car(p) != symbol(MULTIPLY))
return 0
if (isfraction(cadr(p)))
return 1
while (iscons(p))
if (isdenominator(car(p)))
return 1
p = cdr(p)
return 0
emit_expr = (p) ->
# if (level > 0) {
# printexpr(p)
# return
# }
expr_level++
if (car(p) == symbol(ADD))
p = cdr(p)
if (__is_negative(car(p)))
__emit_char('-')
if (will_be_displayed_as_fraction(car(p)))
__emit_char(' ')
emit_term(car(p))
p = cdr(p)
while (iscons(p))
if (__is_negative(car(p)))
__emit_char(' ')
__emit_char('-')
__emit_char(' ')
else
__emit_char(' ')
__emit_char('+')
__emit_char(' ')
emit_term(car(p))
p = cdr(p)
else
if (__is_negative(p))
__emit_char('-')
if (will_be_displayed_as_fraction(p))
__emit_char(' ')
emit_term(p)
expr_level--
emit_unsigned_expr = (p) ->
if (car(p) == symbol(ADD))
p = cdr(p)
# if (__is_negative(car(p)))
# __emit_char('-')
emit_term(car(p))
p = cdr(p)
while (iscons(p))
if (__is_negative(car(p)))
__emit_char(' ')
__emit_char('-')
__emit_char(' ')
else
__emit_char(' ')
__emit_char('+')
__emit_char(' ')
emit_term(car(p))
p = cdr(p)
else
# if (__is_negative(p))
# __emit_char('-')
emit_term(p)
__is_negative = (p) ->
if (isnegativenumber(p))
return 1
if (car(p) == symbol(MULTIPLY) && isnegativenumber(cadr(p)))
return 1
return 0
emit_term = (p) ->
if (car(p) == symbol(MULTIPLY))
n = count_denominators(p)
if (n && level == 0)
emit_fraction(p, n)
else
emit_multiply(p, n)
else
emit_factor(p)
isdenominator = (p) ->
if (car(p) == symbol(POWER) && cadr(p) != symbol(E) && __is_negative(caddr(p)))
return 1
else
return 0
count_denominators = (p) ->
count = 0
p = cdr(p)
# if (isfraction(car(p))) {
# count++
# p = cdr(p)
# }
while (iscons(p))
q = car(p)
if (isdenominator(q))
count++
p = cdr(p)
return count
# n is the number of denominators, not counting a fraction like 1/2
emit_multiply = (p, n) ->
if (n == 0)
p = cdr(p)
if (isplusone(car(p)) || isminusone(car(p)))
p = cdr(p)
emit_factor(car(p))
p = cdr(p)
while (iscons(p))
__emit_char(' ')
emit_factor(car(p))
p = cdr(p)
else
emit_numerators(p)
__emit_char('/')
# need grouping if more than one denominator
if (n > 1 || isfraction(cadr(p)))
__emit_char('(')
emit_denominators(p)
__emit_char(')')
else
emit_denominators(p)
#define A p3
#define B p4
# sign of term has already been emitted
emit_fraction = (p, d) ->
count = 0
k1 = 0
k2 = 0
n = 0
x = 0
save()
p3 = one; # p3 is A
p4 = one; # p4 is B
# handle numerical coefficient
if (isrational(cadr(p)))
push(cadr(p))
mp_numerator()
absval()
p3 = pop(); # p3 is A
push(cadr(p))
mp_denominator()
p4 = pop(); # p4 is B
if (isdouble(cadr(p)))
push(cadr(p))
absval()
p3 = pop(); # p3 is A
# count numerators
if (isplusone(p3)) # p3 is A
n = 0
else
n = 1
p1 = cdr(p)
if (isnum(car(p1)))
p1 = cdr(p1)
while (iscons(p1))
p2 = car(p1)
if (isdenominator(p2))
doNothing = 1
else
n++
p1 = cdr(p1)
# emit numerators
x = emit_x
k1 = yindex
count = 0
# emit numerical coefficient
if (!isplusone(p3)) # p3 is A
emit_number(p3, 0); # p3 is A
count++
# skip over "multiply"
p1 = cdr(p)
# skip over numerical coefficient, already handled
if (isnum(car(p1)))
p1 = cdr(p1)
while (iscons(p1))
p2 = car(p1)
if (isdenominator(p2))
doNothing = 1
else
if (count > 0)
__emit_char(' ')
if (n == 1)
emit_expr(p2)
else
emit_factor(p2)
count++
p1 = cdr(p1)
if (count == 0)
__emit_char('1')
# emit denominators
k2 = yindex
count = 0
if (!isplusone(p4)) # p4 is B
emit_number(p4, 0); # p4 is B
count++
d++
p1 = cdr(p)
if (isrational(car(p1)))
p1 = cdr(p1)
while (iscons(p1))
p2 = car(p1)
if (isdenominator(p2))
if (count > 0)
__emit_char(' ')
emit_denominator(p2, d)
count++
p1 = cdr(p1)
fixup_fraction(x, k1, k2)
restore()
# p points to a multiply
emit_numerators = (p) ->
save()
n = 0
p1 = one
p = cdr(p)
if (isrational(car(p)))
push(car(p))
mp_numerator()
absval()
p1 = pop()
p = cdr(p)
else if (isdouble(car(p)))
push(car(p))
absval()
p1 = pop()
p = cdr(p)
n = 0
if (!isplusone(p1))
emit_number(p1, 0)
n++
while (iscons(p))
if (isdenominator(car(p)))
doNothing = 1
else
if (n > 0)
__emit_char(' ')
emit_factor(car(p))
n++
p = cdr(p)
if (n == 0)
__emit_char('1')
restore()
# p points to a multiply
emit_denominators = (p) ->
save()
n = 0
p = cdr(p)
if (isfraction(car(p)))
push(car(p))
mp_denominator()
p1 = pop()
emit_number(p1, 0)
n++
p = cdr(p)
while (iscons(p))
if (isdenominator(car(p)))
if (n > 0)
__emit_char(' ')
emit_denominator(car(p), 0)
n++
p = cdr(p)
restore()
emit_factor = (p) ->
if (istensor(p))
if (level == 0)
#emit_tensor(p)
emit_flat_tensor(p)
else
emit_flat_tensor(p)
return
if (isdouble(p))
emit_number(p, 0)
return
if (car(p) == symbol(ADD) || car(p) == symbol(MULTIPLY))
emit_subexpr(p)
return
if (car(p) == symbol(POWER))
emit_power(p)
return
if (iscons(p))
#if (car(p) == symbol(FORMAL) && cadr(p).k == SYM)
# emit_symbol(cadr(p))
#else
emit_function(p)
return
if (isnum(p))
if (level == 0)
emit_numerical_fraction(p)
else
emit_number(p, 0)
return
if (issymbol(p))
emit_symbol(p)
return
if (isstr(p))
emit_string(p)
return
emit_numerical_fraction = (p) ->
k1 = 0
k2 = 0
x = 0
save()
push(p)
mp_numerator()
absval()
p3 = pop(); # p3 is A
push(p)
mp_denominator()
p4 = pop(); # p4 is B
if (isplusone(p4)) # p4 is B
emit_number(p3, 0); # p3 is A
restore()
return
x = emit_x
k1 = yindex
emit_number(p3, 0); # p3 is A
k2 = yindex
emit_number(p4, 0) # p4 is B
fixup_fraction(x, k1, k2)
restore()
# if it's a factor then it doesn't need parens around it, i.e. 1/sin(theta)^2
isfactor = (p) ->
if (iscons(p) && car(p) != symbol(ADD) && car(p) != symbol(MULTIPLY) && car(p) != symbol(POWER))
return 1
if (issymbol(p))
return 1
if (isfraction(p))
return 0
if (isnegativenumber(p))
return 0
if (isnum(p))
return 1
return 0
emit_power = (p) ->
k1 = 0
k2 = 0
x = 0
if (cadr(p) == symbol(E))
__emit_str("exp(")
emit_expr(caddr(p))
__emit_char(')')
return
if (level > 0)
if (isminusone(caddr(p)))
__emit_char('1')
__emit_char('/')
if (isfactor(cadr(p)))
emit_factor(cadr(p))
else
emit_subexpr(cadr(p))
else
if (isfactor(cadr(p)))
emit_factor(cadr(p))
else
emit_subexpr(cadr(p))
__emit_char('^')
if (isfactor(caddr(p)))
emit_factor(caddr(p))
else
emit_subexpr(caddr(p))
return
# special case: 1 over something
if (__is_negative(caddr(p)))
x = emit_x
k1 = yindex
__emit_char('1')
k2 = yindex
#level++
emit_denominator(p, 1)
#level--
fixup_fraction(x, k1, k2)
return
k1 = yindex
if (isfactor(cadr(p)))
emit_factor(cadr(p))
else
emit_subexpr(cadr(p))
k2 = yindex
level++
emit_expr(caddr(p))
level--
fixup_power(k1, k2)
# if n == 1 then emit as expr (no parens)
# p is a power
emit_denominator = (p, n) ->
k1 = 0
k2 = 0
# special case: 1 over something
if (isminusone(caddr(p)))
if (n == 1)
emit_expr(cadr(p))
else
emit_factor(cadr(p))
return
k1 = yindex
# emit base
if (isfactor(cadr(p)))
emit_factor(cadr(p))
else
emit_subexpr(cadr(p))
k2 = yindex
# emit exponent, don't emit minus sign
level++
emit_unsigned_expr(caddr(p))
level--
fixup_power(k1, k2)
emit_function = (p) ->
if (car(p) == symbol(INDEX) && issymbol(cadr(p)))
emit_index_function(p)
return
if (car(p) == symbol(FACTORIAL))
emit_factorial_function(p)
return
if (car(p) == symbol(DERIVATIVE))
__emit_char('d')
else
emit_symbol(car(p))
__emit_char('(')
p = cdr(p)
if (iscons(p))
emit_expr(car(p))
p = cdr(p)
while (iscons(p))
__emit_char(',')
#__emit_char(' ')
emit_expr(car(p))
p = cdr(p)
__emit_char(')')
emit_index_function = (p) ->
p = cdr(p)
if (caar(p) == symbol(ADD) || caar(p) == symbol(MULTIPLY) || caar(p) == symbol(POWER) || caar(p) == symbol(FACTORIAL))
emit_subexpr(car(p))
else
emit_expr(car(p))
__emit_char('[')
p = cdr(p)
if (iscons(p))
emit_expr(car(p))
p = cdr(p)
while(iscons(p))
__emit_char(',')
emit_expr(car(p))
p = cdr(p)
__emit_char(']')
emit_factorial_function = (p) ->
p = cadr(p)
if (car(p) == symbol(ADD) || car(p) == symbol(MULTIPLY) || car(p) == symbol(POWER) || car(p) == symbol(FACTORIAL))
emit_subexpr(p)
else
emit_expr(p)
__emit_char('!')
emit_subexpr = (p) ->
__emit_char('(')
emit_expr(p)
__emit_char(')')
emit_symbol = (p) ->
i = 0
if (p == symbol(E))
__emit_str("exp(1)")
return
pPrintName = get_printname(p)
for i in [0...pPrintName.length]
__emit_char(pPrintName[i])
emit_string = (p) ->
i = 0
pString = p.str
__emit_char('"')
for i in [0...pString.length]
__emit_char(pString[i])
__emit_char('"')
fixup_fraction = (x, k1, k2) ->
dx = 0
dy = 0
i = 0
w = 0
y = 0
h1 = 0
w1 = 0
y1 = 0
h2 = 0
w2 = 0
y2 = 0
[h1,w1,y1] = get_size(k1, k2)
[h2,w2,y2] = get_size(k2, yindex)
if (w2 > w1)
dx = (w2 - w1) / 2; # shift numerator right
else
dx = 0
dx++
# this is how much is below the baseline
y = y1 + h1 - 1
dy = -y - 1
move(k1, k2, dx, dy)
if (w2 > w1)
dx = -w1
else
dx = -w1 + (w1 - w2) / 2
dx++
dy = -y2 + 1
move(k2, yindex, dx, dy)
if (w2 > w1)
w = w2
else
w = w1
w+=2
emit_x = x
for i in [0...w]
__emit_char('-')
fixup_power = (k1, k2) ->
dy = 0
h1 = 0
w1 = 0
y1 = 0
h2 = 0
w2 = 0
y2 = 0
[h1,w1,y1] = get_size(k1, k2)
[h2,w2,y2] = get_size(k2, yindex)
# move superscript to baseline
dy = -y2 - h2 + 1
# now move above base
dy += y1 - 1
move(k2, yindex, 0, dy)
move = (j, k, dx, dy) ->
i = 0
for i in [j...k]
chartab[i].x += dx
chartab[i].y += dy
# finds the bounding rectangle and vertical position
get_size = (j, k)->
i = 0
min_x = chartab[j].x
max_x = chartab[j].x
min_y = chartab[j].y
max_y = chartab[j].y
for i in [(j + 1)...k]
if (chartab[i].x < min_x)
min_x = chartab[i].x
if (chartab[i].x > max_x)
max_x = chartab[i].x
if (chartab[i].y < min_y)
min_y = chartab[i].y
if (chartab[i].y > max_y)
max_y = chartab[i].y
h = max_y - min_y + 1
w = max_x - min_x + 1
y = min_y
return [h,w,y]
displaychar = (c) ->
__emit_char(c)
__emit_char = (c) ->
if (yindex == YMAX)
return
if !chartab[yindex]?
debugger
chartab[yindex].c = c
chartab[yindex].x = emit_x
chartab[yindex].y = 0
yindex++
emit_x++
__emit_str = (s) ->
i = 0
for i in [0...s.length]
__emit_char(s[i])
emit_number = (p, emit_sign) ->
tmpString = ""
i = 0
switch (p.k)
when NUM
tmpString = p.q.a.toString()
if (tmpString[0] == '-' && emit_sign == 0)
tmpString = tmpString.substring(1)
for i in [0...tmpString.length]
__emit_char(tmpString[i])
tmpString = p.q.b.toString()
if (tmpString == "1")
break
__emit_char('/')
for i in [0...tmpString.length]
__emit_char(tmpString[i])
when DOUBLE
tmpString = doubleToReasonableString(p.d)
if (tmpString[0] == '-' && emit_sign == 0)
tmpString = tmpString.substring(1)
for i in [0...tmpString.length]
__emit_char(tmpString[i])
# a and b are glyphs
cmpGlyphs = (a, b) ->
if (a.y < b.y)
return -1
if (a.y > b.y)
return 1
if (a.x < b.x)
return -1
if (a.x > b.x)
return 1
return 0
print_glyphs = ->
i = 0
accumulator = ""
# now sort the glyphs by their vertical positions,
# since we are going to build a string where obviously the
# "upper" line has to printed out first, followed by
# a new line, followed by the other lines.
#qsort(chartab, yindex, sizeof (struct glyph), __cmp)
subsetOfStack = chartab.slice(0,yindex)
subsetOfStack.sort(cmpGlyphs)
chartab = [].concat(subsetOfStack).concat(chartab.slice(yindex))
x = 0
y = chartab[0].y
for i in [0...yindex]
while (chartab[i].y > y)
accumulator += printchar('\n')
x = 0
y++
while (chartab[i].x > x)
accumulator += printchar_nowrap(' ')
x++
accumulator += printchar_nowrap(chartab[i].c)
x++
return accumulator
buffer = ""
getdisplaystr = ->
yindex = 0
level = 0
emit_x = 0
emit_expr(pop())
fill_buf()
return buffer
fill_buf = ->
tmpBuffer = buffer
sIndex = 0
i = 0
#qsort(chartab, yindex, sizeof (struct glyph), __cmp)
subsetOfStack = chartab.slice(0,yindex)
subsetOfStack.sort(cmpGlyphs)
chartab = [].concat(subsetOfStack).concat(chartab.slice(yindex))
x = 0
y = chartab[0].y
for i in [0...yindex]
while (chartab[i].y > y)
tmpBuffer[sIndex++] = '\n'
x = 0
y++
while (chartab[i].x > x)
tmpBuffer[sIndex++] = ' '
x++
tmpBuffer[sIndex++] = chartab[i].c
x++
tmpBuffer[sIndex++] = '\n'
N = 100
class oneElement
x: 0
y: 0
h: 0
w: 0
index: 0
count: 0
elem = []
for elelmIndex in [0...10000]
elem[elelmIndex] = new oneElement
SPACE_BETWEEN_COLUMNS = 3
SPACE_BETWEEN_ROWS = 1
emit_tensor = (p) ->
i = 0
n = 0
nrow = 0
ncol = 0
x = 0
y = 0
h = 0
w = 0
dx = 0
dy = 0
eh = 0
ew = 0
row = 0
col = 0
if (p.tensor.ndim > 2)
emit_flat_tensor(p)
return
nrow = p.tensor.dim[0]
if (p.tensor.ndim == 2)
ncol = p.tensor.dim[1]
else
ncol = 1
n = nrow * ncol
if (n > N)
emit_flat_tensor(p)
return
# horizontal coordinate of the matrix
#if 0
#emit_x += 2; # make space for left paren
#endif
x = emit_x
# emit each element
for i in [0...n]
elem[i].index = yindex
elem[i].x = emit_x
emit_expr(p.tensor.elem[i])
elem[i].count = yindex - elem[i].index
[elem[i].h, elem[i].w, elem[i].y] = get_size(elem[i].index, yindex)
# find element height and width
eh = 0
ew = 0
for i in [0...n]
if (elem[i].h > eh)
eh = elem[i].h
if (elem[i].w > ew)
ew = elem[i].w
# this is the overall height of the matrix
h = nrow * eh + (nrow - 1) * SPACE_BETWEEN_ROWS
# this is the overall width of the matrix
w = ncol * ew + (ncol - 1) * SPACE_BETWEEN_COLUMNS
# this is the vertical coordinate of the matrix
y = -(h / 2)
# move elements around
for row in [0...nrow]
for col in [0...ncol]
i = row * ncol + col
# first move to upper left corner of matrix
dx = x - elem[i].x
dy = y - elem[i].y
move(elem[i].index, elem[i].index + elem[i].count, dx, dy)
# now move to official position
dx = 0
if (col > 0)
dx = col * (ew + SPACE_BETWEEN_COLUMNS)
dy = 0
if (row > 0)
dy = row * (eh + SPACE_BETWEEN_ROWS)
# small correction for horizontal centering
dx += (ew - elem[i].w) / 2
# small correction for vertical centering
dy += (eh - elem[i].h) / 2
move(elem[i].index, elem[i].index + elem[i].count, dx, dy)
emit_x = x + w
###
if 0
# left brace
for (i = 0; i < h; i++) {
if (yindex == YMAX)
break
chartab[yindex].c = '|'
chartab[yindex].x = x - 2
chartab[yindex].y = y + i
yindex++
}
# right brace
emit_x++
for (i = 0; i < h; i++) {
if (yindex == YMAX)
break
chartab[yindex].c = '|'
chartab[yindex].x = emit_x
chartab[yindex].y = y + i
yindex++
}
emit_x++
endif
###
emit_flat_tensor = (p) ->
emit_tensor_inner(p, 0, 0)
emit_tensor_inner = (p, j, k) ->
i = 0
__emit_char('(')
for i in [0...p.tensor.dim[j]]
if (j + 1 == p.tensor.ndim)
emit_expr(p.tensor.elem[k])
k = k + 1
else
k = emit_tensor_inner(p, j + 1, k)
if (i + 1 < p.tensor.dim[j])
__emit_char(',')
__emit_char(')')
return k