diff --git a/readme.md b/readme.md index bd8de79..01c0cb3 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,6 @@ ARGON 3 is a math-driven programming language designed to make code easy to read and write. It's not meant to be fast, as it's interpreted. This specification should be used as a guideline, and is subject to change for later versions. Later updates for Argon 3 should be backwards compatible (where possible) with code designed for older versions of the interpreter. ## 📚 Features - - Easy to read and write: Argon 3 is designed with clarity of code in mind, making it easier for you and others to read and write code. - All numbers are stored as rational numbers, preventing precision errors. - Math-driven: Designed for mathematical computations, Argon 3 uses techniques and rules set in maths. It's designed to be easy for mathematicians to write and understand algorithms in. @@ -20,7 +19,6 @@ ARGON 3 is a math-driven programming language designed to make code easy to read As of now, Argon 3 does not have an installer. Feel free to clone this repo and run the `build` file for your plateform. the build will be found in `bin/argon(.exe)`. ## 📖 Usage - To use Argon 3, you can create a file with the .ar extension and write your code in it. Then, you can run your code using the interpreter. For example, if you have a file called example.ar, you can run it using the following command: ``` diff --git a/src/built-ins.go b/src/built-ins.go index 61d5ac5..3061898 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -3,7 +3,7 @@ package main var vars = scope{} func init() { - vars["vars"] = vars + vars["global"] = vars vars["term"] = ArTerm vars["true"] = true vars["false"] = false @@ -21,10 +21,33 @@ func init() { } return nil, ArErr{TYPE: "TypeError", message: "Cannot get length of " + typeof(a[0]), EXISTS: true} }} + vars["map"] = builtinFunc{"map", func(a ...any) (any, ArErr) { + if len(a) == 0 { + return ArMap{}, ArErr{} + } + switch x := a[0].(type) { + case ArMap: + return x, ArErr{} + case string: + newmap := ArMap{} + for i, v := range x { + newmap[i] = string(v) + } + return newmap, ArErr{} + case []any: + newmap := ArMap{} + for i, v := range x { + newmap[i] = v + } + return newmap, ArErr{} + } + return nil, ArErr{TYPE: "TypeError", message: "Cannot create map from " + typeof(a[0]), EXISTS: true} + }} vars["time"] = ArTime vars["PI"] = PI vars["π"] = PI vars["e"] = e sqrt := builtinFunc{"sqrt", ArgonSqrt} vars["sqrt"] = sqrt + vars["thread"] = builtinFunc{"thread", ArThread} } diff --git a/src/call.go b/src/call.go index f698bd0..be008d8 100644 --- a/src/call.go +++ b/src/call.go @@ -30,7 +30,7 @@ func parseCall(code UNPARSEcode, index int, codelines []UNPARSEcode) (any, bool, for i := 1; i < len(splitby); i++ { name := strings.Join(splitby[0:i], "(") argstr := strings.Join(splitby[i:], "(") - args, success, argserr := getValuesFromCommas(argstr, index, codelines) + args, success, argserr := getValuesFromLetter(argstr, ",", index, codelines, true) arguments = args if !success { if i == len(splitby)-1 { @@ -56,9 +56,18 @@ func parseCall(code UNPARSEcode, index int, codelines []UNPARSEcode) (any, bool, } func runCall(c call, stack stack) (any, ArErr) { - callable, err := runVal(c.callable, stack) - if err.EXISTS { - return nil, err + var callable any + switch x := c.callable.(type) { + case builtinFunc: + callable = x + case Callable: + callable = x + default: + callable_, err := runVal(c.callable, stack) + if err.EXISTS { + return nil, err + } + callable = callable_ } args := []any{} level := append(stack, scope{}) diff --git a/src/commaseperate.go b/src/commaseperate.go deleted file mode 100644 index f08a8b3..0000000 --- a/src/commaseperate.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "strings" -) - -func getValuesFromCommas(str string, index int, codelines []UNPARSEcode) ([]any, bool, ArErr) { - // make a function which takes a string of code and returns a translated values - str = strings.Trim(str, " ") - commasplit := strings.Split(str, ",") - temp := []string{} - arguments := []any{} - if str != "" { - for i, arg := range commasplit { - temp = append(temp, arg) - test := strings.TrimSpace(strings.Join(temp, ",")) - resp, worked, _, _ := translateVal(UNPARSEcode{code: test, realcode: codelines[index].realcode, line: index + 1, path: codelines[index].path}, index, codelines, false) - if worked { - arguments = append(arguments, resp) - temp = []string{} - } else if i == len(commasplit)-1 { - return nil, false, ArErr{"Syntax Error", "invalid argument", codelines[index].line, codelines[index].path, codelines[index].realcode, true} - } - } - } - return arguments, true, ArErr{} -} diff --git a/src/letterseperateseperate.go b/src/letterseperateseperate.go new file mode 100644 index 0000000..5d1b925 --- /dev/null +++ b/src/letterseperateseperate.go @@ -0,0 +1,32 @@ +package main + +import ( + "strings" +) + +func getValuesFromLetter(str string, splitstr string, index int, codelines []UNPARSEcode, allowempty bool) ([]any, bool, ArErr) { + // make a function which takes a string of code and returns a translated values + str = strings.Trim(str, " ") + commasplit := strings.Split(str, splitstr) + temp := []string{} + arguments := []any{} + if str != "" { + for i, arg := range commasplit { + temp = append(temp, arg) + test := strings.TrimSpace(strings.Join(temp, splitstr)) + if test == "" && allowempty { + arguments = append(arguments, nil) + temp = []string{} + } else { + resp, worked, _, _ := translateVal(UNPARSEcode{code: test, realcode: codelines[index].realcode, line: index + 1, path: codelines[index].path}, index, codelines, false) + if worked { + arguments = append(arguments, resp) + temp = []string{} + } else if i == len(commasplit)-1 { + return nil, false, ArErr{"Syntax Error", "invalid argument", codelines[index].line, codelines[index].path, codelines[index].realcode, true} + } + } + } + } + return arguments, true, ArErr{} +} diff --git a/src/map.go b/src/map.go deleted file mode 100644 index e415375..0000000 --- a/src/map.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "fmt" - "strings" -) - -type ArMap = map[any]any - -type ArClass struct { - value any - MAP ArMap -} - -var mapGetCompile = makeRegex(`(.|\n)+\.([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*( *)`) - -type ArMapGet struct { - VAL any - key any - line int - code string - path string -} - -func mapGet(r ArMapGet, stack stack) (any, ArErr) { - resp, err := runVal(r.VAL, stack) - if err.EXISTS { - return nil, err - } - key, err := runVal(r.key, stack) - if err.EXISTS { - return nil, err - } - switch m := resp.(type) { - case ArMap: - if _, ok := m[key]; !ok { - return nil, ArErr{ - "KeyError", - "key '" + fmt.Sprint(key) + "' not found", - r.line, - r.path, - r.code, - true, - } - } - return m[key], ArErr{} - case ArClass: - if _, ok := m.MAP[key]; !ok { - return nil, ArErr{ - "KeyError", - "key '" + fmt.Sprint(key) + "' not found", - r.line, - r.path, - r.code, - true, - } - } - return m.MAP[key], ArErr{} - - } - return nil, ArErr{ - "TypeError", - "cannot read " + anyToArgon(key, true, true, 3, 0, false, 0) + " from type '" + typeof(resp) + "'", - r.line, - r.path, - r.code, - true, - } -} - -func classVal(r any) any { - if _, ok := r.(ArClass); ok { - return r.(ArClass).value - } - return r -} - -func isMapGet(code UNPARSEcode) bool { - return mapGetCompile.MatchString(code.code) -} - -func mapGetParse(code UNPARSEcode, index int, codelines []UNPARSEcode) (ArMapGet, bool, ArErr, int) { - trim := strings.TrimSpace(code.code) - split := strings.Split(trim, ".") - start := strings.Join(split[:len(split)-1], ".") - key := split[len(split)-1] - resp, worked, err, i := translateVal(UNPARSEcode{code: start, realcode: code.realcode, line: code.line, path: code.path}, index, codelines, false) - if !worked { - return ArMapGet{}, false, err, i - } - k := key - return ArMapGet{resp, k, code.line, code.realcode, code.path}, true, ArErr{}, 1 -} diff --git a/src/mapAndArray.go b/src/mapAndArray.go new file mode 100644 index 0000000..244b46a --- /dev/null +++ b/src/mapAndArray.go @@ -0,0 +1,477 @@ +package main + +import ( + "fmt" + "strings" +) + +type ArMap = map[any]any +type ArArray = []any + +type ArClass struct { + value any + MAP ArMap +} + +var mapGetCompile = makeRegex(`(.|\n)+\.([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*( *)`) +var indexGetCompile = makeRegex(`(.|\n)+\[(.|\n)+\]( *)`) + +type ArMapGet struct { + VAL any + start any + end any + step any + index bool + numberofindex int + line int + code string + path string +} + +func mapGet(r ArMapGet, stack stack) (any, ArErr) { + resp, err := runVal(r.VAL, stack) + if err.EXISTS { + return nil, err + } + switch m := resp.(type) { + case ArMap: + if r.numberofindex > 1 { + return nil, ArErr{ + "IndexError", + "index not found", + r.line, + r.path, + r.code, + true, + } + } + key, err := runVal(r.start, stack) + if err.EXISTS { + return nil, err + } + if _, ok := m[key]; !ok { + return nil, ArErr{ + "KeyError", + "key '" + fmt.Sprint(key) + "' not found", + r.line, + r.path, + r.code, + true, + } + } + return m[key], ArErr{} + + case ArArray: + startindex := 0 + endindex := 1 + step := 1 + + if !r.index { + key, err := runVal(r.start, stack) + if err.EXISTS { + return nil, err + } + if key == "length" { + return len(m), ArErr{} + } + } + if r.start != nil { + sindex, err := runVal(r.start, stack) + if err.EXISTS { + return nil, err + } + if typeof(sindex) != "number" { + return nil, ArErr{ + "TypeError", + "index must be a number", + r.line, + r.path, + r.code, + true, + } + } + num := sindex.(number) + if !num.IsInt() { + return nil, ArErr{ + "TypeError", + "index must be an integer", + r.line, + r.path, + r.code, + true, + } + } + startindex = int(num.Num().Int64()) + endindex = startindex + 1 + } + if r.end != nil { + eindex, err := runVal(r.end, stack) + if err.EXISTS { + return nil, err + } + if typeof(eindex) != "number" { + return nil, ArErr{ + "TypeError", + "ending index must be a number", + r.line, + r.path, + r.code, + true, + } + } + num := eindex.(number) + if !num.IsInt() { + return nil, ArErr{ + "TypeError", + "ending index must be an integer", + r.line, + r.path, + r.code, + true, + } + } + endindex = int(num.Num().Int64()) + } else if r.numberofindex > 1 { + endindex = len(m) + } + if r.step != nil { + step, err := runVal(r.step, stack) + if err.EXISTS { + return nil, err + } + if typeof(step) != "number" { + return nil, ArErr{ + "TypeError", + "step must be a number", + r.line, + r.path, + r.code, + true, + } + } + num := step.(number) + if !num.IsInt() { + return nil, ArErr{ + "TypeError", + "step must be an integer", + r.line, + r.path, + r.code, + true, + } + } + step = int(num.Num().Int64()) + } + if startindex < 0 { + startindex = len(m) + startindex + } + if endindex < 0 { + endindex = len(m) + endindex + } + if step < 0 { + step = -step + startindex, endindex = endindex, startindex + } + if startindex < 0 || startindex >= len(m) { + return nil, ArErr{ + "IndexError", + "index '" + fmt.Sprint(startindex) + "' out of range", + r.line, + r.path, + r.code, + true, + } + } + if endindex < 0 || endindex > len(m) { + return nil, ArErr{ + "IndexError", + "index '" + fmt.Sprint(endindex) + "' out of range", + r.line, + r.path, + r.code, + true, + } + } + if step == 0 { + return nil, ArErr{ + "ValueError", + "step cannot be 0", + r.line, + r.path, + r.code, + true, + } + } + return m[startindex:endindex:step], ArErr{} + case ArClass: + if r.numberofindex > 1 { + return nil, ArErr{ + "IndexError", + "index not found", + r.line, + r.path, + r.code, + true, + } + } + key, err := runVal(r.start, stack) + if err.EXISTS { + return nil, err + } + if _, ok := m.MAP[key]; !ok { + return nil, ArErr{ + "KeyError", + "key '" + fmt.Sprint(key) + "' not found", + r.line, + r.path, + r.code, + true, + } + } + return m.MAP[key], ArErr{} + case string: + startindex := 0 + endindex := 1 + step := 1 + + if !r.index { + key, err := runVal(r.start, stack) + if err.EXISTS { + return nil, err + } + if key == "length" { + return len(m), ArErr{} + } + } + if r.start != nil { + sindex, err := runVal(r.start, stack) + if err.EXISTS { + return nil, err + } + if typeof(sindex) != "number" { + return nil, ArErr{ + "TypeError", + "index must be a number", + r.line, + r.path, + r.code, + true, + } + } + num := sindex.(number) + if !num.IsInt() { + return nil, ArErr{ + "TypeError", + "index must be an integer", + r.line, + r.path, + r.code, + true, + } + } + startindex = int(num.Num().Int64()) + endindex = startindex + 1 + } + if r.end != nil { + eindex, err := runVal(r.end, stack) + if err.EXISTS { + return nil, err + } + if typeof(eindex) != "number" { + return nil, ArErr{ + "TypeError", + "ending index must be a number", + r.line, + r.path, + r.code, + true, + } + } + num := eindex.(number) + if !num.IsInt() { + return nil, ArErr{ + "TypeError", + "ending index must be an integer", + r.line, + r.path, + r.code, + true, + } + } + endindex = int(num.Num().Int64()) + } else if r.numberofindex > 1 { + endindex = len(m) + } + if r.step != nil { + step, err := runVal(r.step, stack) + if err.EXISTS { + return nil, err + } + if typeof(step) != "number" { + return nil, ArErr{ + "TypeError", + "step must be a number", + r.line, + r.path, + r.code, + true, + } + } + num := step.(number) + if !num.IsInt() { + return nil, ArErr{ + "TypeError", + "step must be an integer", + r.line, + r.path, + r.code, + true, + } + } + step = int(num.Num().Int64()) + } + if startindex < 0 { + startindex = len(m) + startindex + } + if endindex < 0 { + endindex = len(m) + endindex + } + if step < 0 { + step = -step + startindex, endindex = endindex, startindex + } + if startindex < 0 || startindex >= len(m) { + return nil, ArErr{ + "IndexError", + "index '" + fmt.Sprint(startindex) + "' out of range", + r.line, + r.path, + r.code, + true, + } + } + if endindex < 0 || endindex > len(m) { + return nil, ArErr{ + "IndexError", + "index '" + fmt.Sprint(endindex) + "' out of range", + r.line, + r.path, + r.code, + true, + } + } + if step == 0 { + return nil, ArErr{ + "ValueError", + "step cannot be 0", + r.line, + r.path, + r.code, + true, + } + } + return string(([]byte(m))[startindex:endindex:step]), ArErr{} + } + + key, err := runVal(r.start, stack) + if err.EXISTS { + return nil, err + } + return nil, ArErr{ + "TypeError", + "cannot read " + anyToArgon(key, true, true, 3, 0, false, 0) + " from type '" + typeof(resp) + "'", + r.line, + r.path, + r.code, + true, + } +} + +func classVal(r any) any { + if _, ok := r.(ArClass); ok { + return r.(ArClass).value + } + return r +} + +func isMapGet(code UNPARSEcode) bool { + return mapGetCompile.MatchString(code.code) +} + +func mapGetParse(code UNPARSEcode, index int, codelines []UNPARSEcode) (ArMapGet, bool, ArErr, int) { + trim := strings.TrimSpace(code.code) + split := strings.Split(trim, ".") + start := strings.Join(split[:len(split)-1], ".") + key := split[len(split)-1] + resp, worked, err, i := translateVal(UNPARSEcode{code: start, realcode: code.realcode, line: code.line, path: code.path}, index, codelines, false) + if !worked { + return ArMapGet{}, false, err, i + } + k := key + return ArMapGet{resp, k, nil, nil, false, 1, code.line, code.realcode, code.path}, true, ArErr{}, 1 +} + +func isIndexGet(code UNPARSEcode) bool { + return indexGetCompile.MatchString(code.code) +} + +func indexGetParse(code UNPARSEcode, index int, codelines []UNPARSEcode) (ArMapGet, bool, ArErr, int) { + trim := strings.TrimSpace(code.code) + trim = trim[:len(trim)-1] + split := strings.Split(trim, "[") + var toindex any + var start any + var end any + var step any + numberofindexs := 0 + for i := 1; i < len(split); i++ { + ti := strings.Join(split[:i], "[") + innerbrackets := strings.Join(split[i:], "[") + args, success, argserr := getValuesFromLetter(innerbrackets, ":", index, codelines, true) + if !success { + if i == len(split)-1 { + return ArMapGet{}, false, argserr, 1 + } + continue + } + if len(args) > 3 { + return ArMapGet{}, false, ArErr{ + "SyntaxError", + "too many arguments for index get", + code.line, + code.path, + code.realcode, + true, + }, 1 + } + tival, worked, err, i := translateVal(UNPARSEcode{code: ti, realcode: code.realcode, line: code.line, path: code.path}, index, codelines, false) + if !worked { + if i == len(split)-1 { + return ArMapGet{}, false, err, i + } + continue + } + numberofindexs = len(args) + if len(args) >= 1 { + toindex = tival + start = args[0] + } + if len(args) >= 2 { + end = args[1] + } + if len(args) >= 3 { + step = args[2] + } + } + if toindex == nil { + return ArMapGet{}, false, ArErr{ + "SyntaxError", + "invalid index get", + code.line, + code.path, + code.realcode, + true, + }, 1 + } + return ArMapGet{toindex, start, end, step, true, numberofindexs, code.line, code.realcode, code.path}, true, ArErr{}, 1 +} diff --git a/src/threading.go b/src/threading.go new file mode 100644 index 0000000..172f91e --- /dev/null +++ b/src/threading.go @@ -0,0 +1,49 @@ +package main + +import "sync" + +func ArThread(args ...any) (any, ArErr) { + if len(args) == 0 { + return nil, ArErr{TYPE: "TypeError", message: "Cannot call thread without a function", EXISTS: true} + } + var tocall any + switch x := args[0].(type) { + case Callable: + tocall = x + case builtinFunc: + tocall = x + default: + return nil, ArErr{TYPE: "TypeError", message: "Cannot call thread with a '" + typeof(args[0]) + "'", EXISTS: true} + } + var resp any + var err ArErr + currentscope := stack{vars, scope{}} + hasrun := false + joined := false + var wg sync.WaitGroup + threaMap := ArMap{ + "start": builtinFunc{"start", func(args ...any) (any, ArErr) { + if hasrun { + return nil, ArErr{TYPE: "Runtime Error", message: "Cannot start a thread twice", EXISTS: true} + } + hasrun = true + wg.Add(1) + go func() { + resp, err = runCall(call{tocall, []any{}, "", 0, ""}, currentscope) + wg.Done() + }() + return nil, ArErr{} + }}, + "join": builtinFunc{"join", func(args ...any) (any, ArErr) { + if !hasrun { + return nil, ArErr{TYPE: "Runtime Error", message: "Cannot join a thread that has not started", EXISTS: true} + } else if joined { + return nil, ArErr{TYPE: "Runtime Error", message: "Cannot join a thread twice", EXISTS: true} + } + joined = true + wg.Wait() + return resp, err + }}, + } + return threaMap, ArErr{} +} diff --git a/src/to-argon.go b/src/to-argon.go index f908eb3..5ca250a 100644 --- a/src/to-argon.go +++ b/src/to-argon.go @@ -70,6 +70,9 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, color b output = append(output, "\x1b[0m") } case ArMap: + if len(x) == 0 { + return "{}" + } keys := make([]any, len(x)) i := 0 diff --git a/src/translate.go b/src/translate.go index f3d0ced..02db5cd 100644 --- a/src/translate.go +++ b/src/translate.go @@ -61,6 +61,8 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine b return parseVariable(code) } else if isMapGet(code) { return mapGetParse(code, index, codelines) + } else if isIndexGet(code) { + return indexGetParse(code, index, codelines) } else if isString(code) { return parseString(code) } diff --git a/src/variable.go b/src/variable.go index beea4a2..2e26a90 100644 --- a/src/variable.go +++ b/src/variable.go @@ -5,7 +5,7 @@ import ( ) var variableCompile = makeRegex(`( *)([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*( *)`) -var validname = makeRegex(`(.|\n)+(\(( *)((([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*)(( *)\,( *)([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*)*)?( *)\))`) +var validname = makeRegex(`(.|\n)*(\(( *)((([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*)(( *)\,( *)([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*)*)?( *)\))`) var setVariableCompile = makeRegex(`( *)(let( +))(.|\n)+( *)=(.|\n)+`) var autoAsignVariableCompile = makeRegex(`(.|\n)+=(.|\n)+`) var deleteVariableCompile = makeRegex(`( *)delete( +)(.|\n)+( *)`) @@ -101,6 +101,9 @@ func nameToTranslated(code UNPARSEcode, index int, lines []UNPARSEcode) (any, bo } } name := strings.TrimSpace(trimmed[:start]) + if name == "" { + return setFunction{toset: nil, params: params}, true, ArErr{}, 1 + } if blockedVariableNames[name] { return accessVariable{}, false, ArErr{"Naming Error", "\"" + name + "\" is a reserved keyword", code.line, code.path, code.realcode, true}, 1 } @@ -136,6 +139,9 @@ func parseSetVariable(code UNPARSEcode, index int, lines []UNPARSEcode) (setVari function = true params = toset.(setFunction).params toset = toset.(setFunction).toset + if toset == nil { + return setVariable{}, false, ArErr{"Type Error", "can't set for non variable, did you mean to put 'let' before?", code.line, code.path, code.realcode, true}, 1 + } default: return setVariable{}, false, ArErr{"Type Error", "can't set for non variable, did you mean '=='?", code.line, code.path, code.realcode, true}, 1 } @@ -210,7 +216,7 @@ func setVariableValue(v setVariable, stack stack) (any, ArErr) { if err.EXISTS { return nil, err } - key, err := runVal(x.key, stack) + key, err := runVal(x.start, stack) if err.EXISTS { return nil, err } @@ -260,7 +266,7 @@ func runDelete(d ArDelete, stack stack) (any, ArErr) { if err.EXISTS { return nil, err } - key, err := runVal(x.key, stack) + key, err := runVal(x.start, stack) if err.EXISTS { return nil, err } diff --git a/test.ar b/test.ar index f3e6787..1b966bb 100644 --- a/test.ar +++ b/test.ar @@ -1,4 +1,7 @@ -x = 10 -term.log(x) -x = x+1 -term.log(x) \ No newline at end of file +let t = thread(f()=time.snooze(1)) + +term.log("start") +t.start() +term.log('started') +let val = t.join() +term.log('joined', val) \ No newline at end of file