Skip to content

Commit

Permalink
Import project
Browse files Browse the repository at this point in the history
  • Loading branch information
skius committed Dec 24, 2020
1 parent cb22bdc commit 10ff880
Show file tree
Hide file tree
Showing 20 changed files with 16,801 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
328 changes: 328 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
package ast

import (
"github.com/skius/stringlang/token"
"strconv"
"strings"
)

func NewContext(args []string, funcs map[string]func([]string)string) Context {
return Context {
Args: args,
VariableMap: make(map[Var]Val),
FunctionMap: funcs,
}
}

type Context struct {
Args []string
VariableMap map[Var]Val
FunctionMap map[string]func([]string)string
MaxStackSize int64
MaxWhileIter int
}

type Attrib interface {}

type Expr interface {
Eval(Context) Val
String() string
}

type Val string
const False = Val("")
func NewVal(a Attrib) (Expr, error) {
quoted := attribToString(a)
return Val(quoted[1:len(quoted)-1]), nil
}
func (v Val) Eval(c Context) Val {
return v
}
func (v Val) String() string {
return "\"" + string(v) + "\""
}

type ExprSeq []Expr
func NewExprSeq() (Expr, error) {
return ExprSeq([]Expr{}), nil
}
func ExprSeqAppend(s, e Attrib) (Expr, error) {
seq := s.(ExprSeq)
exp := e.(Expr)
seq2 := append(seq, exp)
return seq2, nil
}
func (es ExprSeq) Eval(c Context) Val {
var last Val
for _, exp := range es {
last = exp.Eval(c)
}
return last
}
func (es ExprSeq) String() string {
str := ""
for i, exp := range es {
if i < len(es) - 1 {
str += exp.String() + ";\n"
} else {
str += exp.String()
}
}
return str
}

type Arg int
func NewArg(i Attrib) (Expr, error) {
s := attribToString(i)
intValue, err := strconv.Atoi(s)
return Arg(intValue), err
}
func (a Arg) Eval(c Context) Val {
if int(a) > len(c.Args) {
return ""
}
return Val(c.Args[a])
}
func (a Arg) String() string {
return "$" + strconv.Itoa(int(a))
}

type Equals struct {
A Expr
B Expr
}
func NewEquals(a, b Attrib) (Expr, error) {
return Equals{A: a.(Expr), B: b.(Expr)}, nil
}
func (e Equals) Eval(c Context) Val {
val := "false"
if e.A.Eval(c) == e.B.Eval(c) {
val = "true"
}
return Val(val)
}
func (e Equals) String() string {
return e.A.String() + " == " + e.B.String()
}

type NotEquals struct {
A Expr
B Expr
}
func NewNotEquals(a, b Attrib) (Expr, error) {
return NotEquals{A: a.(Expr), B: b.(Expr)}, nil
}
func (e NotEquals) Eval(c Context) Val {
val := "false"
if e.A.Eval(c) != e.B.Eval(c) {
val = "true"
}
return Val(val)
}
func (e NotEquals) String() string {
return e.A.String() + " != " + e.B.String()
}

type Or struct {
A Expr
B Expr
}
func NewOr(a, b Attrib) (Expr, error) {
return Or{A: a.(Expr), B: b.(Expr)}, nil
}
func (o Or) Eval(c Context) Val {
if boolOf(o.A.Eval(c)) || boolOf(o.B.Eval(c)) {
return Val("true")
} else {
return Val("false")
}
}
func (o Or) String() string {
return o.A.String() + " || " + o.B.String()
}

type And struct {
A Expr
B Expr
}
func NewAnd(a, b Attrib) (Expr, error) {
return And{A: a.(Expr), B: b.(Expr)}, nil
}
func (a And) Eval(c Context) Val {
if boolOf(a.A.Eval(c)) && boolOf(a.B.Eval(c)) {
return Val("true")
} else {
return Val("false")
}
}
func (a And) String() string {
return a.A.String() + " && " + a.B.String()
}

type Concat struct {
A Expr
B Expr
}
func NewConcat(a, b Attrib) (Expr, error) {
return Concat{A: a.(Expr), B: b.(Expr)}, nil
}
func (cc Concat) Eval(c Context) Val {
return cc.A.Eval(c) + cc.B.Eval(c)
}
func (cc Concat) String() string {
return cc.A.String() + " + " + cc.B.String()
}

type Var string
func NewVar(a Attrib) (Expr, error) {
return Var(attribToString(a)), nil
}
func (v Var) Eval(c Context) Val {
return c.VariableMap[v]
}
func (v Var) String() string {
return string(v)
}

type Call struct {
Fn Var
Args []Expr
}
func NewCall(f, as Attrib) (Expr, error) {
fn := f.(Var)
args := as.(ExprSeq)
return Call {Fn: fn, Args: args}, nil
}
func (ca Call) Eval(c Context) Val {
vals := make([]string, 0, len(ca.Args))
for _, argExp := range ca.Args {
v := argExp.Eval(c)
vals = append(vals, string(v))
}
fn, ok := c.FunctionMap[string(ca.Fn)]
if !ok {
panic("function '" + string(ca.Fn) + "' not found.")
}
res := fn(vals)
return Val(res)
}
func (ca Call) String() string {
args := make([]string, 0, len(ca.Args))
for _, arg := range ca.Args {
args = append(args, arg.String())
}

return ca.Fn.String() + "(" + strings.Join(args, ", ") + ")"
}

type Index struct {
Source Expr
I Expr
}
func NewIndex(s, i Attrib) (Expr, error) {
return Index {Source: s.(Expr), I: i.(Expr)}, nil
}
func NewIndexInt(s, i Attrib) (Expr, error) {
return Index {Source: s.(Expr), I: Val(attribToString(i))}, nil
}
func (i Index) Eval(c Context) Val {
src := string(i.Source.Eval(c))
idx, err := strconv.Atoi(string(i.I.Eval(c)))
if err != nil {
return Val("")
}
if idx >= len(src) {
return Val("")
}
return Val(src[idx])
}
func (i Index) String() string {
return i.Source.String() + "[" + i.I.String() + "]"
}

type Assn struct {
V Var
E Expr
}
func NewAssn(v, e Attrib) (Expr, error) {
va := v.(Var)
ex := e.(Expr)
return Assn {V: va, E: ex}, nil
}
func (a Assn) Eval(c Context) Val {
newVal := a.E.Eval(c)
c.VariableMap[a.V] = newVal
return newVal
}
func (a Assn) String() string {
return a.V.String() + " = " + a.E.String()
}

type IfElse struct {
Cond Expr
Then Expr
Else Expr
}
func NewIfElse(c, t, e Attrib) (Expr, error) {
co := c.(Expr)
th := t.(Expr)
el := e.(Expr)
return IfElse{Cond: co, Then: th, Else: el}, nil
}
func (e IfElse) Eval(c Context) Val {
if boolOf(e.Cond.Eval(c)) {
return e.Then.Eval(c)
} else {
return e.Else.Eval(c)
}
}
func (e IfElse) String() string {
str := "if (" + e.Cond.String() + ") {\n\t" + e.Then.String() + "\n} else {\n\t" + e.Else.String() + "\n}"
return str
}

type While struct {
Cond Expr
Body Expr
}
func NewWhile(c, b Attrib) (Expr, error) {
co := c.(Expr)
bo := b.(Expr)
return While{Cond: co, Body: bo}, nil
}
func (e While) Eval(c Context) Val {
var cond Val = e.Cond.Eval(c)
var body Val
steps := 0
for boolOf(cond) {
body = e.Body.Eval(c)
cond = e.Cond.Eval(c)

if c.MaxWhileIter > -1 && steps > c.MaxWhileIter {
break
}
if c.MaxStackSize > -1 && checkSize(c.VariableMap) > c.MaxStackSize {
break
}
steps++
}
return body
}
func (e While) String() string {
str := "while (" + e.Cond.String() + ") {\n\t" + e.Body.String() + "\n}"
return str
}


func attribToString(a Attrib) string {
return string(a.(*token.Token).Lit)
}
func boolOf(v Val) bool {
return v != "false" && v != ""
}
func checkSize(m map[Var]Val) (total int64) {
for k, v := range m {
total += int64(len(k)) + int64(len(v))
}
return
}
63 changes: 63 additions & 0 deletions cmd/stringlang/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main

import (
"fmt"
"github.com/skius/stringlang"
"io/ioutil"
"math/rand"
"os"
"strconv"
"strings"
"time"
)

func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: ./stringlang <program.stringlang> ..<args>")
return
}
file := os.Args[1]
content, err := ioutil.ReadFile(file)
if err != nil {
panic(err)
}

expr, err := stringlang.Parse(content)
if err != nil {
panic(err)
}

rand.Seed(time.Now().UnixNano())
funcs := map[string]func([]string)string {
"random": func(args []string) string {
num := len(args)
if num == 0 {
return strconv.Itoa(rand.Intn(10) + 1)
} else if num == 1 {
val, err := strconv.Atoi(args[0])
if err == nil {
return strconv.Itoa(rand.Intn(val) + 1)
}
}
return args[rand.Intn(num)]
},
"length" : func(args []string) string {
return strconv.Itoa(len(args[0]))
},
}

// We want the .stringlang file, and not the interpreter's path to be argument 0
args := make([]string, len(os.Args) - 1)
for i := 1; i < len(os.Args); i++ {
args[i-1] = os.Args[i]
}

ctx := stringlang.NewContext(args, funcs)
ctx.MaxWhileIter = 10000
ctx.MaxStackSize = 100 * 1024 * 1024 // 100MB limit for variables

result := string(expr.Eval(ctx))

fmt.Println("Returns:")
fmt.Println(strings.ReplaceAll(result, `\n`, "\n"))
}
Loading

0 comments on commit 10ff880

Please sign in to comment.