You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nimf/builtins.go

903 lines
18 KiB

package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"sort"
"strings"
"time"
"git.rawtext.club/sloum/nimf/termios"
)
var builtins = make(map[string]func())
func initBuiltins() {
// Math
builtins["+"] = add
builtins["-"] = subtract
builtins["*"] = multiply
builtins["/"] = divide
builtins["%"] = mod
builtins["&"] = bitAnd
builtins["|"] = bitOr
builtins["xor"] = bitXor
builtins["<<"] = bitLeftShift
builtins[">>"] = bitRightShift
// Stack
builtins["drop"] = drop
builtins["dup"] = dup
builtins["swap"] = swap
builtins["over"] = over
builtins["roll"] = roll
builtins["pick"] = pick
builtins[".s"] = showStack
builtins[".r"] = showAddressStack
builtins["<r"] = moveToAddressStack
builtins["r>"] = moveFromAddressStack
builtins["r@"] = copyFromAddressStack
builtins["rdepth"] = rdepth
builtins["depth"] = depth
builtins["clearstack"] = clearStack
// Logical
builtins[">"] = gt
builtins["<"] = lt
builtins["="] = equal
// System
builtins["bye"] = bye
builtins["halt"] = sysExit
builtins["words"] = words
builtins["sleep"] = sleep
builtins["see"] = showSubroutine
builtins["if"] = nimfIf
builtins["else"] = nimfElse
builtins["then"] = nimfThen
builtins["do"] = do
builtins["loop"] = loop
builtins["error"] = throwError
builtins["winsize"] = winsize
builtins["timenow"] = timenow
builtins["get-env"] = getEnv
builtins["exit"] = exitWord
// Memory
builtins["var"] = variable
builtins["svar"] = stringVariable
builtins["allot"] = allotMemory
builtins["!"] = writeToMemory
builtins["set"] = writeToMemory
builtins["s!"] = writeStringToMemory
builtins["set-string"] = writeStringToMemory
builtins["@"] = readFromMemory
builtins["get"] = readFromMemory
builtins["s@"] = readStringIntoTempBuffer
builtins["get-string"] = readStringIntoTempBuffer
builtins["sr@"] = readRawStringIntoTempBuffer
builtins["get-raw-string"] = readRawStringIntoTempBuffer
builtins["+!"] = memoryValueAddition
builtins["local"] = localVar
builtins["lallot"] = localAllotMemory
// IO
builtins[","] = output
builtins["cr"] = newline
builtins["space"] = space
builtins["input"] = bufferedInputRequest
builtins["key"] = getKeyPress
builtins["emit"] = emit
builtins["tcp.connect"] = connect
builtins["tcp.close"] = connClose
builtins["tcp.write"] = connWrite
builtins["tcp.read"] = connReadAll
builtins["file.open"] = openFile
builtins["file.exists?"] = fileExists
builtins["file.read"] = readLine
builtins["file.write"] = writeToFile
builtins["file.close"] = closeFile
builtins["subproc.create"] = addSubprocess
builtins["subproc.send"] = pipeToSubprocess
builtins["subproc.exec"] = runSubprocess
builtins["subproc.cd"] = procWorkingDir
builtins["subproc.ready?"] = nilSubprocess
builtins["pwd"] = getPwd
builtins["cd"] = changeDir
}
//==================
// MATH
//==================
func add() {
data.Push(data.Pop() + data.Pop())
}
func subtract() {
second := data.Pop()
data.Push(data.Pop() - second)
}
func multiply() {
data.Push(data.Pop() * data.Pop())
}
func divide() {
second := data.Pop()
data.Push(data.Pop() / second)
}
func mod() {
second := data.Pop()
data.Push(data.Pop() % second)
}
func bitAnd() {
second := data.Pop()
data.Push(data.Pop() & second)
}
func bitOr() {
second := data.Pop()
data.Push(data.Pop() | second)
}
func bitXor() {
second := data.Pop()
data.Push(data.Pop() ^ second)
}
func bitLeftShift() {
second := data.Pop()
data.Push(data.Pop() << uint(second))
}
func bitRightShift() {
second := data.Pop()
data.Push(data.Pop() >> uint(second))
}
//==================
// STACK
//==================
func showStack() {
fmt.Printf("<%d> %v ", data.Pointer+1, data.stack[:data.Pointer+1])
memory.Set(outputPrintedFlag, -1)
}
func showAddressStack() {
fmt.Printf("<%d> %v ", address.Pointer+1, address.stack[:address.Pointer+1])
memory.Set(outputPrintedFlag, -1)
}
func clearStack() {
data.Pointer = -1
}
func depth() {
data.Push(data.Pointer + 1)
}
func drop() {
data.Pop()
}
func roll() {
count := data.Peep()
if count <= 0 {
data.Pop()
return
}
swap()
address.Push(data.Pop())
data.Push(1)
subtract()
roll()
data.Push(address.Pop())
swap()
}
func pick() {
count := data.Pop()
if count <= 0 {
return
}
if data.Pointer-count < 0 {
handleError(fmt.Errorf("Stack underflow"))
return
}
data.Push(data.stack[data.Pointer-count])
}
func swap() {
first := data.Pop()
last := data.Pop()
data.Push(first)
data.Push(last)
}
func over() {
first := data.Pop()
last := data.Pop()
data.Push(last)
data.Push(first)
data.Push(last)
}
func dup() {
item := data.Pop()
data.Push(item)
data.Push(item)
}
func moveToAddressStack() {
address.Push(data.Pop())
}
func moveFromAddressStack() {
data.Push(address.Pop())
}
func copyFromAddressStack() {
data.Push(address.Peep())
}
func rdepth() {
data.Push(address.Pointer + 1)
}
//==================
// LOGICAL
//==================
func gt() {
data.Push(bool2Int((data.Pop() < data.Pop())))
}
func lt() {
data.Push(bool2Int((data.Pop() > data.Pop())))
}
func equal() {
data.Push(bool2Int((data.Pop() == data.Pop())))
}
//==================
// SYSTEM
//==================
func bye() {
os.Exit(0)
}
func timenow() {
now := time.Now().Unix()
data.Push(int(now))
}
func sysExit() {
code := data.Pop()
os.Exit(code)
}
func throwError() {
if memory.Get(actionModeFlag) == 0 {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body", "error"))
return
}
length := memory.Get(tempString)
var out strings.Builder
if length < 1 {
out.WriteString("Generic error, no message provided")
} else {
intSlice := memory.memory[51 : 50+memory.memory[50]+1]
for _, num := range intSlice {
out.WriteRune(rune(num))
}
}
handleError(fmt.Errorf("%s", out.String()))
}
func sleep() {
amount := data.Pop()
time.Sleep(time.Duration(amount) * time.Millisecond)
}
func words() {
wordList := make([]string, 0, 25)
for k := range builtins {
wordList = append(wordList, k)
}
for k := range names {
if strings.Index(k, ".private.") > -1 {
continue
}
wordList = append(wordList, k)
}
sort.Strings(wordList)
out := strings.Join(wordList, " ")
fmt.Print(out)
memory.Set(outputPrintedFlag, -1)
}
func showSubroutine() {
name, err := scanner.Read()
if err != nil {
handleError(fmt.Errorf("show subroutine panic"))
return
}
nameString := name.String()
if val, ok := names[nameString]; ok {
fmt.Printf("\033[3m%s\033[23m ", buildWordDefinition(nameString, val))
} else if _, ok := builtins[nameString]; ok {
fmt.Printf("\033[3m%s is a built-in\033[23m ", name)
} else {
fmt.Printf("\033[31m%s is not a known word\033[0m ", name)
}
memory.Set(outputPrintedFlag, -1)
}
func do() {
if memory.Get(actionModeFlag) == 0 {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body", "do"))
return
} else {
memory.Put(actionLoopCounter, &address)
}
}
func loop() {
if memory.Get(actionModeFlag) == 0 {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body", "loop"))
return
} else {
truthiness := data.Pop()
if truthiness != 0 {
// Conditional is still true: loop
memory.Set(actionLoopCounter, address.Peep())
memory.Set(loopSetFlag, -1) // Set the loop return flag
} else {
// Conditional is false: break the looping
address.Pop()
memory.Set(loopSetFlag, 0) // Set the loop return flag
}
}
}
// Starts an if branch
func nimfIf() {
if memory.Get(actionModeFlag) == 0 {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body", "if"))
return
} else {
// Increment depth
memory.Set(conditionalDepthCounter, memory.Get(conditionalDepthCounter)+1)
// Determine current truthiness of the condition
truthiness := data.Peep()
if truthiness != 0 {
truthiness = -1
}
// If currently set to run pop the data stack
// else do not pop (like you would for a 'live' if block
if memory.ValueEquals(conditionalBranchRunFlag, -1) {
data.Pop()
}
// Push the run state flag to the address stack
// TODO check on this, Im not sure it is needed
memory.Put(conditionalBranchRunFlag, &address)
// If run state is false push 0
if memory.ValueEquals(conditionalBranchRunFlag, 0) {
// This ensures that dead else branches never run
address.Push(-1)
} else {
memory.Set(conditionalBranchRunFlag, truthiness)
address.Push(truthiness)
}
}
}
// Starts the else in an if
func nimfElse() {
if memory.Get(actionModeFlag) == 0 {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body", "else"))
return
} else if memory.Get(conditionalDepthCounter) < 1 {
handleError(fmt.Errorf("The word %q was found (without a preceeding %q)", "else", "if"))
return
} else {
truthiness := address.Peep()
if truthiness == 0 {
memory.Set(conditionalBranchRunFlag, -1)
} else {
memory.Set(conditionalBranchRunFlag, 0)
}
}
}
// Ends a conditional
func nimfThen() {
if memory.Get(actionModeFlag) == 0 {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body", "then"))
return
} else if memory.Get(conditionalDepthCounter) < 1 {
handleError(fmt.Errorf("The word %q was found (without a preceeding %q)", "then", "if"))
return
} else {
address.Pop()
memory.Set(conditionalDepthCounter, memory.Get(conditionalDepthCounter)-1)
priorRunState := address.Pop()
if memory.ValueEquals(conditionalDepthCounter, 0) {
memory.Set(conditionalBranchRunFlag, -1)
} else {
memory.Set(conditionalBranchRunFlag, priorRunState)
}
}
}
func winsize() {
col, row := termios.GetWindowSize()
data.Push(col)
data.Push(row)
}
func getEnv() {
address := data.Pop()
str, err := nimfStringToGolang(address)
if err != nil {
data.Push(0)
return
}
val, ok := os.LookupEnv(str)
if !ok {
data.Push(0)
return
}
golangStringToNimf(val)
data.Push(-1)
}
func exitWord() {
if memory.Get(actionModeFlag) == 0 {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body", "exit"))
return
} else {
memory.Set(exitFlag, -1)
}
}
//==================
// MEMORY
//==================
func variable() {
memory.Set(varAssignmentFlag, -1)
}
func stringVariable() {
memory.Set(svarAssignmentFlag, -1)
}
func writeToMemory() {
location := data.Pop()
value := data.Pop()
memory.Set(location, value)
}
func readFromMemory() {
memory.Put(data.Pop(), &data)
}
func allotMemory() {
memory.Allocate(data.Pop())
}
func localAllotMemory() {
amt := data.Pop()
memory.lpointer += amt
}
func memoryValueAddition() {
location := data.Pop()
value := data.Pop()
current := memory.Get(location)
memory.Set(location, value+current)
}
func localVar() {
memory.Set(localAssignmentFlag, -1)
}
func writeStringToMemory() {
address := data.Pop()
memory.SetString(address)
}
func readStringIntoTempBuffer() {
address := data.Pop()
length := memory.Get(address)
if length == 0 {
memory.Set(tempString, 0)
memory.tspointer = 50
return
}
end := length + address
start := address + 1
memory.GetString(start, end)
}
func readRawStringIntoTempBuffer() {
end := data.Pop()
start := data.Pop()
memory.GetString(start, end)
}
//==================
// IO
//==================
func output() {
fmt.Printf("%d", data.Pop())
memory.Set(outputPrintedFlag, -1)
}
func newline() {
fmt.Print("\n")
memory.Set(outputPrintedFlag, -1)
}
func space() {
fmt.Print(" ")
memory.Set(outputPrintedFlag, -1)
}
func emit() {
charCode := data.Pop()
fmt.Printf("%c", charCode)
memory.Set(outputPrintedFlag, -1)
}
func getKeyPress() {
termios.SetCharMode()
reader := bufio.NewReader(os.Stdin)
char, _, err := reader.ReadRune()
if err != nil {
termios.SetLineMode()
return
}
data.Push(int(char))
termios.SetLineMode()
}
func bufferedInputRequest() {
reader := bufio.NewReader(os.Stdin)
text, err := reader.ReadString('\n')
if err != nil {
handleError(err)
return
}
text = strings.TrimSuffix(text, "\n")
golangStringToNimf(text)
}
func connect() {
memLocation := data.Pop()
host, err := nimfStringToGolang(memLocation)
if err != nil {
data.Push(0)
return
}
netConnection, err = net.Dial("tcp", host)
if err != nil {
data.Push(0)
return
}
data.Push(-1)
}
func connWrite() {
memLocation := data.Pop()
msg, err := nimfStringToGolang(memLocation)
if err != nil {
data.Push(0)
return
}
_, err = netConnection.Write([]byte(msg))
if err != nil {
data.Push(0)
return
}
data.Push(-1)
}
func connReadAll() {
result, err := ioutil.ReadAll(netConnection)
if err != nil {
data.Push(0)
return
}
golangStringToNimf(string(result))
data.Push(-1)
}
func connClose() {
if netConnection == nil {
return
}
netConnection.Close()
netConnection = nil
}
func openFile() {
mode := data.Pop()
stringAddress := data.Pop()
if limitIO {
handleError(fmt.Errorf("Nimf is running in a mode that does not allow file IO"))
return
}
fileName, err := nimfStringToGolang(stringAddress)
if err != nil {
handleError(fmt.Errorf("Null path (0 length) provided to file-open"))
return
}
var modeFlag int
switch mode {
case int('r'): // read by line
modeFlag = os.O_RDONLY
case int('R'): // read whole file in and close file
readWholeFile(fileName)
return
case int('w'):
modeFlag = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
case int('a'):
modeFlag = os.O_WRONLY | os.O_CREATE | os.O_APPEND
}
file, err = os.OpenFile(fileName, modeFlag, 0644)
if err != nil {
data.Push(0)
handleError(err)
return
}
if mode == int('r') {
fileReader = bufio.NewReader(file)
}
}
func fileExists() {
stringAddress := data.Pop()
if limitIO {
handleError(fmt.Errorf("Nimf is running in a mode that does not allow file IO"))
return
}
str, err := nimfStringToGolang(stringAddress)
if err != nil {
handleError(fmt.Errorf("Invalid string provided to file-exists?"))
return
}
_, err = os.Stat(str)
if err != nil {
data.Push(0)
} else {
data.Push(-1)
}
}
func readWholeFile(fileName string) {
b, err := ioutil.ReadFile(fileName)
if err != nil {
handleError(err)
return
}
text := string(b)
length := len(text)
if length > 29999-51 {
text = text[:29999-51]
}
golangStringToNimf(text)
}
func readLine() {
line, err := fileReader.ReadString('\n')
if err == io.EOF {
data.Push(0)
} else if err != nil {
handleError(err)
return
} else {
golangStringToNimf(line)
data.Push(-1)
}
}
func writeToFile() {
stringAddress := data.Pop()
str, err := nimfStringToGolang(stringAddress)
if err != nil {
handleError(fmt.Errorf("Invalid string provided to file-write"))
return
}
_, err = fmt.Fprint(file, str)
if err != nil {
handleError(err)
return
}
}
func closeFile() {
if file == nil {
return
}
err := file.Close()
if err != nil {
handleError(err)
return
}
file = nil
}
func addSubprocess() {
address := data.Pop()
if limitIO {
handleError(fmt.Errorf("Nimf is running in a mode that does not allow file IO"))
return
}
procString, err := nimfStringToGolang(address)
if err != nil {
handleError(fmt.Errorf("Invalid process string provided to subproc.create"))
return
}
procFields := strings.Fields(procString)
processWrite = false
if len(procFields) > 1 {
process = exec.Command(procFields[0], procFields[1:]...)
} else {
process = exec.Command(procFields[0])
}
}
func pipeToSubprocess() {
address := data.Pop()
if limitIO {
handleError(fmt.Errorf("Nimf is running in a mode that does not allow file IO"))
return
}
dataString, err := nimfStringToGolang(address)
if err != nil {
handleError(fmt.Errorf("Invalid input string provided to subproc.send"))
return
}
if process == nil {
handleError(fmt.Errorf("There is not a current process, create one with subproc.create"))
return
}
stdin, err := process.StdinPipe()
if err != nil {
handleError(err)
return
}
go func() {
defer stdin.Close()
_, err := fmt.Fprint(stdin, dataString)
if err != nil {
handleError(err)
return
}
processWrite = true
}()
}
func runSubprocess() {
mode := data.Pop()
if limitIO {
handleError(fmt.Errorf("Nimf is running in a mode that does not allow file IO"))
return
}
switch mode {
case int('q'):
// Quick/Non-Interactive Mode
out, err := process.CombinedOutput()
if err != nil {
handleError(err)
return
}
output := string(out)
if len(output) > 0 {
golangStringToNimf(string(out))
} else {
memory.Set(50, 0)
}
case int('i'):
// Interactive Mode
if !processWrite {
process.Stdin = os.Stdin
}
process.Stdout = os.Stdout
err := process.Run()
if err != nil {
handleError(err)
return
}
case int('b'):
// Background Mode
err := process.Start()
if err != nil {
handleError(err)
return
}
default:
handleError(fmt.Errorf("Invalid mode sent to subproc.exec"))
return
}
process = nil
}
func procWorkingDir() {
pathAddress := data.Pop()
if limitIO {
handleError(fmt.Errorf("Nimf is running in a mode that does not allow file IO"))
return
}
path, err := nimfStringToGolang(pathAddress)
if err != nil {
handleError(fmt.Errorf("Invalid path string provided to subproc.cd"))
return
}
path = expandedAbsFilepath(path)
process.Dir = path
}
func nilSubprocess() {
if process == nil {
data.Push(0)
} else {
data.Push(-1)
}
}
func getPwd() {
if limitIO {
handleError(fmt.Errorf("Nimf is running in a mode that does not allow file IO"))
return
}
wd, err := os.Getwd()
if err != nil {
handleError(err)
return
}
golangStringToNimf(wd)
}
func changeDir() {
if limitIO {
handleError(fmt.Errorf("Nimf is running in a mode that does not allow file IO"))
return
}
strAddr := data.Pop()
path, err := nimfStringToGolang(strAddr)
if err != nil {
return
}
err = os.Chdir(expandedAbsFilepath(path))
if err != nil {
handleError(err)
return
}
}