diff --git a/fermi b/fermi index bc9a06f..4d4c44f 100755 Binary files a/fermi and b/fermi differ diff --git a/main/error.go b/main/error.go new file mode 100644 index 0000000..31487d5 --- /dev/null +++ b/main/error.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" +) + +type FermiError struct { + Code string + Msg string +} + +func (e FermiError) Error() string { + return e.Msg +} + +func PrintError(e error) { + if fermi_error, ok := e.(FermiError); ok { + fmt.Printf("Error: %s: %s\n", fermi_error.Code, fermi_error.Msg) + fmt.Printf("Type \"help\" or \"h\" to see grammar and command flags\n") + } else { + fmt.Printf("Error: %s\n", e.Error()) + } +} diff --git a/main/errors.go b/main/errors.go deleted file mode 100644 index 834cc21..0000000 --- a/main/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -type MyError struct { - Code string - Msg string -} - -func (me MyError) Error() string { - return me.Msg -} diff --git a/main/fermi.go b/main/fermi.go index 6e67f3b..10acba7 100644 --- a/main/fermi.go +++ b/main/fermi.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "errors" "flag" "fmt" "git.nunosempere.com/NunoSempere/fermi/sample" @@ -84,7 +83,7 @@ const HELP_MSG = "1. Grammar:\n" + " + 1 10\n" + " * beta 1 10\n" + " 1 10 (multiplication taken as default operation)\n" + - " =: x\n" + + " =: x\n" + " .\n" + " 1 100\n" + " + x\n" + @@ -94,6 +93,9 @@ const HELP_MSG = "1. Grammar:\n" + " 1 10\n" + " + beta 1 100\n" + " )\n" + + " =. y\n" + + " mx x 1 y 2.33\n" + + " + mx x 30% y 70%\n" + " exit\n" + "\n" + "2. Command flags:\n" + @@ -103,7 +105,7 @@ const HELP_MSG = "1. Grammar:\n" + " Specifies a file with a model to run\n" + " -n int\n" + " Specifies the number of samples to draw when using samples (default 100000)\n" + - " -h Shows help message\n" + " -h Shows help message" const NORMAL90CONFIDENCE = 1.6448536269514727 const INIT_DIST Scalar = Scalar(1) @@ -126,16 +128,16 @@ func operateDistsAsSamples(dist1 Dist, dist2 Dist, op string) (Dist, error) { if ys[0] != 0 { zs[i] = xs[i] / ys[i] } else { - fmt.Println("Error: When dividing as samples, division by zero") - return nil, errors.New("Division by zero") + // fmt.Println("Error: When dividing as samples, division by zero") + return nil, FermiError{Code: "Division by zero", Msg: "When operating on samples, division by zero"} } case "+": zs[i] = xs[i] + ys[i] case "-": zs[i] = xs[i] - ys[i] default: - fmt.Println("Error: Operation not recognized") - return nil, errors.New("Operation not recognized") + // fmt.Println("Error: Operation not recognized") + return nil, FermiError{Code: "Unknown operation", Msg: "When operating on samples, operation not recognized"} } } @@ -210,14 +212,12 @@ func divideDists(old_dist Dist, new_dist Dist) (Dist, error) { switch n := new_dist.(type) { case Lognormal: if n.high == 0 || n.low == 0 { - fmt.Println("Error: Can't divide by 0.0") - return nil, errors.New("Error: division by zero") + return nil, FermiError{Code: "Division by zero", Msg: "When operating two lognormals, one of the parameters is zero, which would result in division by zero"} } return multiplyLogDists(o, Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}), nil case Scalar: if n == 0.0 { - fmt.Println("Error: Can't divide by 0.0") - return nil, errors.New("Error: division by zero scalar") + return nil, FermiError{Code: "Division by zero", Msg: "When operating a lognormal with a scalar, trying to divide but the scalar is zero"} } return multiplyLogDistAndScalar(o, Scalar(1.0/n)) } @@ -229,8 +229,7 @@ func divideDists(old_dist Dist, new_dist Dist) (Dist, error) { return multiplyLogDistAndScalar(Lognormal{low: 1.0 / n.high, high: 1.0 / n.low}, o) case Scalar: if n == 0.0 { - fmt.Println("Error: Can't divide by 0.0") - return nil, errors.New("Error: division by zero scalar") + return nil, FermiError{Code: "Division by zero", Msg: "When operating two scalars, trying to divide but the divisor is is zero"} } return Scalar(float64(o) / float64(n)), nil } @@ -251,7 +250,7 @@ func operateDists(old_dist Dist, new_dist Dist, op string) (Dist, error) { case "-": return operateDistsAsSamples(old_dist, new_dist, "-") default: - return nil, PrintAndReturnErr("Can't combine distributions in this way") + return nil, FermiError{Code: "Unknown operation", Msg: "When operating distributions, operation not recognized"} } } @@ -259,9 +258,17 @@ func operateDists(old_dist Dist, new_dist Dist, op string) (Dist, error) { func parseMixture(words []string, vars map[string]Dist) (Dist, error) { // mx, mix, var weight var weight var weight ... // Check syntax - if len(words)%2 != 0 { - return nil, PrintAndReturnErr("Not a mixture. \nMixture syntax: \nmx x 2.5 y 8 z 10\ni.e.: mx var weight var2 weight2 ... var_n weight_n") + switch { + case len(words) < 1: + return nil, FermiError{Code: "Not a mixture", Msg: "Input can't be a mixture, since it doesn't have enough words"} + case words[0] != "mx": + return nil, FermiError{Code: "Not a mixture", Msg: "Input can't be a mixture, since it is not preceded by the mx keyword"} + case len(words)%2 != 1: + return nil, FermiError{Code: "Not a mixture", Msg: "When parsing a mixture, input doesn't have equal number of variables and weights. \nMixture syntax: \nmx x 20% y 70% z 10%\ni.e.: mx var weight var2 weight2 ... var_n weight_n"} + case len(words) < 5: + return nil, FermiError{Code: "Not a mixture", Msg: "When parsing a mixture, not enough words. \nMixture syntax: \nmx x 20% y 70% z 10%\ni.e.: mx var weight var2 weight2 ... var_n weight_n"} } + words = words[1:] // crop "mx" at the beginning var fs []func(int, sample.State) float64 var weights []float64 @@ -270,14 +277,14 @@ func parseMixture(words []string, vars map[string]Dist) (Dist, error) { if i%2 == 0 { dist, exists := vars[word] if !exists { - return nil, PrintAndReturnErr("Expected mixture variable but didn't get a variable. \nMixture syntax: \nmx x 2.5 y 8 z 10\ni.e.: mx var weight var2 weight2 ... var_n weight_n") + return nil, FermiError{Code: "Not a mixture variable", Msg: "When parsing a mixture, expected mixture variable but didn't get a variable. \nMixture syntax: \nmx x 2.5 y 8 z 10\ni.e.: mx var weight var2 weight2 ... var_n weight_n"} } f := dist.Sampler fs = append(fs, f) } else { weight, err := ParseFloat(word) if err != nil { - return nil, PrintAndReturnErr("Expected mixture weight but didn't get a float. \nMixture syntax: \nmx x 2.5 y 8 z 10\ni.e.: mx var weight var2 weight2 ... var_n weight_n") + return nil, FermiError{Code: "Not a mixture weight", Msg: "When parsing a mixture, expected mixture weight but didn't get a variable. \nMixture syntax: \nmx x 2.5 y 8 z 10\ni.e.: mx var weight var2 weight2 ... var_n weight_n"} } weights = append(weights, weight) } @@ -291,26 +298,26 @@ func parseMixture(words []string, vars map[string]Dist) (Dist, error) { } /* Parser and repl */ -func parseWordsErr(err_msg string) (string, Dist, error) { - return "", nil, PrintAndReturnErr(err_msg) -} func parseWordsIntoOpAndDist(words []string, vars map[string]Dist) (string, Dist, error) { + parseWordsErr := func(msg string) (string, Dist, error) { + return "", nil, FermiError{Code: "Malformed input", Msg: "When parsing words into operator and distribution: " + msg} + } op := "" var dist Dist switch words[0] { - case "*", "/", "+", "-", "mx": + case "*", "/", "+", "-": op = words[0] words = words[1:] default: op = "*" } - switch len(words) { - case 0: + switch { + case len(words) == 0: return parseWordsErr("Operator must have operand; can't operate on nothing") - case 1: + case len(words) == 1: var_word, var_word_exists := vars[words[0]] single_float, err1 := ParseFloat(words[0]) // abstract this away to search for K/M/B/T/etc. switch { @@ -321,7 +328,7 @@ func parseWordsIntoOpAndDist(words []string, vars map[string]Dist) (string, Dist case err1 != nil && !var_word_exists: return parseWordsErr("Trying to operate on a scalar, but scalar is neither a float nor an assigned variable") } - case 2: + case len(words) == 2: new_low, err1 := ParseFloat(words[0]) new_high, err2 := ParseFloat(words[1]) switch { @@ -335,7 +342,7 @@ func parseWordsIntoOpAndDist(words []string, vars map[string]Dist) (string, Dist return parseWordsErr("Trying to parse two floats as a lognormal, but the first number is larger than the second number") } dist = Lognormal{low: new_low, high: new_high} - case 3: + case len(words) == 3: switch { case words[0] == "beta" || words[0] == "b": a, err1 := ParseFloat(words[1]) @@ -347,18 +354,12 @@ func parseWordsIntoOpAndDist(words []string, vars map[string]Dist) (string, Dist default: return parseWordsErr("Input not understood or not implemented yet") } - default: - switch op { - case "mx": - tmp, err := parseMixture(words, vars) - if err != nil { - return parseWordsErr("Error parsing a mixture: " + err.Error()) - } - dist = tmp - op = "*" - default: - return parseWordsErr("Input not understood or not implemented yet") + case len(words) >= 4: // four or more words + if words[0] == "mx" { + dist, err := parseMixture(words, vars) + return op, dist, err } + return parseWordsErr("Input not understood or not implemented yet") } return op, dist, nil } @@ -379,17 +380,11 @@ replForLoop: words := strings.Split(new_line_trimmed, " ") switch { - case strings.TrimSpace(new_line_trimmed) == "": /* Empty line case */ + + /* Empty line */ + case strings.TrimSpace(new_line_trimmed) == "": continue replForLoop - /* Parenthesis */ - case len(words) == 2 && (words[0] == "*" || words[0] == "+" || words[0] == "-" || words[0] == "/") && words[1] == "(": - new_stack := runRepl(Stack{old_dist: INIT_DIST, vars: stack.vars}, reader, echo_flag) - combined_dist, err := operateDists(stack.old_dist, new_stack.old_dist, words[0]) - if err == nil { - stack.old_dist = combined_dist - } - case len(words) == 1 && words[0] == ")": - return stack + /* Special operations */ case words[0] == "exit" || words[0] == "e": os.Exit(0) @@ -402,6 +397,7 @@ replForLoop: fmt.Println() case words[0] == "stats" || words[0] == "s": PrettyPrintStats(stack.old_dist) + /* Variable assignment */ case words[0] == "=:" && len(words) == 2: stack.vars[words[1]] = stack.old_dist @@ -411,16 +407,36 @@ replForLoop: fmt.Printf("%s ", words[1]) PrettyPrintDist(stack.old_dist) stack.old_dist = INIT_DIST + + /* Parenthesis */ + case len(words) == 2 && (words[0] == "*" || words[0] == "+" || words[0] == "-" || words[0] == "/") && words[1] == "(": + new_stack := runRepl(Stack{old_dist: INIT_DIST, vars: stack.vars}, reader, echo_flag) + combined_dist, err := operateDists(stack.old_dist, new_stack.old_dist, words[0]) + if err != nil { + PrintError(err) + } else { + stack.old_dist = combined_dist + } + case len(words) == 1 && words[0] == ")": + return stack + + /* Bread and butter distribution operations */ default: op, new_dist, err := parseWordsIntoOpAndDist(words, stack.vars) if err != nil { + PrintError(err) + PrettyPrintDist(stack.old_dist) continue replForLoop } combined_dist, err := operateDists(stack.old_dist, new_dist, op) - if err == nil { - stack.old_dist = combined_dist + if err != nil { + PrintError(err) + PrettyPrintDist(stack.old_dist) + continue replForLoop } + stack.old_dist = combined_dist } + PrettyPrintDist(stack.old_dist) } } diff --git a/makefile b/makefile index b138a13..0e4ab0e 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,5 @@ build: - go build main/fermi.go main/pretty.go + go build main/fermi.go main/pretty.go main/error.go run: go run fermi.go