diff --git a/.gitignore b/.gitignore index b7dab5e..9e87c05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ node_modules -build \ No newline at end of file +bin \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..5d2dc11 --- /dev/null +++ b/build.bat @@ -0,0 +1,3 @@ +@echo off + +go build -o bin/Argon-v3.exe ./src \ No newline at end of file diff --git a/readme.md b/readme.md index 9506b05..32ac8e8 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,48 @@ -# Argon +
+

+ +

+

Argon 3

+
-The go to language for mathematicians! +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. -# Read Me +## 📚 Features -due to the project being in very early stages, the readme docs file is not yet being worked on. [read the specification to get an understanding of how the language will work.](spec.md) + - 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. + - 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. + - Lightweight: The Argon 3 interpreter is small and doesn't require a lot of system resources to run. -# Licence +## 💻 Installation +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-v3(.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: + +``` +argon example.ar +``` + +## 🔍 Specification + +For a detailed specification of the Argon 3 language, please refer to [spec.md](spec.md). + +## 🚀 Example Code + +Here's an example of how to define a function in Argon 3: + +```javascript +f(x) = x^2 + 2*x + 1 +log('f(10) =', f(10)) +``` + +This code defines a function f(x) that calculates x^2 + 2*x + 1. It then calls the function with an argument of 10 and logs the result to the console. + +Please note that this example is subject to change as the specification is in beta and may be updated frequently. + +## Licence MIT diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..bbe3f9c --- /dev/null +++ b/run.bat @@ -0,0 +1,5 @@ +@echo off + +:: run the go run command passing the path to the main.go file, with the working directory set to the bin folder. pass in the arguments + +go run ./src %* \ No newline at end of file diff --git a/spec.md b/spec.md index 53581b7..6c501d6 100644 --- a/spec.md +++ b/spec.md @@ -24,10 +24,10 @@ reused variables, and infomation for use in understanding the pseudo REGEX: ## set variable -`(let/const or nothing) {NAME} = {ANY}` +`(let or nothing) {NAME} = {ANY}` -let and const variables will set variables. if a const is already set in that stack, it will throw an error. -at the end of a opperation (e.g. if, while, or function) drop the variable stack. +let variables will set variables. at the end of a opperation (e.g. if, while, or function) drop the +variable stack. having no verb at the start suggests the program would like to edit a variables from a different stack. if there is no variable found in the other stacks, then it sets one in the current stack as a let. @@ -36,17 +36,16 @@ setting variables returns the value, which can be used. example: -``` -if (x = 10) > 5 [ +```javascript +if (x = 10) > 5 do log(x, 'is bigger than 5') -] ``` --- ## functions -`(let/const or nothing) {NAME}({PARAMS}) = {CODE}` +`(let or nothing) {NAME}({PARAMS}) = {CODE}` the starting verb follows the rules set by [set variable](#set-variable). @@ -54,41 +53,41 @@ function can be called by using its name followed by brackets with the params. example: -``` - -const f(x) = x^2 + 2*x + 1 +```javascript +f(x) = x^2 + 2*x + 1 log('f(10) =', f(10)) - ``` output: -``` - +```javascript f(10) = 121 - ``` -if the function does not return, then the value given is unknown/null +if the function does not return, then the value returned is `unknown` --- ## wrap -`[{CODE}]` +``` +do + {CODE} +``` -a wrap encloses code in square brackets. its used to create a new scope, so variables set from +a wrap encloses code indented in after the word `do` its used to create a new scope, so variables set from inside the wraps scope are deleted once the wrap is finished. example: -``` +```javascript let name = unknown -[ + +do name = input('name: ') let age = input('age: ') log('you are', age, 'years old!') -] + log('hello', name) log('we do not know your age anymore because it got deleted when the wrap finished.') ``` @@ -97,17 +96,19 @@ A wrap, unless specificifed otherwise, can have a return value. This value can b example: -``` -const password = [ +```javascript +let password = do let password = input("set password: ") - while len(password) < 8 [ + while len(password) < 8 do log("password must be longer then 8 characters!") password = input("set password: ") - ] return password -] log("your password is", password) ``` If the wrap does not take a return value, then the wrap passes the return value back to a parent wrap. + +## Comments +`//{COMMENT}` +Comments allow the programmer to write a message into their code, without the message being processed by the computer. \ No newline at end of file diff --git a/src/comment.go b/src/comment.go new file mode 100644 index 0000000..1e6fcd7 --- /dev/null +++ b/src/comment.go @@ -0,0 +1,7 @@ +package main + +var commentCompile = makeRegex("( *)//.*") + +func isComment(code UNPARSEcode) bool { + return commentCompile.MatchString(code.code) +} diff --git a/src/import.go b/src/import.go new file mode 100644 index 0000000..35dc7c5 --- /dev/null +++ b/src/import.go @@ -0,0 +1,102 @@ +package main + +import ( + "bufio" + "errors" + "log" + "os" + "path/filepath" +) + +func FileExists(filename string) bool { + if _, err := os.Stat(filename); err == nil { + return true + + } else if errors.Is(err, os.ErrNotExist) { + return false + } else { + return false + } +} + +func readFile(path string) []UNPARSEcode { + file, err := os.Open(path) + if err != nil { + log.Fatal(err) + return nil + } + defer file.Close() + + scanner := bufio.NewScanner(file) + // optionally, resize scanner's capacity for lines over 64K, see next example + output := []UNPARSEcode{} + line := 1 + for scanner.Scan() { + output = append(output, UNPARSEcode{scanner.Text(), line}) + line++ + } + + if err := scanner.Err(); err != nil { + log.Fatal(err) + return nil + } + return output +} + +func importMod(realpath string, origin string, main bool) string { + extention := filepath.Ext(realpath) + path := realpath + if extention == "" { + path += ".ar" + } + ex, err := os.Getwd() + if err != nil { + return err.Error() + } + executable, err := os.Executable() + if err != nil { + return err.Error() + } + executable = filepath.Dir(executable) + isABS := filepath.IsAbs(path) + var pathsToTest []string + if isABS { + pathsToTest = []string{ + filepath.Join(path), + filepath.Join(realpath, "init.ar"), + } + } else { + pathsToTest = []string{ + filepath.Join(origin, realpath, "init.ar"), + filepath.Join(origin, path), + filepath.Join(origin, "modules", path), + filepath.Join(origin, "modules", realpath, "init.ar"), + filepath.Join(ex, path), + filepath.Join(ex, "modules", realpath, "init.ar"), + filepath.Join(ex, "modules", path), + filepath.Join(executable, "modules", realpath, "init.ar"), + filepath.Join(executable, "modules", path), + } + } + + var p string + var found bool + for _, p = range pathsToTest { + if FileExists(p) { + found = true + break + } + } + + if !found { + return "File does not exist: " + realpath + } + codelines := readFile(p) + + translated, translationerr := translate(codelines) + if translationerr != "" { + return translationerr + } + run(translated) + return "" +} diff --git a/src/main.go b/src/main.go index 1b8c3d8..7ab9269 100644 --- a/src/main.go +++ b/src/main.go @@ -1,8 +1,23 @@ package main -import "fmt" +import ( + "os" +) + +// args without the program path +var Args = os.Args[1:] func main() { - translate("") - fmt.Println("hello world") + + ex, e := os.Getwd() + if e != nil { + panic(e) + } + if len(Args) == 0 { + panic("No file specified") + } + err := importMod(Args[0], ex, true) + if err != "" { + panic(err) + } } diff --git a/src/number.go b/src/number.go index a932068..044a163 100644 --- a/src/number.go +++ b/src/number.go @@ -6,6 +6,8 @@ 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]+)?)))?))( *)") + // a number type type number = *big.Rat @@ -19,6 +21,10 @@ func stringToNumber(str string) (*big.Rat, bool) { return newNumber().SetString(str) } +func isNumber(code UNPARSEcode) bool { + return numberCompile.MatchString(code.code) +} + // converts a number type to a string func numberToString(num number, fraction int) string { if fraction != 0 { @@ -72,3 +78,12 @@ var subscript = map[byte]string{ '8': "₈", '9': "₉", } + +// returns translateNumber, success, error +func parseNumber(code UNPARSEcode) (translateNumber, bool, string) { + output, _ := newNumber().SetString(code.code) + return translateNumber{ + number: output, + line: code.line, + }, true, "" +} diff --git a/src/regex.go b/src/regex.go new file mode 100644 index 0000000..1d47e72 --- /dev/null +++ b/src/regex.go @@ -0,0 +1,14 @@ +package main + +import ( + "log" + "regexp" +) + +func makeRegex(str string) *regexp.Regexp { + Compile, err := regexp.Compile("^(" + str + ")$") + if err != nil { + log.Fatal(err) + } + return Compile +} diff --git a/src/run.go b/src/run.go new file mode 100644 index 0000000..c69bd42 --- /dev/null +++ b/src/run.go @@ -0,0 +1,24 @@ +package main + +import "fmt" + +func runLine(line any) (any, string) { + switch line.(type) { + case translateNumber: + return (numberToString(line.(translateNumber).number, 0)), "" + case translateString: + return (line.(translateString).str), "" + } + return nil, "Error: invalid code on line " + fmt.Sprint(line.(translateNumber).line) + ": " + line.(translateNumber).code +} + +// returns error +func run(translated []any) (any, string) { + for _, val := range translated { + _, err := runLine(val) + if err != "" { + return nil, err + } + } + return nil, "" +} diff --git a/src/string.go b/src/string.go new file mode 100644 index 0000000..87bd260 --- /dev/null +++ b/src/string.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "strconv" + "strings" +) + +var stringCompile = makeRegex("(( *)\"((\\\\([a-z\\\"'`]))|[^\\\"])*\"( *))|(( *)'((\\\\([a-z\\'\"`]))|[^\\'])*'( *))") + +func isString(code UNPARSEcode) bool { + return stringCompile.MatchString(code.code) +} + +func unquoted( + str string, +) (string, error) { + str = strings.Trim(str, " ") + if str[0] == '\'' { + str = strings.Replace(str, "\\\"", "\"", -1) + str = strings.Replace(str, "\"", "\\\"", -1) + } + str = str[1 : len(str)-1] + str = strings.Replace(str, "\\'", "'", -1) + str = "\"" + str + "\"" + return strconv.Unquote(str) +} + +// returns translateString, success, error +func parseString(code UNPARSEcode) (translateString, bool, string) { + 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{ + str: unquoted, + line: code.line, + }, true, "" +} diff --git a/src/translate.go b/src/translate.go index 5c4166e..88a356d 100644 --- a/src/translate.go +++ b/src/translate.go @@ -2,9 +2,42 @@ package main import ( "fmt" + "log" ) -func translate(code string) { - output, _ := newNumber().SetString("3.1415") - fmt.Println(numberToString(output, 0)) +// returns (translateNumber | translateString), success, error +func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine bool) (any, bool, string) { + if isLine { + if isComment(code) { + return nil, true, "" + } + } + + 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, "" +} + +// returns [](translateNumber | translateString), error +func translate(codelines []UNPARSEcode) ([]any, string) { + translated := []any{} + for i, code := range codelines { + val, _, err := translateVal(code, i, codelines, true) + + if err != "" { + log.Fatal(err) + return nil, err + } + if val == nil { + continue + } + translated = append(translated, val) + } + return translated, "" } diff --git a/src/types.go b/src/types.go new file mode 100644 index 0000000..1ef3291 --- /dev/null +++ b/src/types.go @@ -0,0 +1,18 @@ +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/test.ar b/test.ar new file mode 100644 index 0000000..119aa6a --- /dev/null +++ b/test.ar @@ -0,0 +1,26 @@ +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