Adds a few new special forms (var+ and set+), updates readme, adds license, adds test file
This commit is contained in:
parent
c96e2fc0d9
commit
e9d7a43e0c
|
@ -0,0 +1 @@
|
|||
felise
|
87
README.md
87
README.md
|
@ -4,6 +4,10 @@ _felise_ is a [concatenative](https://en.wikipedia.org/wiki/Concatenative_progra
|
|||
|
||||
## Example
|
||||
|
||||
Since things are still in a major early development phase I figure an example of the code and a metric will suffice for the readme for now.
|
||||
|
||||
A file, `fibtest.fe`:
|
||||
|
||||
``` forth
|
||||
proc fib
|
||||
| calculates the nth fibonacci number
|
||||
|
@ -12,35 +16,70 @@ proc fib
|
|||
INT var fib#
|
||||
set fib#
|
||||
|
||||
fib# 0 = if 1 .
|
||||
fib# 0 = if 1 return .
|
||||
|
||||
fib# 1 = if 3 .
|
||||
fib# 1 = if 3 return .
|
||||
|
||||
fib# 1 > if
|
||||
INT var grandparent 1 set grandparent
|
||||
INT var parent 3 set parent
|
||||
INT var i 2 set i
|
||||
INT var me
|
||||
INT var grandparent 1 set grandparent
|
||||
INT var parent 3 set parent
|
||||
INT var i 2 set i
|
||||
INT var me
|
||||
|
||||
proc continue-loop?
|
||||
i fib# < i
|
||||
.
|
||||
proc continue-loop?
|
||||
i fib# < .
|
||||
|
||||
<=fib#
|
||||
while
|
||||
3 parent *
|
||||
grandparent -
|
||||
set me
|
||||
parent set grandparent
|
||||
me set parent
|
||||
i 1 + set i
|
||||
<=fib#
|
||||
.
|
||||
me
|
||||
.
|
||||
.
|
||||
continue-loop?
|
||||
while
|
||||
3 parent *
|
||||
grandparent -
|
||||
set me
|
||||
parent set grandparent
|
||||
me set parent
|
||||
i 1 + set i
|
||||
continue-loop? .
|
||||
me .
|
||||
|
||||
5 fib println
|
||||
40 fib
|
||||
"Result: "
|
||||
println println # print both the text and the output
|
||||
```
|
||||
|
||||
Running `time felise ./fibtest.fe` outputs:
|
||||
|
||||
```
|
||||
23416728348467685
|
||||
|
||||
real 0m0.009s
|
||||
user 0m0.005s
|
||||
sys 0m0.006s
|
||||
```
|
||||
|
||||
Pretty snappy for this initial test as I am building things. Granted, it is iterative and not recursive (which would certainly slow down).
|
||||
|
||||
## Building
|
||||
|
||||
Assuming you have the mainline [go compiler](https://go.dev/dl/) and [git](https://git-scm.com/) installed (and are using a posix-ish shell on a non-windows system):
|
||||
|
||||
```
|
||||
git clone https://git.rawtext.club/sloum/felise && cd felise && go install
|
||||
```
|
||||
|
||||
You should then be able to run `felise` to load the repl (assuming the path that `go install` installs to is on your path -- you can always use `go build` to build in the local directory instead). As things develop a Makefile will likely be added, but right now it just isnt necessary.
|
||||
|
||||
## Status
|
||||
|
||||
- There are no modules/libraries or loading support for such things as of yet
|
||||
- There is a small library of built-in procedures to enable the basic functionality of the language. Most of it deals with control flow, variable/procedure creation, and stack manipulation. More is needed and will be coming soon along with documentation
|
||||
- While I write above that a non-windows system is required... it may build and work on windows. I'm not sure, as I do not use that OS and do not have a system to test it on. [YMMV](https://en.wiktionary.org/wiki/your_mileage_may_vary)
|
||||
- The repl works
|
||||
- Loading of a single file works
|
||||
- Error messages are at least mildly helpful
|
||||
- They include a line number, a message, and if the error is nested inside of procedures a trace of the call order of the procedures
|
||||
- There is no input functionality at present: no reading from file or connection, no command line args
|
||||
- There is no support for arrays, but they are on the horizon
|
||||
- User defined procedures cannot yet request the next execution token (similar to how the `var` or `set` builtins work), this has been figured out but has not been implemented yet: coming soon
|
||||
|
||||
## License
|
||||
|
||||
Notice: This is not free/libre software as defined by the FSF or OSC. I do not ascribe to their vision of free software. I believe that capitalism is an inherent threat to freedom and is an injustice to everyone. As such I use the Floodgap Free Software License (Version 1). The license is included in the repo (see `LICENSE` file), and is mentioned and linked in each source code file. The basic idea is that you as an individual can do anything you like with this software that does not involve, directly or indirectly, money changing hands. There are a few exceptions to this noted in the license, but in general, that is the idea. Free to use in any way you like, so long as you don't charge for it. You are, of course, welcome to license code produced for the felise interpreter/language in any way you choose. The license I have chosen only applies to my interpreter code.
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ func isKeyword(s string) bool {
|
|||
"length", "ref", "return", "print", "println", "dup",
|
||||
"drop", "over", "swap", "cast", "type", "while", "dowhile",
|
||||
"if", "else", "and", "or", ">", "<", "=", "stackdump",
|
||||
"clearstack":
|
||||
"clearstack", "var+", "set+":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
24
interpret.go
24
interpret.go
|
@ -56,20 +56,16 @@ ReplacedSymbol:
|
|||
return fmt.Errorf(stackErrFmt, t.line, err.Error())
|
||||
}
|
||||
cond := toBool(v).val.(bool)
|
||||
if cond {
|
||||
for {
|
||||
err = interpret(t.val.(while).body, NewEnv(en))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, err = globalStack.Pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf(stackErrFmt, t.line, err.Error())
|
||||
}
|
||||
if !toBool(v).val.(bool) {
|
||||
break
|
||||
}
|
||||
for cond {
|
||||
err = interpret(t.val.(while).body, NewEnv(en))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, err = globalStack.Pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf(stackErrFmt, t.line, err.Error())
|
||||
}
|
||||
cond = toBool(v).val.(bool)
|
||||
}
|
||||
} else {
|
||||
err = interpret(t.val.(while).body, NewEnv(en))
|
||||
|
@ -104,7 +100,7 @@ ReplacedSymbol:
|
|||
// This join is meant to create a callstack trace of sorts
|
||||
// so that if an error was found multiple procs deep each
|
||||
// _should_ adds it proc name and line to the list.
|
||||
return errors.Join(err, fmt.Errorf(" - In procedure `%s` on line %d", t.val.(proc).name, t.line))
|
||||
return errors.Join(err, fmt.Errorf(" - In procedure `%s`", t.val.(proc).name))
|
||||
}
|
||||
if globalRetStack.Depth() > 0 {
|
||||
globalRetStack.Pop()
|
||||
|
|
85
keywords.go
85
keywords.go
|
@ -23,6 +23,7 @@ import (
|
|||
TODO / Notes on broken things
|
||||
|
||||
- Add `not`
|
||||
- Add a version of `var` and `set` that can set to a parent scope
|
||||
|
||||
*/
|
||||
|
||||
|
@ -38,8 +39,12 @@ func callKeyword(kw token, r *tokenReader, en *env) error {
|
|||
return libDivide(kw.line)
|
||||
case "var":
|
||||
return libVar(kw.line, r, en)
|
||||
case "var+":
|
||||
return libVarPlus(kw.line, r, en)
|
||||
case "set":
|
||||
return libSet(kw.line, r, en)
|
||||
case "set+":
|
||||
return libSetPlus(kw.line, r, en)
|
||||
case "print":
|
||||
return libPrint(false, kw.line)
|
||||
case "println":
|
||||
|
@ -184,7 +189,45 @@ func libVar(line int, r *tokenReader, en *env) error {
|
|||
}
|
||||
newToken.val = typeInit(newToken.kind)
|
||||
en.Add(t.val.(string), newToken)
|
||||
fmt.Println(*en)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func libVarPlus(line int, r *tokenReader, en *env) error {
|
||||
scope, err := globalStack.Pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf(stackErrFmt, line, err.Error())
|
||||
}
|
||||
if scope.kind != INT {
|
||||
return fmt.Errorf("ERROR | Line %d\n `set+ expected an INT declaring how many scopes to jump up\n", line)
|
||||
}
|
||||
for i := scope.val.(int); i > 0; i-- {
|
||||
if en.parent != nil {
|
||||
en = en.parent
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
typeToken, err := globalStack.Pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf(stackErrFmt, line, err.Error())
|
||||
}
|
||||
if typeToken.kind != TYPE {
|
||||
return fmt.Errorf("ERROR | Line %d\n `var` expects a type on the top of the stack\n ", line)
|
||||
}
|
||||
newToken := token{}
|
||||
newToken.kind = typeToken.val.(int)
|
||||
newToken.line = line
|
||||
|
||||
t, err := r.Read()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR | Line %d\n Unexpectedly reached EOF\n", line)
|
||||
}
|
||||
if t.kind != SYMBOL {
|
||||
return fmt.Errorf("ERROR | Line %d\n A non-symbol value was given as an identifier to var\n", line)
|
||||
}
|
||||
newToken.val = typeInit(newToken.kind)
|
||||
en.Add(t.val.(string), newToken)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -212,6 +255,46 @@ func libSet(line int, r *tokenReader, en *env) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ANY INT set+ SYMBOL
|
||||
// INT represents the number of scopes to jump up
|
||||
func libSetPlus(line int, r *tokenReader, en *env) error {
|
||||
t, err := r.Read()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR | Line %d\n Unexpectedly reached EOF\n", line)
|
||||
}
|
||||
if t.kind != SYMBOL {
|
||||
return fmt.Errorf("ERROR | Line %d\n A non-symbol value was given as an identifier to set\n", line)
|
||||
}
|
||||
scope, err := globalStack.Pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf(stackErrFmt, line, err.Error())
|
||||
}
|
||||
if scope.kind != INT {
|
||||
return fmt.Errorf("ERROR | Line %d\n `set+ expected an INT declaring how many scopes to jump up\n", line)
|
||||
}
|
||||
for i := scope.val.(int); i > 0; i-- {
|
||||
if en.parent != nil {
|
||||
en = en.parent
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
e, err := en.Find(t.val.(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR | Line %d\n %s\n", t.line, err.Error())
|
||||
}
|
||||
v1, err := globalStack.Pop()
|
||||
if err != nil {
|
||||
return fmt.Errorf(stackErrFmt, line, err.Error())
|
||||
}
|
||||
current := e.vars[t.val.(string)]
|
||||
if v1.kind != current.kind {
|
||||
return fmt.Errorf("ERROR | Line %d\n Cannot assign %s to %s var\n", t.line, kindToString(v1.kind), kindToString(current.kind))
|
||||
}
|
||||
e.vars[t.val.(string)] = v1
|
||||
return nil
|
||||
}
|
||||
|
||||
func libPrint(newline bool, line int) error {
|
||||
v1, err := globalStack.Pop()
|
||||
if err != nil {
|
||||
|
|
32
main.go
32
main.go
|
@ -63,7 +63,8 @@ length [varname]
|
|||
// Not yet sure how to do this
|
||||
ref [ident/symbol]
|
||||
Put the symbol on TOS, to reference something that would
|
||||
otherwise be executed immediately (if, while, proc, etc)
|
||||
otherwise be executed immediately (if, while, proc, etc).
|
||||
Still thinking out the semantics.
|
||||
|
||||
---
|
||||
|
||||
|
@ -104,7 +105,7 @@ Builtins on radar:
|
|||
|
||||
Need some way to respond to args given at runtime... may need
|
||||
an array for that. Or maybe not? Maybe an `arg` word can be
|
||||
defined so that: `0 INT arg myarg` would let someone pass an
|
||||
defined so that: `0 INT flag myarg` would let someone pass an
|
||||
arg: `felise myfile.fe --myarg=24`, with the arg defaulting to
|
||||
0... does something like that work?
|
||||
*/
|
||||
|
@ -162,22 +163,26 @@ func repl() {
|
|||
text.WriteString(in)
|
||||
}
|
||||
if !cont {
|
||||
lexParseInterpret(text.String(), baseEnv)
|
||||
lexParseInterpret(text.String(), baseEnv, true)
|
||||
text.Reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lexParseInterpret(s string, en *env) {
|
||||
func lexParseInterpret(s string, en *env, repl bool) {
|
||||
lexedTokens := lex(s)
|
||||
r := NewTokenReader(lexedTokens)
|
||||
parsedTokens := parse(r)
|
||||
err := interpret(parsedTokens, en)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
globalStack.sp = 0
|
||||
} else {
|
||||
fmt.Println("\n\033[0mOk.")
|
||||
if repl {
|
||||
globalStack.sp = 0
|
||||
} else {
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if repl {
|
||||
fmt.Println("\n\033[0mOk.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,11 +191,18 @@ func main() {
|
|||
verFlag := flag.Bool("v", false, "Print version information and exit")
|
||||
flag.BoolVar(&debug, "debug", false, "Turns on debug mode")
|
||||
flag.Parse()
|
||||
flagArgs := flag.Args()
|
||||
|
||||
if *verFlag {
|
||||
fmt.Print(VersionString())
|
||||
return
|
||||
} else if len(flagArgs) > 0 {
|
||||
s, err := readFile(ExpandedAbsFilepath(flagArgs[0]))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "felise could not read the given input file:\n `%s`\n", flagArgs[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
lexParseInterpret(s, NewEnv(nil), false)
|
||||
} else {
|
||||
repl()
|
||||
}
|
||||
|
||||
repl()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# comment test
|
||||
|
||||
proc fib
|
||||
| calculates the nth fibonacci number
|
||||
where n is an INT on TOS at call time |
|
||||
|
||||
INT var fib#
|
||||
set fib#
|
||||
|
||||
fib# 0 = if 1 return .
|
||||
|
||||
fib# 1 = if 3 return .
|
||||
|
||||
INT var grandparent 1 set grandparent
|
||||
INT var parent 3 set parent
|
||||
INT var i 2 set i
|
||||
INT var me
|
||||
|
||||
proc continue-loop?
|
||||
i fib# < .
|
||||
|
||||
continue-loop?
|
||||
while
|
||||
3 parent *
|
||||
grandparent -
|
||||
set me
|
||||
parent set grandparent
|
||||
me set parent
|
||||
i 1 + set i
|
||||
continue-loop? .
|
||||
me .
|
||||
|
||||
40 fib println
|
||||
|
Loading…
Reference in New Issue