Beginning of rewrite with lex/parse phases and new scanner, less forthy string, etc. Strings arent working right, but other things are.

rewrite
sloum 2 months ago
parent b256a95bb6
commit 3ee35d4a68

@ -7,10 +7,8 @@ import (
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"os/exec"
"sort"
"strconv"
"strings"
"time"
@ -59,18 +57,18 @@ func initBuiltins() {
builtins["words"] = words
builtins["\""] = toggleStringOutput
builtins["sleep"] = sleep
builtins[":"] = addSubroutine
builtins[";"] = func() {} // Handled as part of addSubroutine
builtins["module-end"] = func() {} // Ends module reading
builtins["("] = startComment
builtins[")"] = endComment
builtins[":"] = func() {}
builtins[";"] = func() {}
builtins["module-end"] = func() {}
builtins["("] = func() {}
builtins[")"] = func() {}
builtins["see"] = showSubroutine
builtins["if"] = nimfIf
builtins["else"] = nimfElse
builtins["then"] = nimfThen
builtins["do"] = do
builtins["loop"] = loop
builtins["inline"] = inlineCode
builtins["inline"] = func() {}
builtins["error"] = throwError
builtins["winsize"] = winsize
builtins["timenow"] = timenow
@ -93,7 +91,7 @@ func initBuiltins() {
builtins["get-raw-string"] = readRawStringIntoTempBuffer
builtins["+!"] = memoryValueAddition
builtins["local"] = localVar
builtins["lallot"]= localAllotMemory
builtins["lallot"] = localAllotMemory
// IO
builtins[","] = output
@ -111,9 +109,9 @@ func initBuiltins() {
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.create"] = addSubprocess
builtins["subproc.send"] = pipeToSubprocess
builtins["subproc.exec"] = runSubprocess
builtins["subproc.cd"] = procWorkingDir
builtins["subproc.ready?"] = nilSubprocess
}
@ -311,13 +309,19 @@ func sysExit() {
func throwError() {
if memory.Get(actionModeFlag) == 0 {
scanner.SetError(fmt.Errorf("'error' found outside of subroutine body"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body on line %d of %s", "error", tok.line, tok.file))
}
return
}
length := memory.Get(tempString)
var out strings.Builder
if length < 1 {
out.WriteString("Generic, no message provided")
out.WriteString("Generic error, no message provided")
} else {
intSlice := memory.memory[51 : 50+memory.memory[50]+1]
for _, num := range intSlice {
@ -325,7 +329,7 @@ func throwError() {
}
}
scanner.SetError(fmt.Errorf(out.String()))
handleError(fmt.Errorf("%s", out.String()))
}
func sleep() {
@ -351,11 +355,15 @@ func words() {
}
func showSubroutine() {
name := scanner.Next()
if val, ok := names[name]; ok {
formula := strings.Join(val, " ")
fmt.Printf("\033[3m: %s %s ;\033[23m ", name, formula)
} else if _, ok := builtins[name]; ok {
name, err := scanner.Read()
if err != nil {
// TODO make this use an actual error system
panic("show subroutine panic")
}
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)
@ -363,164 +371,15 @@ func showSubroutine() {
memory.Set(outputPrintedFlag, -1)
}
func addSubroutine() {
// TODO: Needs checks to make sure that ifs do not overlap loop bounds
overwrite := false
name := scanner.Next()
if nameExists(name) {
overwrite = true
}
ignore := make(map[string]bool)
sr := make([]string, 0, 5)
token := scanner.Next()
for token != ";" {
if (len(token) == 2 || len(token) == 3) && token[0] == '`' {
chars := []rune(token)
switch len(chars) {
case 2:
token = strconv.Itoa(int(chars[1]))
case 3:
sub := convertEscapes(token)
chars = []rune(sub)
if len(chars) != 2 {
break
}
token = strconv.Itoa(int(chars[1]))
}
}
if token == "(" || token == ")" {
memory.Set(commentFlag, invertIntBool(memory.Get(commentFlag)))
} else if token == "\"" {
memory.Set(stringModeFlag, invertIntBool(memory.Get(stringModeFlag)))
} else if token == ":" && memory.Get(stringModeFlag) == 0 {
scanner.SetError(fmt.Errorf("Nested subroutine creation statements are not allowed"))
return
}
// If in a comment or a string just add the token
if memory.Get(commentFlag) != 0 || memory.Get(stringModeFlag) != 0 {
sr = append(sr, token)
} else {
if !scanner.isFile && !tokenIsValid(token) {
if _, ok := ignore[token]; !ok {
scanner.SetError(fmt.Errorf("%q, in subroutine %q, does not exist", token, name))
return
}
}
if token == "if" {
memory.Set(branchDepthFVP, memory.Get(branchDepthFVP)+1) // ++ branch depth
} else if (token == "else" || token == "then") && memory.Get(branchDepthFVP) == 0 {
scanner.SetError(fmt.Errorf("%s encountered before if in subroutine %q", token, name))
return
} else if token == "then" {
memory.Set(branchDepthFVP, memory.Get(branchDepthFVP)-1) // -- branch depth
} else if token == "do" {
memory.Set(loopDepthFVP, memory.Get(loopDepthFVP)+1) // ++ loop depth
} else if token == "loop" && memory.ValueEquals(loopDepthFVP, 0) {
scanner.SetError(fmt.Errorf("%s encountered before do in subroutine %q", token, name))
return
} else if token == "loop" {
memory.Set(loopDepthFVP, memory.Get(loopDepthFVP)-1) // -- loop depth
} else if token == "local" {
sr = append(sr, token)
token = scanner.Next()
ignore[token] = true
}
sr = append(sr, token)
}
token = scanner.Next()
}
// Make sure we are exiting the subroutine in a good state
if memory.Get(branchDepthFVP) != 0 {
scanner.SetError(fmt.Errorf("Unclosed if statement in subroutine %q, needs word: then", name))
return
} else if memory.Get(commentFlag) != 0 {
scanner.SetError(fmt.Errorf("Unclosed comment in subroutine %q", name))
return
} else if memory.Get(stringModeFlag) != 0 {
scanner.SetError(fmt.Errorf("Unclosed string in subroutine %q", name))
return
} else if memory.Get(loopDepthFVP) != 0 {
scanner.SetError(fmt.Errorf("Unclosed do/loop in subroutine %q", name))
}
names[name] = sr
if overwrite && !scanner.isFile {
scanner.AddOutput(fmt.Sprintf("\033[3m%q\033[0m updated", name))
} else if !scanner.isFile {
scanner.AddOutput(fmt.Sprintf("\033[3m%q\033[0m compiled", name))
}
}
func inlineCode() {
length := memory.Get(tempString)
nameSlice := make([]rune, length, length)
for i, _ := range nameSlice {
nameSlice[i] = rune(memory.Get(i + 51))
}
name := strings.TrimSpace(string(nameSlice))
// Add check for absolute path (ie. if path exists, use it)
if !strings.HasSuffix(name, ".nh") {
name = name + ".nh"
}
if _, ok := inlinedFiles[name]; ok {
if !scanner.isFile {
fmt.Fprintf(os.Stderr, "Already inlined %q\n", name)
}
return
}
xdg_lib_path := os.Getenv("XDG_DATA_HOME")
if xdg_lib_path == "" {
xdg_lib_path = "~/.local/share/nimf/"
} else {
xdg_lib_path = filepath.Join(xdg_lib_path, "nimf")
}
paths := []string{
"./",
"./lib",
expandedAbsFilepath(xdg_lib_path),
"/usr/local/lib/nimf",
}
var f string
var err error
for _, p := range paths {
path := filepath.Join(p, name)
f, err = readFile(path)
if err == nil {
break
}
}
if err != nil {
panic(fmt.Errorf("Library file %q not found", name))
}
inlinedFiles[name] = true
// Make a deep copy of the existing scanner state
oldscanner := scanner
oldscanner.tokens = make([]string, len(scanner.tokens))
copy(scanner.tokens, oldscanner.tokens)
// Replace the scanner state with a new one for this file
scanner = newTokenizer(strings.NewReader(f), true)
for {
word := scanner.Next()
if word == "module-end" {
break
}
execute(word)
}
// Return to the old scanner state
scanner = oldscanner
}
func do() {
if memory.Get(actionModeFlag) == 0 {
scanner.SetError(fmt.Errorf("'do' found outside of subroutine body"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body on line %d of %s", "do", tok.line, tok.file))
}
} else {
memory.Put(actionLoopCounter, &address)
}
@ -528,7 +387,13 @@ func do() {
func loop() {
if memory.Get(actionModeFlag) == 0 {
scanner.SetError(fmt.Errorf("'loop' found outside of subroutine body"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body on line %d of %s", "loop", tok.line, tok.file))
}
} else {
truthiness := data.Pop()
if truthiness != 0 {
@ -546,7 +411,13 @@ func loop() {
// Starts an if branch
func nimfIf() {
if memory.Get(actionModeFlag) == 0 {
scanner.SetError(fmt.Errorf("'if' found outside of subroutine body"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body on line %d of %s", "if", tok.line, tok.file))
}
} else {
// Increment depth
memory.Set(conditionalDepthCounter, memory.Get(conditionalDepthCounter)+1)
@ -581,9 +452,21 @@ func nimfIf() {
// Starts the else in an if
func nimfElse() {
if memory.Get(actionModeFlag) == 0 {
scanner.SetError(fmt.Errorf("'else' found outside of subroutine body"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body on line %d of %s", "else", tok.line, tok.file))
}
} else if memory.Get(conditionalDepthCounter) < 1 {
scanner.SetError(fmt.Errorf("'else' encountered without preceeding 'if'"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found (without a preceeding %q) line %d of %s", "else", "if", tok.line, tok.file))
}
} else {
truthiness := address.Peep()
if truthiness == 0 {
@ -597,9 +480,21 @@ func nimfElse() {
// Ends a conditional
func nimfThen() {
if memory.Get(actionModeFlag) == 0 {
scanner.SetError(fmt.Errorf("'then' found outside of subroutine body"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body on line %d of %s", "then", tok.line, tok.file))
}
} else if memory.Get(conditionalDepthCounter) < 1 {
scanner.SetError(fmt.Errorf("'then' encountered without preceeding 'if'"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found (without a preceeding %q) line %d of %s", "then", "if", tok.line, tok.file))
}
} else {
address.Pop()
memory.Set(conditionalDepthCounter, memory.Get(conditionalDepthCounter)-1)
@ -612,18 +507,6 @@ func nimfThen() {
}
}
func startComment() {
memory.Set(commentFlag, -1)
}
func endComment() {
if memory.Get(commentFlag) == 0 {
scanner.SetError(fmt.Errorf("Unexpected ')' found while not in comment"))
} else {
memory.Set(commentFlag, 0)
}
}
func winsize() {
col, row := termios.GetWindowSize()
data.Push(col)
@ -648,7 +531,13 @@ func getEnv() {
func exitWord() {
if memory.Get(actionModeFlag) == 0 {
scanner.SetError(fmt.Errorf("'exit' found outside of subroutine body"))
tok, err := scanner.PeekLast()
if err != nil {
// TODO this will need a better message that makes it clear than there was an error throwing an error
handleError(err)
} else {
handleError(fmt.Errorf("The word %q was found outside of a subroutine body on line %d of %s", "exit", tok.line, tok.file))
}
} else {
memory.Set(exitFlag, -1)
}
@ -751,7 +640,7 @@ func getKeyPress() {
char, _, err := reader.ReadRune()
if err != nil {
scanner.SetError(fmt.Errorf("key; %s", err))
termios.SetLineMode()
return
}
data.Push(int(char))
@ -821,7 +710,7 @@ func openFile() {
mode := data.Pop()
stringAddress := data.Pop()
fileName, err := nimfStringToGolang(stringAddress)
if err != nil{
if err != nil {
panic("Null path (0 length) provided to file-open")
}
@ -830,7 +719,7 @@ func openFile() {
case int('r'): // read by line
modeFlag = os.O_RDONLY
case int('R'): // read whole file in and close file
readWholeFile(fileName)
readWholeFile(fileName)
return
case int('w'):
modeFlag = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
@ -910,32 +799,32 @@ func closeFile() {
}
func addSubprocess() {
address := data.Pop()
procString, err := nimfStringToGolang(address)
if err != nil {
panic("Invalid process string provided to subproc.create")
}
procFields := strings.Fields(procString)
address := data.Pop()
procString, err := nimfStringToGolang(address)
if err != nil {
panic("Invalid process string provided to subproc.create")
}
procFields := strings.Fields(procString)
processWrite = false
if len(procFields) > 1 {
process = exec.Command(procFields[0], procFields[1:]...)
} else {
process = exec.Command(procFields[0])
}
if len(procFields) > 1 {
process = exec.Command(procFields[0], procFields[1:]...)
} else {
process = exec.Command(procFields[0])
}
}
func pipeToSubprocess() {
address := data.Pop()
dataString, err := nimfStringToGolang(address)
if err != nil {
panic("Invalid input string provided to subproc.send")
}
if process == nil {
panic("There is not a current process, create one with subproc.create")
}
address := data.Pop()
dataString, err := nimfStringToGolang(address)
if err != nil {
panic("Invalid input string provided to subproc.send")
}
if process == nil {
panic("There is not a current process, create one with subproc.create")
}
stdin, err := process.StdinPipe()
if err != nil {
panic(err.Error())
panic(err.Error())
}
go func() {
@ -949,22 +838,22 @@ func pipeToSubprocess() {
}
func runSubprocess() {
mode := data.Pop()
switch mode {
case int('q'):
// Quick/Non-Interactive Mode
out, err := process.CombinedOutput()
if err != nil {
panic(err.Error())
}
mode := data.Pop()
switch mode {
case int('q'):
// Quick/Non-Interactive Mode
out, err := process.CombinedOutput()
if err != nil {
panic(err.Error())
}
output := string(out)
if len(output) > 0 {
golangStringToNimf(string(out))
} else {
memory.Set(50, 0)
}
case int('i'):
// Interactive Mode
case int('i'):
// Interactive Mode
if !processWrite {
process.Stdin = os.Stdin
}
@ -973,24 +862,24 @@ func runSubprocess() {
if err != nil {
panic(err.Error())
}
case int('b'):
// Background Mode
err := process.Start()
if err != nil {
panic(err.Error())
}
default:
panic("Invalid mode sent to subproc.exec")
}
process = nil
case int('b'):
// Background Mode
err := process.Start()
if err != nil {
panic(err.Error())
}
default:
panic("Invalid mode sent to subproc.exec")
}
process = nil
}
func procWorkingDir() {
pathAddress := data.Pop()
path, err := nimfStringToGolang(pathAddress)
if err != nil {
panic("Invalid path string provided to subproc.cd")
}
path, err := nimfStringToGolang(pathAddress)
if err != nil {
panic("Invalid path string provided to subproc.cd")
}
path = expandedAbsFilepath(path)
process.Dir = path
}
@ -1002,4 +891,3 @@ func nilSubprocess() {
data.Push(-1)
}
}

@ -3,24 +3,54 @@ package main
import (
"fmt"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"strings"
)
var inlinedFiles map[string]bool = map[string]bool{}
func readFile(path string) (string, error) {
b, err := ioutil.ReadFile(path)
b, err := ioutil.ReadFile(ExpandedAbsFilepath(path))
if err != nil {
return "", fmt.Errorf("Error: Unable to open file %s\n", path)
return "", fmt.Errorf("Unable to open file %s\n", path)
}
fs := string(b)
if strings.HasPrefix(fs, "#!") {
ind := strings.IndexRune(fs, '\n')
if len(fs) > ind {
fileLine += 1
fs = fs[ind+1:]
return string(b), nil
}
func ExpandedAbsFilepath(p string) string {
if strings.HasPrefix(p, "~") {
if p == "~" || strings.HasPrefix(p, "~/") {
homedir, _ := os.UserHomeDir()
if len(p) <= 2 {
p = homedir
} else if len(p) > 2 {
p = filepath.Join(homedir, p[2:])
}
} else {
i := strings.IndexRune(p, '/')
var u string
var remainder string
if i < 0 {
u = p[1:]
remainder = ""
} else {
u = p[1:i]
remainder = p[i:]
}
usr, err := user.Lookup(u)
if err != nil {
p = filepath.Join("/home", u, remainder)
} else {
p = filepath.Join(usr.HomeDir, remainder)
}
}
} else if !strings.HasPrefix(p, "/") {
wd, _ := os.Getwd()
p = filepath.Join(wd, p)
}
return fs, nil
path, _ := filepath.Abs(p)
return path
}

@ -1,7 +1,9 @@
package main
import (
"bufio"
"fmt"
"os"
"os/user"
"path/filepath"
"strconv"
@ -82,7 +84,7 @@ func tokenIsValid(t string) bool {
func recoverFromStackOverUnder() {
if r := recover(); r != nil {
data.Pointer, address.Pointer = -1, -1
scanner.SetError(fmt.Errorf("%v", r))
handleError(fmt.Errorf("%v", r))
}
}
@ -115,14 +117,14 @@ func storeNewString(name string) {
defer memory.Set(svarAssignmentFlag, 0)
_, err := strconv.Atoi(name)
if err == nil {
scanner.SetError(fmt.Errorf("Invalid svar name: %q", name))
handleError(fmt.Errorf("Invalid svar name: %q", name))
return
}
if _, ok := names[name]; ok {
scanner.SetError(fmt.Errorf("Svar %q already exists", name))
handleError(fmt.Errorf("Svar %q already exists", name))
return
}
names[name] = []string{strconv.Itoa(memory.NewSvarPointer())}
names[name] = []token{token{t: strconv.Itoa(memory.NewSvarPointer()), line: -1, file: "runtime", tokType: intType}}
}
func nimfStringToGolang(address int) (string, error) {
@ -131,7 +133,7 @@ func nimfStringToGolang(address int) (string, error) {
return "", fmt.Errorf("Invalid string")
}
var text strings.Builder
for i := address + 1; i <= address + length; i++ {
for i := address + 1; i <= address+length; i++ {
text.WriteRune(rune(memory.Get(i)))
}
return text.String(), nil
@ -161,11 +163,11 @@ func expandedAbsFilepath(p string) string {
return path
}
func cleanUpEarlyExit(remainingAction []string) {
func cleanUpEarlyExit(remainingAction []token) {
thenSkip := 0
loopSkip := 0
for _, val := range remainingAction {
switch val {
switch val.t.(string) {
case "if":
thenSkip += 1
case "do":
@ -193,7 +195,7 @@ func cleanUpEarlyExit(remainingAction []string) {
}
}
memory.Set(actionModeFlag, 0) // Turn off action mode
memory.Set(exitFlag, 0) // Turn the exit flag off
memory.Set(exitFlag, 0) // Turn the exit flag off
}
func cleanUpLocalMemory(start int) {
@ -202,3 +204,35 @@ func cleanUpLocalMemory(start int) {
}
memory.lpointer = start
}
func getReplInput() []token {
reader := bufio.NewReader(os.Stdin)
fmt.Print("\033[1;32m> \033[0m")
text, err := reader.ReadString('\n')
if err != nil {
panic("Error reading line at repl")
}
for strings.Count(text, ":") != strings.Count(text, ";") {
ntext, err := reader.ReadString('\n')
if err != nil {
panic("Error reading line at repl")
}
text = text + ntext
}
return parse(text, true)
}
func handleError(e error) {
fmt.Fprintf(os.Stderr, "\033[91;1mError:\n\033[0m\033[1m%s\033[0m\n", e.Error())
if interactive {
reset = true
data.Pointer, address.Pointer = -1, -1
memory.Set(3, 0) // string mode is off
memory.Set(4, 0) // string mode is off
memory.Set(8, 0) // branch depth is 0
memory.Set(9, 0) // loop depth is 0
memory.Set(11, 0) // variable setting is off
} else {
os.Exit(1)
}
}

@ -4,9 +4,9 @@
=======================================================
)
" text " inline
" std " inline
" num " inline
"text" inline
"std" inline
"num" inline
: convert.int-to-string ( n -- )
local value

@ -33,9 +33,9 @@
------------------------ )
" std " inline
" num " inline
" text " inline
"std" inline
"num" inline
"text" inline
@ -78,7 +78,7 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
------------------------ )
: gopher.help ( _ -- _ :: Prints a helpful message about using the gopher module )
" gopher\n------\n\ngopher.visit\tNavigate to a host/resource; will query for info; no stack effects\ngopher.follow\tEats top of stack as the link to follow and navigates to that link\ngopher.back\tGo back a page, up to three levels; no stack effects\ngopher.reload\treloads the current page; no stack effects\ngopher.url?\tEats top of stack as the link to print the url information for\ngopher.favs-path\tSet this string var to a local path that leads to a gophermap to use for favorites\ngopher.fav\tEats top of stack as the link id to store as a favorite; must have set gopher.favs-path\ngopher.favs\tNavigates to the file set by gopher.favs-path, which must be set; no stack effects "
"gopher\n------\n\ngopher.visit\tNavigate to a host/resource; will query for info; no stack effects\ngopher.follow\tEats top of stack as the link to follow and navigates to that link\ngopher.back\tGo back a page, up to three levels; no stack effects\ngopher.reload\treloads the current page; no stack effects\ngopher.url?\tEats top of stack as the link to print the url information for\ngopher.favs-path\tSet this string var to a local path that leads to a gophermap to use for favorites\ngopher.fav\tEats top of stack as the link id to store as a favorite; must have set gopher.favs-path\ngopher.favs\tNavigates to the file set by gopher.favs-path, which must be set; no stack effects"
str.print-buf
;
@ -96,7 +96,7 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
: gopher.follow ( link-num -- _ :: Follows a link with the given link number )
depth 1 < if
" must have a link number on TOS for gopher.follow " error
"must have a link number on TOS for gopher.follow" error
then
dup gopher.private.links @ ++ < if
gopher.host:port @ num.positive? if
@ -105,7 +105,7 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
gopher.private.get-link-address
gopher.private.request-and-parse
else
" Invalid link ID " error
"Invalid link ID" error
then
gopher.private.print-separator
;
@ -116,7 +116,7 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
gopher.private.previous-resource get-string gopher.resource set-string
gopher.private.request-and-parse
else
" No history available " error
"No history available" error
then
gopher.private.previous-host:port2 @ num.positive? if
@ -143,7 +143,7 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
gopher.host:port @ num.positive? if
gopher.private.request-and-parse
else
" No page to reload "
"No page to reload"
then
;
@ -155,18 +155,18 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
: gopher.fav ( link-num -- _ :: Adds a link to the filepath stored at gopher.favs-path )
dup gopher.private.links @ > if
" Invalid link number " error
"Invalid link number" error
then
gopher.favs-path @ 1 < if
" No favs path has been set " error
"No favs path has been set" error
then
gopher.favs-path `a file.open
" 1 " str.buf-addr file.write
" \e[1mEnter favorite title $\_\e[0m " str.print-buf input
"1" str.buf-addr file.write
"\e[1mEnter favorite title $\_\e[0m" str.print-buf input
str.buf-addr file.write
" \t " str.buf-addr file.write
"\t" str.buf-addr file.write
gopher.private.links + @ ( get the address of the host )
dup dup @ ++ + ( get the address of the resource, leaving host address on stack )
file.write ( write the resource )
@ -176,13 +176,13 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
-- str.buf-addr ! ( remove the port, if need be )
then
str.buf-addr file.write
" \t70\n " str.buf-addr file.write
"\t70\n" str.buf-addr file.write
file.close
;
: gopher.favs ( _ -- _ :: Navigates to the gophermap stored at gopher.favs-path )
gopher.favs-path @ 1 < if
" No favs path has been set " error
"No favs path has been set" error
then
gopher.favs-path `R file.open
gopher.private.page set-string
@ -201,7 +201,7 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
: gopher.private.print-separator ( _ -- _ :: Prints an ascii horizontal separator )
" \n\e[7m _________________________________________________________\e[0m\n " str.print-buf
"\n\e[7m _________________________________________________________\e[0m\n" str.print-buf
;
: gopher.private.add-to-history ( _ -- _ :: Adds a new url to history )
@ -233,21 +233,21 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
gopher.private.page set-string
else
tcp.close
" Could not read from resource " error
"Could not read from resource" error
then
else
tcp.close
" Writing resource to host failed " error
"Writing resource to host failed" error
then
else
" Unable to connect to requested host:port " error
"Unable to connect to requested host:port" error
then
;
: gopher.private.input-host:port ( _ -- _ :: Requests the host and port from the user and stores it in the gopher.host:port variable )
" \e[1mhost:port $\_\e[0m " str.print-buf ( Print the query )
"\e[1mhost:port $\_\e[0m" str.print-buf ( Print the query )
input str.buf-addr @ num.zero? if
" empty host/port " error
"empty host/port" error
then
gopher.host:port set-string
gopher.private.add-default-port
@ -263,9 +263,9 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
;
: gopher.private.input-resource ( _ -- _ :: Requests the resource from the user and stores it in the gopher.resource variable, adding a newline to the end for use in sending the request )
" \e[1mresource \_$\_\e[0m " str.print-buf
"\e[1mresource \_$\_\e[0m" str.print-buf
input str.buf-addr @ num.zero? if
" / "
"/"
then
`\r str.buf-addr str.append-char
`\n str.buf-addr str.append-char
@ -304,7 +304,7 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
: gopher.private.parse-map ( _ -- _ :: Initiates parsing of gophermaps and intiates link building and text output )
gopher.private.page @ 1 < if
" No page data to display " error
"No page data to display" error
then
1 gopher.private.line-offset ! ( Set to the beginning )
0 gopher.private.links ! ( Reset the link count to zero )
@ -354,7 +354,7 @@ var gopher.favs-path 100 allot ( a string representing a local path to a bookmar
swap `\t swap gopher.private.line-storage swap str.offset-index-of -- ( end offset )
gopher.private.line-storage + ( end address )
2dup > if ( if the start is bigger than the end we have a null field )
" / "
"/"
else
get-raw-string
then

@ -6,7 +6,7 @@
=======================================================
)
" std " inline
"std" inline
( Absolute value )
: num.abs ( n -- +u )

@ -6,7 +6,7 @@
note that the `winsize` word is a nimf buitin and is
thus always available and is not a part of this lib )
" text " inline
"text" inline
: term.simple-esc-code ( ch n -- _ )
@ -51,11 +51,11 @@
;
: term.hide-cursor ( -- )
" \e[?25l " str.print-buf
"\e[?25l" str.print-buf
;
: term.show-cursor ( -- )
" \e[?25h " str.print-buf
"\e[?25h" str.print-buf
;
: term.bell ( -- )

@ -3,8 +3,8 @@
a collection of character and string helpers
======================================================= )
" std " inline
" num " inline
"std" inline
"num" inline
( ------------------------
@ -217,7 +217,7 @@ var str.private.end
str.private.start !
str.private.end @ str.private.start @ <= if
" End position found before start position in str.sub " error
"End position found before start position in str.sub" error
then
str.private.address @ str.private.length @ +
@ -236,11 +236,11 @@ var str.private.end
loop
else
str.private.restore-vars
" String length less than substring end point " error
"String length less than substring end point" error
then
else
str.private.restore-vars
" String length less than zero " error
"String length less than zero" error
then
str.private.restore-vars

@ -6,7 +6,7 @@ import (
"fmt"
"net"
"os"
"os/exec"
"os/exec"
"strconv"
"strings"
)
@ -32,12 +32,13 @@ var process *exec.Cmd
var processWrite bool
// scanner
var scanner tokens
var scanner *tokenReader
// Run mode
var interactive bool = true
var reset bool = false
func action(sr []string) {
func action(sr []token) {
locals := make(map[string]int)
returnLpointer := memory.lpointer
defer cleanUpLocalMemory(returnLpointer)
@ -52,21 +53,20 @@ func action(sr []string) {
}
if memory.ValueEquals(localAssignmentFlag, -1) {
memory.Set(localAssignmentFlag, 0)
_, err := strconv.Atoi(sr[i])
if err == nil {
scanner.SetError(fmt.Errorf("Invalid var name: %q", sr[i]))
if sr[i].tokType != wordType {
handleError(fmt.Errorf("Invalid var name: %q at line %d of %s", sr[i].String(), sr[i].line, sr[i].file))
return
}
locals[sr[i]] = memory.pointer + memory.lpointer
locals[sr[i].String()] = memory.pointer + memory.lpointer
memory.lpointer++
} else if val, ok := locals[sr[i]]; ok {
} else if val, ok := locals[sr[i].String()]; ok {
if memory.Get(conditionalBranchRunFlag) != 0 && memory.Get(commentFlag) == 0 {
data.Push(val)
}
} else {
execute(sr[i])
}
if scanner.IsError() {
if reset {
memory.Set(actionModeFlag, 0) // Turn off action mode
return
}
@ -77,13 +77,10 @@ func action(sr []string) {
}
}
func execute(field string) {
func execute(field token) {
defer recoverFromStackOverUnder()
// Skip comments
if memory.Get(commentFlag) != 0 && field != ")" {
return
}
fieldString := field.String()
// Skip execution if we are in the dead
// branch of an if block. If, else, and then
@ -91,65 +88,76 @@ func execute(field string) {
// since they need to accumulate in registers
// properly and handle this within their own
// words
if memory.Get(conditionalBranchRunFlag) == 0 && field != "else" && field != "then" && field != "if" && field != "(" && field != ")" {
if memory.Get(conditionalBranchRunFlag) == 0 &&
fieldString != "else" &&
fieldString != "then" &&
fieldString != "if" {
return
}
var n int
var errInt error
if field[0] == '`' {
chars := []rune(field)
// TODO move this logic into the parser?
// it converts char literals: 'R
// to their int representation
if field.String()[0] == '`' {
chars := []rune(field.String())
switch len(chars) {
case 2:
field = strconv.Itoa(int(chars[1]))
field = token{
t: strconv.Itoa(int(chars[1])),
tokType: intType,
line: field.line,
file: field.file,
}
case 3:
sub := convertEscapes(field)
sub := convertEscapes(field.String())
chars = []rune(sub)
if len(chars) != 2 {
break
}
field = strconv.Itoa(int(chars[1]))
field = token{
t: strconv.Itoa(int(chars[1])),
tokType: intType,
line: field.line,
file: field.file,
}
}
}
if memory.Get(stringModeFlag) == -1 && field != "\"" {
// If we are in a string, add the word to the string
addToStoredString(field, &memory.tspointer)
} else if n, errInt = strconv.Atoi(field); errInt == nil {
if field.tokType == stringType {
toggleStringOutput()
addToStoredString(field.String(), &memory.tspointer)
toggleStringOutput()
} else if field.tokType == intType {
// If we are dealing with a number add it to the stack
data.Push(n)
data.Push(field.t.(int))
} else if memory.Get(varAssignmentFlag) == -1 {
// If we are setting a var value
// If we somehow got here with an integer word, throw an error
_, err := strconv.Atoi(field)
if err == nil {
scanner.SetError(fmt.Errorf("Invalid var name: %q", field))
return
}
// If the word already exists throw an error
if _, ok := names[field]; ok {
scanner.SetError(fmt.Errorf("Var %q already exists", field))
if _, ok := names[field.String()]; ok {
handleError(fmt.Errorf("Tried to create a var, %q, at line %d in %s that already exists", field.String(), field.line, field.file))
return
}
names[field] = []string{strconv.Itoa(memory.NewVarPointer())}
names[field.String()] = []token{
token{
t: strconv.Itoa(memory.NewVarPointer()),
line: field.line,
file: field.file,
tokType: intType}}
memory.Set(varAssignmentFlag, 0)
} else if memory.Get(svarAssignmentFlag) == -1 {
// Store a string from the temp buffer in long term memory
storeNewString(field)
} else if val, ok := names[field]; ok {
storeNewString(field.String())
} else if val, ok := names[field.String()]; ok {
// Execute the nimf word
action(val)
if scanner.IsError() {
if reset {
return
}
} else if val, ok := builtins[field]; ok {
} else if val, ok := builtins[field.String()]; ok {
// Execute the builtin
val()
} else {
scanner.SetError(fmt.Errorf("Unknown word: %s", field))
handleError(fmt.Errorf("Unknown word: %s. Found on line %d in %s", field.String(), field.line, field.file))
}
}
@ -171,6 +179,10 @@ func main() {
os.Exit(1)
}
/* TODO make a choice here:
- Parse a file, if given one and see if it has a mem/stack size directive
- If mem or stack are passed in at run time, use them. Otherwise use file based directive followed by defaults
*/
data, address = NewStack(*stackDepth), NewStack(*stackDepth)
memory = NewSystemMemory(*memoryAmt)
memory.Set(conditionalBranchRunFlag, -1)
@ -183,24 +195,35 @@ func main() {
interactive = false
command := *runCommand + "\n"
command = strings.Replace(command, " inline ", " inline\n", -1) // TODO figure out why inline breaks without a following \n
scanner = newTokenizer(strings.NewReader(command), true)
scanner = NewTokenReader(parse(command, false))
if len(args) > 0 {
fmt.Fprintf(os.Stderr, "Warning: -run was passed along with a file, the file is being ignored")
}
} else if len(args) > 0 {
f, err := readFile(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to open file %q", args[0])
os.Exit(1)
}
interactive = false
scanner = newTokenizer(strings.NewReader(f), true)
scanner = NewTokenReader(parse(args[0], false))
} else {
systemHeader()
scanner = newTokenizer(os.Stdin, false)
scanner = NewTokenReader(getReplInput())
}
for {
word := scanner.Next()
if interactive && reset {
scanner = NewTokenReader(getReplInput())
reset = false
} else if interactive && scanner.Len() == 0 {
scanner = NewTokenReader(getReplInput())
if scanner.Len() == 0 {
continue
}
}
word, err := scanner.Read()
if err != nil {
break
}
execute(word)
// TODO replace all scanner style calls with the new parse
// system. This will involve building out a repl function
// for repl entry. It will also involve a shit ton of
// replacements dealing with errors.
}
}

@ -1,6 +1,21 @@
package main
var names = make(map[string][]string)
import "strings"
var names = make(map[string][]token)
func buildWordDefinition(name string, stream []token) string {
var b strings.Builder
b.WriteString(": ")
b.WriteString(name)
b.WriteRune(' ')
for _, v := range stream {
b.WriteString(v.String())
b.WriteRune(' ')
}
b.WriteRune(';')
return b.String()
}
func initNames() {
}

@ -1,105 +1,508 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"unicode"
)
type tokens struct {
tokens []string
output []string
pointer int
reader *bufio.Reader
err error
isFile bool
}
const (
replFile string = "REPL input"
intType int = iota
stringType
wordType
)
var fileLine int = 0
type tokenReader struct {
slice []token
pc int
}
func newTokenizer(reader io.Reader, isFile bool) tokens {
return tokens{[]string{}, []string{}, -1, bufio.NewReader(reader), nil, isFile}
func NewTokenReader(tokens []token) *tokenReader {
return &tokenReader{tokens, 0}
}
func (t *tokens) scan() {
t.reset()
if !t.isFile {
fmt.Print("\033[1;32m> \033[0m")
} else {
fileLine++
func (t tokenReader) Len() int {
count := len(t.slice) - t.pc
if count < 0 {
return 0
}
str, err := t.reader.ReadString('\n')
if err != nil && err == io.EOF && !interactive {
fmt.Print("\n")
os.Exit(0)
} else if err != nil {
panic(err)
return count
}
func (t *tokenReader) Read() (token, error) {
if t.pc == len(t.slice) {
return token{}, fmt.Errorf("No more tokens in token stream")
}
t.tokens = strings.Fields(str)
tok := t.slice[t.pc]
t.pc++
return tok, nil
}
func (t tokens) hasToken() bool {
if len(t.tokens) == 0 || t.pointer == len(t.tokens)-1 {
return false
func (t *tokenReader) PeekLast() (token, error) {
if t.pc == 0 {
return token{}, fmt.Errorf("Invalid call to PeekLast")
}
return true
return t.slice[t.pc-1], nil
}
func (t tokens) IsError() bool {
if t.err == nil {
return false
func (t *tokenReader) Unread() {
t.pc--
if t.pc < 0 {
t.pc = 0
}
return true
}
func (t *tokens) AddOutput(item string) {
t.output = append(t.output, item)
type value interface{}
type token struct {
t value
line int
file string
tokType int
}
func (t *tokens) SetError(e error) {
t.err = e
func (t token) String() string {
return fmt.Sprintf("%v", t.t)
}
func (t *tokens) reset() {
if t.err != nil {
if t.isFile && !interactive {
fmt.Fprintf(os.Stderr, "\033[91;1mError on line %d:\033[0m %s\n", fileLine, t.err.Error())
os.Exit(1)
// parse takes a filepath or datastring as a
// string 'f'. If 'f' is datastring, rather than
// a filepath, repl should be true; otherwise false
func parse(f string, repl bool) []token {
stream := lex(f, repl)
out := make([]token, 0, len(stream))
reader := NewTokenReader(stream)
for reader.Len() > 0 {
tok, err := reader.Read()
if err != nil {
break
}
if tok.tokType == wordType && tok.String() == ":" {
eatWordDefinition(reader, tok.file, tok.line)
} else if tok.tokType == wordType {
out = append(out, tok)
} else if tok.tokType == stringType {
// Handle the case where a string is used for inlining
nextTok, err := reader.Read()
if err == nil && nextTok.tokType == wordType && nextTok.String() == "inline" {
fileName := tok.String()
if !strings.HasSuffix(fileName, ".nh") {
fileName = fileName + ".nh"
}
xdg_lib_path := os.Getenv("XDG_DATA_HOME")
if xdg_lib_path == "" {
xdg_lib_path = "~/.local/share/nimf/"
} else {