mathpan/main.go

496 lines
10 KiB
Go

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()
case '+', '-', '*', '/', 'p', 'r', '%', '|', '~', '&', '^', 'm':
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
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)
if (isOperation && len(operation.operands) < 1) || parent == nil {
// 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()
var number int64
var err error
if selectedExpression != nil {
number, err = selectedExpression.Solution()
}
if err != nil {
clear(0, height - 10, 25, 10)
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)
}
}
}