diff --git a/build.bat b/build.bat index 5d2dc11..1318b26 100644 --- a/build.bat +++ b/build.bat @@ -1,3 +1,3 @@ @echo off -go build -o bin/Argon-v3.exe ./src \ No newline at end of file +go build -o bin/argon.exe ./src \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..c9fb28a --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +go build -o bin/argon ./src \ No newline at end of file diff --git a/readme.md b/readme.md index 32ac8e8..2f7c12a 100644 --- a/readme.md +++ b/readme.md @@ -10,6 +10,7 @@ ARGON 3 is a math-driven programming language designed to make code easy to read ## 📚 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. - Interpreted: Argon 3 is an interpreted language, so you don't need to compile your code before running it. - Cross-platform: Argon 3 can be run on any platform that has an interpreter for it. diff --git a/spec.md b/spec.md index 6c501d6..97d3efe 100644 --- a/spec.md +++ b/spec.md @@ -64,7 +64,7 @@ output: f(10) = 121 ``` -if the function does not return, then the value returned is `unknown` +if the function does not return, then the value returned is `null` --- diff --git a/src/built-in-functions.go b/src/built-in-functions.go new file mode 100644 index 0000000..40ca7b9 --- /dev/null +++ b/src/built-in-functions.go @@ -0,0 +1,34 @@ +package main + +import "fmt" + +type builtinFunc struct { + name string + FUNC func(...any) (any, ArErr) +} + +func ArgonLog(args ...any) (any, ArErr) { + output := []any{} + for i := 0; i < len(args); i++ { + output = append(output, anyToArgon(args[i], false)) + } + fmt.Println(output...) + return nil, ArErr{} +} + +func ArgonAdd(args ...any) (any, ArErr) { + return reduce(func(x any, y any) any { + return newNumber().Add(x.(number), y.(number)) + }, args), ArErr{} +} +func ArgonDiv(args ...any) (any, ArErr) { + return reduce(func(x any, y any) any { + return newNumber().Quo(y.(number), x.(number)) + }, args), ArErr{} +} + +func ArgonMult(args ...any) (any, ArErr) { + return reduce(func(x any, y any) any { + return newNumber().Mul(y.(number), x.(number)) + }, args), ArErr{} +} diff --git a/src/built-ins.go b/src/built-ins.go new file mode 100644 index 0000000..d250468 --- /dev/null +++ b/src/built-ins.go @@ -0,0 +1,27 @@ +package main + +var vars = map[string]variableValue{} + +func init() { + vars["log"] = variableValue{ + EXISTS: true, + VAL: builtinFunc{"log", ArgonLog}, + } + vars["add"] = variableValue{ + EXISTS: true, + VAL: builtinFunc{"add", ArgonAdd}, + } + vars["div"] = variableValue{ + EXISTS: true, + VAL: builtinFunc{"div", ArgonDiv}, + } + vars["mult"] = variableValue{ + EXISTS: true, + VAL: builtinFunc{"mult", ArgonMult}, + } + pi, _ := newNumber().SetString("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989") + vars["PI"] = variableValue{ + EXISTS: true, + VAL: pi, + } +} diff --git a/src/call.go b/src/call.go new file mode 100644 index 0000000..7859baf --- /dev/null +++ b/src/call.go @@ -0,0 +1,76 @@ +package main + +import ( + "strings" +) + +var callCompile = makeRegex("( *).+\\(.*\\)( *)") + +type call struct { + callable any + args []any + code string + line int +} + +func isCall(code UNPARSEcode) bool { + return callCompile.MatchString(code.code) +} + +func parseCall(code UNPARSEcode, index int, codelines []UNPARSEcode) (any, bool, ArErr, int) { + trim := strings.TrimSpace(code.code) + trim = trim[:len(trim)-1] + splitby := strings.Split(trim, "(") + + var works bool + var callable any + var arguments []any + 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) + arguments = args + if !success { + if i == len(splitby)-1 { + return nil, false, argserr, 1 + } + continue + } + resp, worked, _, _ := translateVal(UNPARSEcode{code: name, realcode: code.realcode, line: index + 1, path: code.path}, index, codelines, false) + if !worked { + if i == len(splitby)-1 { + return nil, false, ArErr{"Syntax Error", "invalid callable", code.line, code.path, code.realcode, true}, 1 + } + continue + } + works = true + callable = resp + break + } + if !works { + return nil, false, ArErr{"Syntax Error", "invalid call", code.line, code.path, code.realcode, true}, 1 + } + return call{callable: callable, args: arguments, line: code.line, code: code.code}, true, ArErr{}, 1 +} + +func runCall(c call, stack []map[string]variableValue) (any, ArErr) { + callable, err := runVal(c.callable, stack) + if err.EXISTS { + return nil, err + } + args := []any{} + for _, arg := range c.args { + resp, err := runVal(arg, stack) + if err.EXISTS { + return nil, err + } + args = append(args, resp) + } + switch x := callable.(type) { + case builtinFunc: + return x.FUNC(args...) + case Callable: + return nil, ArErr{"Runtime Error", "cannot call a class", c.line, "", c.code, true} + } + return nil, ArErr{"Runtime Error", typeof(callable) + "' is not callable", c.line, "", c.code, true} +} diff --git a/src/callable.go b/src/callable.go new file mode 100644 index 0000000..ee03c02 --- /dev/null +++ b/src/callable.go @@ -0,0 +1,9 @@ +package main + +type Callable struct { + name string + params []string + code []any + stack []map[string]variableValue + line int +} diff --git a/src/commaseperate.go b/src/commaseperate.go new file mode 100644 index 0000000..f08a8b3 --- /dev/null +++ b/src/commaseperate.go @@ -0,0 +1,27 @@ +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/comment.go b/src/comment.go index 1e6fcd7..fcdfb47 100644 --- a/src/comment.go +++ b/src/comment.go @@ -1,7 +1,29 @@ package main -var commentCompile = makeRegex("( *)//.*") +import ( + "strings" +) + +var commentCompile = makeRegex(".*//.*") func isComment(code UNPARSEcode) bool { return commentCompile.MatchString(code.code) } + +func isBlank(code UNPARSEcode) bool { + return strings.TrimSpace(code.code) == "" +} + +func parseComment(code UNPARSEcode, index int, codelines []UNPARSEcode) (any, bool, ArErr) { + split := strings.Split(code.code, "//") + temp := []string{} + for i := 0; i < len(split)-1; i++ { + temp = append(temp, split[i]) + joined := strings.Join(temp, "//") + resp, worked, _, _ := translateVal(UNPARSEcode{code: joined, realcode: code.realcode, line: code.line, path: code.path}, index, codelines, true) + if worked { + return resp, true, ArErr{} + } + } + return nil, false, ArErr{"Syntax Error", "invalid comment", code.line, code.path, code.realcode, true} +} diff --git a/src/error.go b/src/error.go new file mode 100644 index 0000000..bc6e882 --- /dev/null +++ b/src/error.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "os" +) + +type ArErr struct { + TYPE string + message string + line int + path string + code string + EXISTS bool +} + +func panicErr(err ArErr) { + fmt.Println(" File:", err.path+":"+fmt.Sprint(err.line)) + fmt.Println(" " + err.code) + fmt.Println() + fmt.Println(err.TYPE+":", err.message) + os.Exit(1) +} diff --git a/src/import.go b/src/import.go index 35dc7c5..667d9ad 100644 --- a/src/import.go +++ b/src/import.go @@ -32,7 +32,8 @@ func readFile(path string) []UNPARSEcode { output := []UNPARSEcode{} line := 1 for scanner.Scan() { - output = append(output, UNPARSEcode{scanner.Text(), line}) + text := scanner.Text() + output = append(output, UNPARSEcode{text, text, line, path}) line++ } @@ -43,7 +44,7 @@ func readFile(path string) []UNPARSEcode { return output } -func importMod(realpath string, origin string, main bool) string { +func importMod(realpath string, origin string, main bool) ArErr { extention := filepath.Ext(realpath) path := realpath if extention == "" { @@ -51,11 +52,11 @@ func importMod(realpath string, origin string, main bool) string { } ex, err := os.Getwd() if err != nil { - return err.Error() + return ArErr{"Import Error", err.Error(), 0, realpath, "", true} } executable, err := os.Executable() if err != nil { - return err.Error() + return ArErr{"Import Error", err.Error(), 0, realpath, "", true} } executable = filepath.Dir(executable) isABS := filepath.IsAbs(path) @@ -89,14 +90,17 @@ func importMod(realpath string, origin string, main bool) string { } if !found { - return "File does not exist: " + realpath + return ArErr{"Import Error", "File does not exist: " + realpath, 0, realpath, "", true} } codelines := readFile(p) translated, translationerr := translate(codelines) - if translationerr != "" { + if translationerr.EXISTS { return translationerr } - run(translated) - return "" + _, runimeErr := run(translated, []map[string]variableValue{vars}) + if runimeErr.EXISTS { + return runimeErr + } + return ArErr{} } diff --git a/src/main.go b/src/main.go index 7ab9269..35f25ee 100644 --- a/src/main.go +++ b/src/main.go @@ -8,7 +8,6 @@ import ( var Args = os.Args[1:] func main() { - ex, e := os.Getwd() if e != nil { panic(e) @@ -17,7 +16,7 @@ func main() { panic("No file specified") } err := importMod(Args[0], ex, true) - if err != "" { - panic(err) + if err.EXISTS { + panicErr(err) } } diff --git a/src/number.go b/src/number.go index 044a163..d20c31e 100644 --- a/src/number.go +++ b/src/number.go @@ -6,7 +6,16 @@ import ( "strings" ) -var numberCompile = makeRegex("( *)((\\-)?(([0-9]*(\\.[0-9]+)?)(e((\\-|\\+)?([0-9]+(\\.[0-9]+)?)))?)|(0b[10]+(.[10]+)?(e((\\-|\\+)?([0-9]+(\\.[0-9]+)?)))?)|(0x[a-fA-F0-9]+(.[a-fA-F0-9]+)?)|(0o[0-7]+(.[0-7]+)?(e((\\-|\\+)?([0-9]+(\\.[0-9]+)?)))?))( *)") +type translateNumber struct { + number + code string + line int +} + +var numberCompile = makeRegex("( *)(\\-)?((([0-9]+(\\.[0-9]+)?)|(\\.[0-9]+))(e((\\-|\\+)?([0-9]+(\\.[0-9]+)?)))?)( *)") +var binaryCompile = makeRegex("( *)(0b[10]+(.[10]+)?(e((\\-|\\+)?([0-9]+(\\.[0-9]+)?)))?)( *)") +var hexCompile = makeRegex("( *)(0x[a-fA-F0-9]+(.[a-fA-F0-9]+)?)( *)") +var octalCompile = makeRegex("( *)(0o[0-7]+(.[0-7]+)?(e((\\-|\\+)?([0-9]+(\\.[0-9]+)?)))?)( *)") // a number type type number = *big.Rat @@ -22,7 +31,7 @@ func stringToNumber(str string) (*big.Rat, bool) { } func isNumber(code UNPARSEcode) bool { - return numberCompile.MatchString(code.code) + return numberCompile.MatchString(code.code) || binaryCompile.MatchString(code.code) || hexCompile.MatchString(code.code) || octalCompile.MatchString(code.code) } // converts a number type to a string @@ -80,10 +89,10 @@ var subscript = map[byte]string{ } // returns translateNumber, success, error -func parseNumber(code UNPARSEcode) (translateNumber, bool, string) { - output, _ := newNumber().SetString(code.code) +func parseNumber(code UNPARSEcode) (translateNumber, bool, ArErr, int) { + output, _ := newNumber().SetString(strings.TrimSpace(code.code)) return translateNumber{ number: output, line: code.line, - }, true, "" + }, true, ArErr{}, 1 } diff --git a/src/reduce.go b/src/reduce.go new file mode 100644 index 0000000..29d0366 --- /dev/null +++ b/src/reduce.go @@ -0,0 +1,9 @@ +package main + +func reduce[T any](reducer func(x T, y T) T, arr []T) T { + result := arr[0] + for i := 1; i < len(arr); i++ { + result = reducer(arr[i], result) + } + return result +} diff --git a/src/run.go b/src/run.go index c69bd42..86bf613 100644 --- a/src/run.go +++ b/src/run.go @@ -1,24 +1,27 @@ package main -import "fmt" - -func runLine(line any) (any, string) { - switch line.(type) { +// returns (number|string|nil), error +func runVal(line any, stack []map[string]variableValue) (any, ArErr) { + switch x := line.(type) { case translateNumber: - return (numberToString(line.(translateNumber).number, 0)), "" + return (x.number), ArErr{} case translateString: - return (line.(translateString).str), "" + return (x.str), ArErr{} + case call: + return runCall(x, stack) + case accessVariable: + return readVariable(x, stack) } - return nil, "Error: invalid code on line " + fmt.Sprint(line.(translateNumber).line) + ": " + line.(translateNumber).code + panic("unreachable") } // returns error -func run(translated []any) (any, string) { +func run(translated []any, stack []map[string]variableValue) (any, ArErr) { for _, val := range translated { - _, err := runLine(val) - if err != "" { + _, err := runVal(val, stack) + if err.EXISTS { return nil, err } } - return nil, "" + return nil, ArErr{} } diff --git a/src/string.go b/src/string.go index 87bd260..225ac02 100644 --- a/src/string.go +++ b/src/string.go @@ -1,11 +1,16 @@ package main import ( - "fmt" "strconv" "strings" ) +type translateString struct { + str string + code string + line int +} + var stringCompile = makeRegex("(( *)\"((\\\\([a-z\\\"'`]))|[^\\\"])*\"( *))|(( *)'((\\\\([a-z\\'\"`]))|[^\\'])*'( *))") func isString(code UNPARSEcode) bool { @@ -27,16 +32,16 @@ func unquoted( } // returns translateString, success, error -func parseString(code UNPARSEcode) (translateString, bool, string) { +func parseString(code UNPARSEcode) (translateString, bool, ArErr, int) { trim := strings.Trim(code.code, " ") unquoted, err := unquoted(trim) if err != nil { - return translateString{}, false, "Syntax Error: invalid string on line " + fmt.Sprint(code.line) + ": " + code.code + return translateString{}, false, ArErr{"Syntax Error", "invalid string", code.line, code.path, code.realcode, true}, 1 } return translateString{ str: unquoted, line: code.line, - }, true, "" + }, true, ArErr{}, 1 } diff --git a/src/to-argon.go b/src/to-argon.go new file mode 100644 index 0000000..b0b5946 --- /dev/null +++ b/src/to-argon.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "math" + "strconv" +) + +func anyToArgon(x any, quote bool) string { + switch x := x.(type) { + case string: + if !quote { + return x + } + return strconv.Quote(x) + case number: + num, _ := x.Float64() + if math.IsNaN(num) { + return "NaN" + } else if math.IsInf(num, 1) { + return "infinity" + } else if math.IsInf(num, -1) { + return "-infinity" + } else { + return strconv.FormatFloat(num, 'f', -1, 64) + } + default: + return fmt.Sprint(x) + } +} diff --git a/src/translate.go b/src/translate.go index 88a356d..ecda5d4 100644 --- a/src/translate.go +++ b/src/translate.go @@ -1,37 +1,43 @@ package main -import ( - "fmt" - "log" -) +type UNPARSEcode struct { + code string + realcode string + line int + path string +} -// returns (translateNumber | translateString), success, error -func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine bool) (any, bool, string) { +// returns (translateNumber | translateString| nil), success, error +func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine bool) (any, bool, ArErr, int) { if isLine { - if isComment(code) { - return nil, true, "" + if isBlank(code) { + return nil, true, ArErr{}, 1 + } else if isComment(code) { + resp, worked, err := parseComment(code, index, codelines) + if worked { + return resp, worked, err, 1 + } } } - - if isNumber(code) { + if isCall(code) { + return parseCall(code, index, codelines) + } else if isVariable(code) { + return parseVariable(code) + } else if isNumber(code) { return parseNumber(code) } else if isString(code) { return parseString(code) } - if isLine { - return nil, false, "Syntax Error: invalid code on line " + fmt.Sprint(code.line) + ": " + code.code - } - return nil, false, "" + return nil, false, ArErr{"Syntax Error", "invalid syntax", code.line, code.path, code.realcode, true}, 1 } // returns [](translateNumber | translateString), error -func translate(codelines []UNPARSEcode) ([]any, string) { +func translate(codelines []UNPARSEcode) ([]any, ArErr) { translated := []any{} for i, code := range codelines { - val, _, err := translateVal(code, i, codelines, true) + val, _, err, _ := translateVal(code, i, codelines, true) - if err != "" { - log.Fatal(err) + if err.EXISTS { return nil, err } if val == nil { @@ -39,5 +45,5 @@ func translate(codelines []UNPARSEcode) ([]any, string) { } translated = append(translated, val) } - return translated, "" + return translated, ArErr{} } diff --git a/src/typeof.go b/src/typeof.go new file mode 100644 index 0000000..2a56992 --- /dev/null +++ b/src/typeof.go @@ -0,0 +1,19 @@ +package main + +func typeof(val any) string { + switch val.(type) { + case number: + return "number" + case string: + return "string" + case nil: + return "null" + case bool: + return "boolean" + case Callable: + return "function" + case builtinFunc: + return "function" + } + return "unknown" +} diff --git a/src/types.go b/src/types.go deleted file mode 100644 index 1ef3291..0000000 --- a/src/types.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -type UNPARSEcode struct { - code string - line int -} - -type translateNumber struct { - number - code string - line int -} - -type translateString struct { - str string - code string - line int -} diff --git a/src/variable.go b/src/variable.go new file mode 100644 index 0000000..37c6901 --- /dev/null +++ b/src/variable.go @@ -0,0 +1,56 @@ +package main + +import ( + "strings" +) + +var variableCompile = makeRegex(`([a-zA-Z_])([a-zA-Z0-9_])*`) + +var blockedVariableNames = map[string]bool{ + "if": true, + "else": true, + "elif": true, + "while": true, + "for": true, + "break": true, + "continue": true, + "return": true, + "let": true, + "import": true, + "from": true, + "do": true, +} + +type variableValue struct { + VAL any + EXISTS any + origin string +} + +type accessVariable struct { + name string + line int + code string + path string +} + +func isVariable(code UNPARSEcode) bool { + return variableCompile.MatchString(code.code) +} + +func parseVariable(code UNPARSEcode) (accessVariable, bool, ArErr, int) { + name := strings.TrimSpace(code.code) + if blockedVariableNames[name] { + return accessVariable{}, false, ArErr{"Naming Error", "Naming Error: \"" + name + "\" is a reserved keyword", code.line, code.path, code.realcode, true}, 1 + } + return accessVariable{name: name, code: code.code, line: code.line}, true, ArErr{}, 1 +} + +func readVariable(v accessVariable, stack []map[string]variableValue) (any, ArErr) { + for i := len(stack) - 1; i >= 0; i-- { + if val, ok := stack[i][v.name]; ok { + return val.VAL, ArErr{} + } + } + return nil, ArErr{"Runtime Error", "variable \"" + v.name + "\" does not exist", v.line, v.path, v.code, true} +} diff --git a/test.ar b/test.ar index 119aa6a..d9314ed 100644 --- a/test.ar +++ b/test.ar @@ -1,26 +1 @@ -0x1 -0x2 -0x3 -0x4 -0x5 -0x6 -0x7 -0x8 -0x9 -0xA -0xB -0xC -0xD -0xE -0xF -0x10 -0x11 -0x12 -0x13 -0x14 -0x15 -0x16 -0x17 -0x18 -0x19 -0x1A \ No newline at end of file +log(mult(PI,PI, 2,4,34,5465)) \ No newline at end of file