add stdlib package with readme
This commit is contained in:
parent
c40aea7326
commit
0b3bac7bca
9 changed files with 123 additions and 47 deletions
|
|
@ -8,8 +8,14 @@ This shell was created to have extremely simple syntax. S-Expressions were chose
|
||||||
### Compiling/Installation
|
### Compiling/Installation
|
||||||
- For now simply run `go install cmd/...` for each utility you wish to use. If you have GOPATH and GOBIN set it should be usable from PATH
|
- For now simply run `go install cmd/...` for each utility you wish to use. If you have GOPATH and GOBIN set it should be usable from PATH
|
||||||
|
|
||||||
|
### Adding SHS to your application
|
||||||
|
- TODO: write a how to here
|
||||||
|
- Make sure the GPLv3 is adhered to
|
||||||
|
- *OVERRIDE THE STDLIB GenFuncTable FUNCTION.* You very likely do NOT want an available function to call system binaries in your embedded shell. Make sure the stdlib Call function is not included.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
- Any contribution to this software is welcome as long as it adheres to the conduct guidelines specified in the `Contributing.md` file in this repository.
|
- Any contribution to this software is welcome as long as it adheres to the conduct guidelines specified in the `Contributing.md` file in this repository.
|
||||||
|
- Consider reading the [STDLIB Readme](https://git.callpipe.com/aidan/shs/-/tree/master/stdlib/Readme.go") for more information on how to extend this project.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Copyright (C) 2019 Aidan Hahn.
|
Copyright (C) 2019 Aidan Hahn.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@
|
||||||
|
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
type Operation func(*Token) *Token
|
import "git.callpipe.com/aidan/shs/log"
|
||||||
|
|
||||||
|
type Operation func(*Token, VarTable, FuncTable) *Token
|
||||||
|
|
||||||
type Function struct {
|
type Function struct {
|
||||||
function Operation
|
function Operation
|
||||||
|
|
@ -50,16 +52,16 @@ func (f Function) ParseFunction(args *Token) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Function) CallFunction(args *Token) *Token {
|
func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token {
|
||||||
if !f.ParseFunction(args) {
|
if !f.ParseFunction(args) {
|
||||||
log.Log(log.Err,
|
log.Log(log.ERR,
|
||||||
"Couldnt call " + f.name,
|
"Couldnt call " + f.name,
|
||||||
"eval")
|
"eval")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
f.timesCalled += 1
|
f.timesCalled += 1
|
||||||
return f.function(args)
|
return f.function(args, vt, ft)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table FuncTable) GetFunction(arg string) *Function {
|
func (table FuncTable) GetFunction(arg string) *Function {
|
||||||
|
|
|
||||||
|
|
@ -15,36 +15,36 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package log
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.callpipe.com/aidan/shs/ast"
|
|
||||||
"git.callpipe.com/aidan/shs/datatypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Print function which is better suited for repl.
|
/* Print function which is better suited for repl.
|
||||||
* This one prints the SEXPRs as one would write them.
|
* This one prints the SEXPRs as one would write them.
|
||||||
*/
|
*/
|
||||||
func (t *ast.Token) String() string {
|
func (t *Token) String() string {
|
||||||
switch tag {
|
switch t.Tag {
|
||||||
case ast.STRING
|
case STRING:
|
||||||
return "\"" + t.Inner.(string) + "\""
|
return "\"" + t.Inner.(string) + "\""
|
||||||
|
|
||||||
case ast.NUMBER:
|
case NUMBER:
|
||||||
return t.Inner.(string)
|
return t.Inner.(string)
|
||||||
|
|
||||||
case ast.LIST:
|
case LIST:
|
||||||
repr := "("
|
repr := "("
|
||||||
for i := t.Inner.(*ast.Token); i != nil; i = i.Next {
|
for i := t.Inner.(*Token); i != nil; i = i.Next {
|
||||||
repr = repr + i.String() + " "
|
repr = repr + i.String() + " "
|
||||||
}
|
}
|
||||||
return repr + ")"
|
return repr + ")"
|
||||||
|
|
||||||
case ast.SYMBOL:
|
case SYMBOL:
|
||||||
return "<" + t.Inner.(string) + ">"
|
return "<" + t.Inner.(string) + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "[UNKNOWN CELL TYPE]"
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print function which breaks each embedded list out on individual lines.
|
/* Print function which breaks each embedded list out on individual lines.
|
||||||
|
|
@ -52,12 +52,12 @@ func (t *ast.Token) String() string {
|
||||||
* Very useful for debugging syntax though.
|
* Very useful for debugging syntax though.
|
||||||
* TODO: Add numbers to denote embedded scope?
|
* TODO: Add numbers to denote embedded scope?
|
||||||
*/
|
*/
|
||||||
func PrintSExprsIndividually(arg *ast.Token) {
|
func PrintSExprsIndividually(arg *Token) {
|
||||||
if arg == nil {
|
if arg == nil {
|
||||||
return //TODO: Handle error here?
|
return //TODO: Handle error here?
|
||||||
}
|
}
|
||||||
|
|
||||||
var lists datatypes.TokenStack;
|
var lists TokenStack;
|
||||||
lists.Push(arg)
|
lists.Push(arg)
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
|
|
@ -68,8 +68,8 @@ loop:
|
||||||
}
|
}
|
||||||
|
|
||||||
for iter := i; iter != nil; iter = iter.Next {
|
for iter := i; iter != nil; iter = iter.Next {
|
||||||
if iter.Tag == ast.LIST {
|
if iter.Tag == LIST {
|
||||||
lists.Push(iter.Inner.(*ast.Token))
|
lists.Push(iter.Inner.(*Token))
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor.WriteString(FmtToken(iter))
|
constructor.WriteString(FmtToken(iter))
|
||||||
|
|
@ -79,14 +79,14 @@ loop:
|
||||||
goto loop
|
goto loop
|
||||||
}
|
}
|
||||||
|
|
||||||
func FmtToken(arg *ast.Token) string {
|
func FmtToken(arg *Token) string {
|
||||||
suffix := "->"
|
suffix := "->"
|
||||||
if arg.Next == nil {
|
if arg.Next == nil {
|
||||||
suffix = ""
|
suffix = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
switch arg.Tag {
|
switch arg.Tag {
|
||||||
case ast.LIST:
|
case LIST:
|
||||||
return fmt.Sprintf("(%s, [List])%s", "LIST", suffix)
|
return fmt.Sprintf("(%s, [List])%s", "LIST", suffix)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -94,15 +94,15 @@ func FmtToken(arg *ast.Token) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTagAsStr(tag ast.Token_t) string {
|
func GetTagAsStr(tag Token_t) string {
|
||||||
switch tag {
|
switch tag {
|
||||||
case ast.LIST:
|
case LIST:
|
||||||
return "LIST"
|
return "LIST"
|
||||||
case ast.STRING:
|
case STRING:
|
||||||
return "STRING"
|
return "STRING"
|
||||||
case ast.NUMBER:
|
case NUMBER:
|
||||||
return "NUMBER"
|
return "NUMBER"
|
||||||
case ast.SYMBOL:
|
case SYMBOL:
|
||||||
return "SYMBOL"
|
return "SYMBOL"
|
||||||
}
|
}
|
||||||
return "UNKNOWN"
|
return "UNKNOWN"
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.callpipe.com/aidan/shs/log"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,23 +15,19 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package datatypes
|
package ast
|
||||||
|
|
||||||
import (
|
|
||||||
"git.callpipe.com/aidan/shs/ast"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TokenStack struct {
|
type TokenStack struct {
|
||||||
buffer []*ast.Token
|
buffer []*Token
|
||||||
capacity int
|
capacity int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenStack) Push(v *ast.Token) {
|
func (s *TokenStack) Push(v *Token) {
|
||||||
s.capacity++
|
s.capacity++
|
||||||
s.buffer = append(s.buffer, v)
|
s.buffer = append(s.buffer, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TokenStack) Pop() *ast.Token {
|
func (s *TokenStack) Pop() *Token {
|
||||||
if s.capacity <= 0 {
|
if s.capacity <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
24
cmd/repl.go
24
cmd/repl.go
|
|
@ -18,8 +18,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"fmt"
|
"fmt"
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"strings"
|
||||||
"strconv"
|
"strconv"
|
||||||
"git.callpipe.com/aidan/shs/ast"
|
"git.callpipe.com/aidan/shs/ast"
|
||||||
"git.callpipe.com/aidan/shs/log"
|
"git.callpipe.com/aidan/shs/log"
|
||||||
|
|
@ -32,10 +34,10 @@ const (
|
||||||
func setLogLvl() {
|
func setLogLvl() {
|
||||||
loglvl := os.Getenv("SH_LOGGING")
|
loglvl := os.Getenv("SH_LOGGING")
|
||||||
if loglvl != "" {
|
if loglvl != "" {
|
||||||
llvl, ok := strconv.ParseInt(loglvl, 10, 8)
|
llvl, err := strconv.ParseInt(loglvl, 10, 8)
|
||||||
if !ok {
|
if err != nil {
|
||||||
log.Log(log.Err,
|
log.Log(log.ERR,
|
||||||
"couldnt parse log level",
|
"couldnt parse log level: " + err.Error(),
|
||||||
"init")
|
"init")
|
||||||
} else {
|
} else {
|
||||||
log.SetLogLvl(llvl)
|
log.SetLogLvl(llvl)
|
||||||
|
|
@ -49,18 +51,18 @@ func main() {
|
||||||
prompt := os.Getenv("SHS_SH_PROMPT")
|
prompt := os.Getenv("SHS_SH_PROMPT")
|
||||||
|
|
||||||
var vars ast.VarTable
|
var vars ast.VarTable
|
||||||
var funcs ast.VarTable
|
var funcs ast.FuncTable
|
||||||
|
|
||||||
useHist := false
|
useHist := false
|
||||||
var histFile os.File
|
var histFile *os.File
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if hist != "" {
|
if hist != "" {
|
||||||
useHist = true
|
useHist = true
|
||||||
histFile, err = os.OpenFile(histFile, os.O_RDWR|os.O_CREATE, 0755)
|
histFile, err = os.OpenFile(hist, os.O_RDWR|os.O_CREATE, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
useHist = false
|
useHist = false
|
||||||
log.Log(log.ERR, "coudlnt open histfile: " + err, "init")
|
log.Log(log.ERR, "coudlnt open histfile: " + err.Error(), "init")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +77,7 @@ func main() {
|
||||||
fmt.Print(prompt + " ")
|
fmt.Print(prompt + " ")
|
||||||
text, err := reader.ReadString('\n')
|
text, err := reader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(log.ERR, "couldnt read user input: " + err, "repl")
|
log.Log(log.ERR, "couldnt read user input: " + err.Error(), "repl")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with a regex
|
// TODO: replace with a regex
|
||||||
|
|
@ -85,7 +87,7 @@ func main() {
|
||||||
if useHist {
|
if useHist {
|
||||||
_, err = histFile.Write([]byte(text + "\n"))
|
_, err = histFile.Write([]byte(text + "\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(log.DEBUG, "couldnt write to histfile: " + err, "repl")
|
log.Log(log.DEBUG, "couldnt write to histfile: " + err.Error(), "repl")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +99,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if debug != "" {
|
if debug != "" {
|
||||||
log.PrintSExprsIndividually(userInput)
|
ast.PrintSExprsIndividually(userInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := userInput.Eval(funcs, vars)
|
result := userInput.Eval(funcs, vars)
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,16 @@ const (
|
||||||
INFO int = 2
|
INFO int = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
var logLevel int
|
var logLevel int64
|
||||||
|
|
||||||
func SetLogLvl(lvl int) {
|
func SetLogLvl(lvl int64) {
|
||||||
if lvl < 4 && lvl > 0 {
|
if lvl < 4 && lvl > 0 {
|
||||||
logLevel = lvl
|
logLevel = lvl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Log(lvl int, msg, context string) {
|
func Log(lvl int, msg, context string) {
|
||||||
if lvl > logLevel {
|
if int64(lvl) > logLevel {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
41
stdlib/Readme.md
Normal file
41
stdlib/Readme.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# SHS STDLIB
|
||||||
|
|
||||||
|
In here are definitions for builtin functions that can be used from the repl. If you are looking for a way to extend this shell to fit your use case you are in the right spot.
|
||||||
|
|
||||||
|
## Datatypes
|
||||||
|
|
||||||
|
Within the AST package you can see [a number of important definitions.]("https://git.callpipe.com/aidan/shs/-/blob/master/ast/func_table.go") Worth noting are FuncTable, Operation, and Function.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Operation func(*Token, VarTable, FuncTable) *Token
|
||||||
|
|
||||||
|
type Function struct {
|
||||||
|
function Operation
|
||||||
|
name string
|
||||||
|
timesCalled int
|
||||||
|
args int // TODO: Make this a list of expected types (TAGs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FuncTable map[string]*Function
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Standard Library
|
||||||
|
|
||||||
|
The standard library is loaded during the init step of the repl (or interpreter if you have embedded one). Alternatively, it can be loaded into your application by importing `git.callpipe.com/shs/stdlib`. In order to get a table of all available functions the `GenFuncTable` function is called, returning a struct of type `ast.FuncTable`. Any functions in the standard lib must be accounted for here in order for them to be available at repl or interpreter. Each Operation operates on a list of [Tokens](https://git.callpipe.com/aidan/shs/-/blob/master/ast/token.go). Every function should adhere to the idea that it takes in a list of Tokens and returns a list of Tokens.
|
||||||
|
|
||||||
|
## Working with Tokens
|
||||||
|
[Tokens](https://git.callpipe.com/aidan/shs/-/blob/master/ast/token.go) are a rudimentary linked list of parsed [Lexemes](https://en.wikipedia.org/wiki/Lexeme). In the ast package there are definitions for Tokens, as well as code for the combined Lex/Parse loop that creates them. Tokens are built in a way that makes operating over them with either recursive or iterative alrogithms easy. When consuming Tokens, one can expect their type by looking at the Tag field. The data stored in the Inner field will be either a string or a \*Token depending on what Tag is. You can expect a \*Token if the Tag field is ast.LIST, and a string in all other cases. If the Tag field is ast.SYMBOL you can look it up in the VarTable or the FuncTable. The VarTable will return either a \*Token (if the symbol is a Variable) or *nil* if nothing is found. The FuncTable will return either a \*Function (if there is a match) or it will return *nil*.
|
||||||
|
|
||||||
|
## Adding a function
|
||||||
|
1. *Write your function in the form of an `ast.Operation`.* Any function that has the defined signature can be an Operation.
|
||||||
|
2. *Create a `Function` to encapsulate your `Operation`.* Make sure to set the `args` and `name` fields. Args will be used to validate function calls and Name will be used in debug/log output.
|
||||||
|
3. *Add your `Function` to the `FuncTable`.* Make sure your `Operations`s get added to the table generated in `GenFuncTable`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
Copyright (C) 2019 Aidan Hahn.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/
|
||||||
28
stdlib/stdlib.go
Normal file
28
stdlib/stdlib.go
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* SHS: Syntactically Homogeneous Shell
|
||||||
|
* Copyright (C) 2019 Aidan Hahn
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package stdlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.callpipe.com/aidan/shs/ast"
|
||||||
|
"git.callpipe.com/aidan/shs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenFuncTable() ast.FuncTable {
|
||||||
|
var stdlib FuncTable
|
||||||
|
return stdlib
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue