2022-11-20 22:51:09 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
import "fmt"
|
|
|
|
import "bytes"
|
|
|
|
import "image"
|
|
|
|
import _ "embed"
|
|
|
|
import _ "image/png"
|
|
|
|
import "git.tebibyte.media/sashakoshka/stone"
|
|
|
|
import _ "git.tebibyte.media/sashakoshka/stone/backends/x"
|
|
|
|
|
|
|
|
//go:embed icon/icon64.png
|
|
|
|
var iconBytes []byte
|
|
|
|
|
|
|
|
var application = &stone.Application { }
|
|
|
|
|
|
|
|
var expressionRoot Expression
|
|
|
|
var inputBuffer stone.DamageBuffer
|
|
|
|
var showLeftColumn bool
|
|
|
|
|
|
|
|
var inputState struct {
|
|
|
|
alt bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func main () {
|
|
|
|
application.SetTitle("MathPan")
|
|
|
|
application.SetSize(64, 10)
|
|
|
|
|
|
|
|
icon, _, err := image.Decode(bytes.NewReader(iconBytes))
|
|
|
|
if err != nil { panic(err) }
|
|
|
|
application.SetIcon([]image.Image { icon })
|
|
|
|
|
|
|
|
application.OnStart(onStart)
|
|
|
|
application.OnResize(redraw)
|
|
|
|
application.OnPress(onPress)
|
|
|
|
|
|
|
|
err = application.Run()
|
|
|
|
if err != nil { panic(err) }
|
|
|
|
}
|
|
|
|
|
|
|
|
func onPress (button stone.Button) {
|
|
|
|
switch button {
|
|
|
|
case stone.KeyUp:
|
|
|
|
if selectedExpression == nil { break }
|
|
|
|
|
|
|
|
newSelection := selectedExpression.Parent()
|
|
|
|
if newSelection != nil {
|
|
|
|
selectedExpression = newSelection
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case stone.KeyLeft:
|
|
|
|
if selectedExpression == nil { break }
|
|
|
|
|
|
|
|
newSelection := selectedExpression.Previous()
|
|
|
|
if newSelection != nil {
|
|
|
|
selectedExpression = newSelection
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case stone.KeyRight:
|
|
|
|
if selectedExpression == nil { break }
|
|
|
|
|
|
|
|
newSelection := selectedExpression.Next()
|
|
|
|
if newSelection != nil {
|
|
|
|
selectedExpression = newSelection
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case stone.KeyLeftAlt, stone.KeyRightAlt:
|
|
|
|
inputState.alt = true
|
|
|
|
|
|
|
|
case '[':
|
|
|
|
insertGeneric(&Operation { })
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
2022-11-20 23:07:46 -07:00
|
|
|
case
|
|
|
|
'+', '-', '*', '/', 'p', 'r', '%', '|', '~', '&', '^', 'm',
|
|
|
|
'<', '>':
|
|
|
|
|
2022-11-20 22:51:09 -07:00
|
|
|
insertOperation(rune(button))
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case stone.KeyDelete:
|
|
|
|
if selectedExpression == nil { break }
|
|
|
|
parent := selectedExpression.Parent()
|
|
|
|
|
|
|
|
if parent == nil {
|
|
|
|
expressionRoot = nil
|
|
|
|
selectedExpression = nil
|
|
|
|
} else {
|
|
|
|
previous := selectedExpression.Parent().Previous()
|
|
|
|
next := selectedExpression.Parent().Next()
|
|
|
|
|
|
|
|
selectedExpression.Parent().Disown(selectedExpression)
|
|
|
|
|
|
|
|
if next == nil {
|
|
|
|
selectedExpression = previous
|
|
|
|
} else {
|
|
|
|
selectedExpression = next
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case stone.KeyBackspace:
|
|
|
|
if selectedExpression == nil { break }
|
|
|
|
|
|
|
|
parent := selectedExpression.Parent()
|
|
|
|
|
|
|
|
switch selectedExpression.(type) {
|
|
|
|
case *IntegerLiteral:
|
|
|
|
integer := selectedExpression.(*IntegerLiteral)
|
|
|
|
if integer.value == 0 { break }
|
|
|
|
integer.value /= int64(integer.displayRadix)
|
|
|
|
goto stopBackspacing
|
|
|
|
}
|
|
|
|
|
|
|
|
if parent == nil {
|
|
|
|
expressionRoot = nil
|
|
|
|
selectedExpression = nil
|
|
|
|
} else {
|
|
|
|
previous := selectedExpression.Parent().Previous()
|
|
|
|
|
|
|
|
selectedExpression.Parent().Disown(selectedExpression)
|
|
|
|
|
|
|
|
if previous != nil {
|
|
|
|
selectedExpression = previous
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stopBackspacing:
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case ' ':
|
|
|
|
insertGeneric(&IntegerLiteral { displayRadix: 10 })
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case
|
|
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
|
|
'a', 'b', 'c', 'd', 'e', 'f':
|
|
|
|
|
|
|
|
value := rune(button)
|
|
|
|
if value >= 'a' {
|
|
|
|
value -= 'a'
|
|
|
|
value += 10
|
|
|
|
} else {
|
|
|
|
value -= '0'
|
|
|
|
}
|
|
|
|
|
|
|
|
switch selectedExpression.(type) {
|
|
|
|
case *Operation:
|
|
|
|
insertGeneric (&IntegerLiteral {
|
|
|
|
displayRadix: 10,
|
|
|
|
value: int64(value),
|
|
|
|
})
|
|
|
|
|
|
|
|
case *IntegerLiteral:
|
|
|
|
integer := selectedExpression.(*IntegerLiteral)
|
|
|
|
integer.value *= int64(integer.displayRadix)
|
|
|
|
integer.value += int64(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case 'x':
|
|
|
|
switch selectedExpression.(type) {
|
|
|
|
case *IntegerLiteral:
|
|
|
|
integer := selectedExpression.(*IntegerLiteral)
|
|
|
|
switch integer.displayRadix {
|
|
|
|
case 2:
|
|
|
|
integer.displayRadix = 8
|
|
|
|
case 8:
|
|
|
|
integer.displayRadix = 10
|
|
|
|
case 10:
|
|
|
|
integer.displayRadix = 16
|
|
|
|
case 16:
|
|
|
|
integer.displayRadix = 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
|
|
|
|
case 'X':
|
|
|
|
switch selectedExpression.(type) {
|
|
|
|
case *IntegerLiteral:
|
|
|
|
integer := selectedExpression.(*IntegerLiteral)
|
|
|
|
switch integer.displayRadix {
|
|
|
|
case 2:
|
|
|
|
integer.displayRadix = 16
|
|
|
|
case 8:
|
|
|
|
integer.displayRadix = 2
|
|
|
|
case 10:
|
|
|
|
integer.displayRadix = 8
|
|
|
|
case 16:
|
|
|
|
integer.displayRadix = 10
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw()
|
|
|
|
application.Draw()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func onRelease (button stone.Button) {
|
|
|
|
switch button {
|
|
|
|
case stone.KeyLeftAlt, stone.KeyRightAlt:
|
|
|
|
inputState.alt = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func insertOperation (symbol rune) {
|
|
|
|
var opcode Opcode
|
|
|
|
switch (symbol) {
|
|
|
|
case '+': opcode = OpcodeAdd
|
|
|
|
case '-': opcode = OpcodeSubtract
|
|
|
|
case '*': opcode = OpcodeMultiply
|
|
|
|
case '/': opcode = OpcodeDivide
|
|
|
|
case 'p': opcode = OpcodePower
|
|
|
|
case 'r': opcode = OpcodeRoot
|
|
|
|
case '%': opcode = OpcodeModulo
|
|
|
|
case '|': opcode = OpcodeOr
|
|
|
|
case '~': opcode = OpcodeNot
|
|
|
|
case '&': opcode = OpcodeAnd
|
|
|
|
case '^': opcode = OpcodeXor
|
2022-11-20 23:07:46 -07:00
|
|
|
case '<': opcode = OpcodeLeftShift
|
|
|
|
case '>': opcode = OpcodeRightShift
|
2022-11-20 22:51:09 -07:00
|
|
|
case 'm': opcode = OpcodeMean
|
|
|
|
}
|
|
|
|
|
|
|
|
operation, isOperation := selectedExpression.(*Operation)
|
|
|
|
if isOperation {
|
|
|
|
if operation.opcode == OpcodeUnknown || inputState.alt {
|
|
|
|
operation.opcode = opcode
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newExpression := &Operation { opcode: opcode }
|
|
|
|
insertGeneric(newExpression)
|
|
|
|
}
|
|
|
|
|
|
|
|
func insertGeneric (expression Expression) {
|
|
|
|
defer func () {
|
|
|
|
selectedExpression = expression
|
|
|
|
} ()
|
|
|
|
|
|
|
|
if expressionRoot == nil {
|
|
|
|
// if there are no expressions, place this one down in the empty
|
|
|
|
// void
|
|
|
|
expressionRoot = expression
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
parent := selectedExpression.Parent()
|
|
|
|
|
|
|
|
if inputState.alt {
|
|
|
|
// if alt is held, swap the selected expression for the new one
|
|
|
|
if parent == nil {
|
|
|
|
expressionRoot = expression
|
|
|
|
} else {
|
|
|
|
selectedExpression.Parent().Swap(selectedExpression, expression)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
operation, isOperation := selectedExpression.(*Operation)
|
|
|
|
|
2022-11-21 16:27:44 -07:00
|
|
|
if isOperation && (len(operation.operands) < 1 || parent == nil) {
|
2022-11-20 22:51:09 -07:00
|
|
|
// if we have an empty operation selected, always insert the
|
|
|
|
// new expression into it
|
|
|
|
operation.Adopt(expression)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// else, insert the new expression after the selected expression
|
|
|
|
if parent == nil {
|
|
|
|
expressionRoot = expression
|
|
|
|
} else {
|
|
|
|
selectedExpression.Parent().Insert(selectedExpression, expression)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func onStart () {
|
|
|
|
redraw()
|
|
|
|
}
|
|
|
|
|
|
|
|
func redraw () {
|
|
|
|
width, _ := application.Size()
|
|
|
|
showLeftColumn = width > 44
|
|
|
|
|
|
|
|
if showLeftColumn {
|
|
|
|
drawNumberReadouts()
|
|
|
|
}
|
|
|
|
drawInput()
|
|
|
|
}
|
|
|
|
|
|
|
|
func drawInput () {
|
|
|
|
width, height := application.Size()
|
|
|
|
xOffset := 0
|
|
|
|
if showLeftColumn {
|
|
|
|
xOffset = 26
|
|
|
|
}
|
|
|
|
y := height - 1
|
|
|
|
|
|
|
|
inputWidth, _ := inputBuffer.Size()
|
|
|
|
newWidth := width - xOffset
|
|
|
|
if newWidth != inputWidth {
|
|
|
|
inputBuffer.SetSize(newWidth, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
inputBuffer.Clear()
|
|
|
|
if expressionRoot != nil {
|
|
|
|
expressionRoot.Render(&inputBuffer, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
for x := 0; x < newWidth; x ++ {
|
|
|
|
character := '_'
|
|
|
|
|
|
|
|
cell := inputBuffer.Cell(x, 0)
|
|
|
|
if cell.Rune() > 0 {
|
|
|
|
character = cell.Rune()
|
|
|
|
}
|
|
|
|
|
|
|
|
application.SetRune(x + xOffset, y, character)
|
|
|
|
|
|
|
|
if character == '_' {
|
|
|
|
application.SetColor(x + xOffset, y, stone.ColorDim)
|
|
|
|
} else {
|
|
|
|
application.SetColor(x + xOffset, y, cell.Color())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func drawNumberReadouts () {
|
|
|
|
_, height := application.Size()
|
2022-11-20 23:07:46 -07:00
|
|
|
|
|
|
|
clear(0, height - 10, 25, 10)
|
|
|
|
|
2022-11-20 22:51:09 -07:00
|
|
|
var number int64
|
|
|
|
var err error
|
|
|
|
if selectedExpression != nil {
|
|
|
|
number, err = selectedExpression.Solution()
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
application.SetDot((25 - len(err.Error())) / 2, height - 6)
|
|
|
|
fmt.Fprint(application, err.Error())
|
|
|
|
} else {
|
|
|
|
drawHexadecimal (0, height - 10, uint64(number))
|
|
|
|
drawDecimal (0, height - 8, uint64(number))
|
|
|
|
drawOctal (0, height - 6, uint64(number))
|
|
|
|
drawBinary (0, height - 4, uint64(number))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func clear (x, y, width, height int) {
|
|
|
|
for yy := 0; yy < height; yy ++ {
|
|
|
|
for xx := 0; xx < width; xx ++ {
|
|
|
|
application.SetRune(xx + x, yy + y, 0)
|
|
|
|
application.SetColor(xx + x, yy + y, stone.ColorForeground)
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func drawBinary (xOffset, yOffset int, number uint64) {
|
|
|
|
bitOffset := 64
|
|
|
|
|
|
|
|
for y := 0; y < 4; y ++ {
|
|
|
|
application.SetDot(xOffset, y + yOffset)
|
|
|
|
fmt.Fprint(application, bitOffset)
|
|
|
|
application.SetColor(xOffset, y + yOffset, stone.ColorBlue)
|
|
|
|
application.SetColor(xOffset + 1, y + yOffset, stone.ColorBlue)
|
|
|
|
|
|
|
|
for x := 0; x < 16; x ++ {
|
|
|
|
bitOffset --
|
|
|
|
|
|
|
|
trueX := x + xOffset + x / 4 + 3
|
|
|
|
character := '0' + rune((number >> bitOffset) & 0x1)
|
|
|
|
|
|
|
|
application.SetRune(trueX, y + yOffset, character)
|
|
|
|
if character == '0' {
|
|
|
|
application.SetColor (
|
|
|
|
trueX,
|
|
|
|
y + yOffset,
|
|
|
|
stone.ColorDim)
|
|
|
|
} else {
|
|
|
|
application.SetColor (
|
|
|
|
trueX,
|
|
|
|
y + yOffset,
|
|
|
|
stone.ColorForeground)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
application.SetDot(xOffset + 23, y + yOffset)
|
|
|
|
fmt.Fprint(application, bitOffset + 1)
|
|
|
|
application.SetColor(xOffset + 23, y + yOffset, stone.ColorBlue)
|
|
|
|
application.SetColor(xOffset + 24, y + yOffset, stone.ColorBlue)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func drawHexadecimal (xOffset, yOffset int, number uint64) {
|
|
|
|
bitOffset := 64
|
|
|
|
|
|
|
|
application.SetDot(xOffset, yOffset)
|
|
|
|
fmt.Fprint(application, "16")
|
|
|
|
application.SetColor(xOffset, yOffset, stone.ColorBlue)
|
|
|
|
application.SetColor(xOffset + 1, yOffset, stone.ColorBlue)
|
|
|
|
|
|
|
|
for x := 0; x < 16; x ++ {
|
|
|
|
bitOffset -= 4
|
|
|
|
|
|
|
|
trueX := x + xOffset + x / 4 + 6
|
|
|
|
character := rune((number >> bitOffset) & 0xF)
|
|
|
|
if character < 10 {
|
|
|
|
character += '0'
|
|
|
|
} else {
|
|
|
|
character += 'A' - 10
|
|
|
|
}
|
|
|
|
|
|
|
|
application.SetRune(trueX, yOffset, character)
|
|
|
|
if character == '0' {
|
|
|
|
application.SetColor (
|
|
|
|
trueX,
|
|
|
|
yOffset,
|
|
|
|
stone.ColorDim)
|
|
|
|
} else {
|
|
|
|
application.SetColor (
|
|
|
|
trueX,
|
|
|
|
yOffset,
|
|
|
|
stone.ColorForeground)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func drawDecimal (xOffset, yOffset int, number uint64) {
|
|
|
|
var divisor uint64 = 10000000000000000000
|
|
|
|
|
|
|
|
application.SetDot(xOffset, yOffset)
|
|
|
|
fmt.Fprint(application, "10")
|
|
|
|
application.SetColor(xOffset, yOffset, stone.ColorBlue)
|
|
|
|
application.SetColor(xOffset + 1, yOffset, stone.ColorBlue)
|
|
|
|
|
|
|
|
for x := 0; x < 20; x ++ {
|
|
|
|
trueX := x + xOffset + 5
|
|
|
|
character := rune((number / divisor) % 10) + '0'
|
|
|
|
|
|
|
|
application.SetRune(trueX, yOffset, character)
|
|
|
|
if character == '0' {
|
|
|
|
application.SetColor (
|
|
|
|
trueX,
|
|
|
|
yOffset,
|
|
|
|
stone.ColorDim)
|
|
|
|
} else {
|
|
|
|
application.SetColor (
|
|
|
|
trueX,
|
|
|
|
yOffset,
|
|
|
|
stone.ColorForeground)
|
|
|
|
}
|
|
|
|
divisor /= 10
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func drawOctal (xOffset, yOffset int, number uint64) {
|
|
|
|
// fuck octal i hate it almost as much as decimal
|
|
|
|
bitOffset := 63
|
|
|
|
|
|
|
|
application.SetDot(xOffset, yOffset)
|
|
|
|
fmt.Fprint(application, "08")
|
|
|
|
application.SetColor(xOffset, yOffset, stone.ColorBlue)
|
|
|
|
application.SetColor(xOffset + 1, yOffset, stone.ColorBlue)
|
|
|
|
|
|
|
|
for x := 0; x < 21; x ++ {
|
|
|
|
bitOffset -= 3
|
|
|
|
|
|
|
|
trueX := x + xOffset + 4
|
|
|
|
character := '0' + rune((number >> bitOffset) % 8)
|
|
|
|
|
|
|
|
application.SetRune(trueX, yOffset, character)
|
|
|
|
if character == '0' {
|
|
|
|
application.SetColor (
|
|
|
|
trueX,
|
|
|
|
yOffset,
|
|
|
|
stone.ColorDim)
|
|
|
|
} else {
|
|
|
|
application.SetColor (
|
|
|
|
trueX,
|
|
|
|
yOffset,
|
|
|
|
stone.ColorForeground)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|