-
Notifications
You must be signed in to change notification settings - Fork 406
feat(example): calculator realm #4084
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
9b9492a
354b409
69c4861
e330d9c
345cc21
8cd3d2a
44cb487
f7ebebc
c7b7ad9
7787495
9aaa711
ffeae14
def6b5c
09209d1
c566b19
e10b516
7df0d90
d529ff5
8a1f4e7
c34cbf4
99fd13f
2749424
d216360
ffeda4b
e2ddb3c
cda0688
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
package calculator | ||
|
||
import ( | ||
"strconv" | ||
"strings" | ||
|
||
"gno.land/p/demo/ufmt" | ||
"gno.land/p/moul/realmpath" | ||
"gno.land/r/leon/hof" | ||
) | ||
|
||
type Node struct { | ||
value string // Value of the current node | ||
left *Node | ||
right *Node | ||
} | ||
|
||
const ( | ||
specialCharacters = "p-*/." | ||
topPriority = "*/" | ||
lowPriority = "p-" | ||
|
||
realmPath = "/r/miko/calculator" | ||
) | ||
|
||
var ( | ||
val float64 | ||
displayVal string | ||
|
||
operationMap = map[string]func(left float64, right float64) float64{ | ||
"p": func(left float64, right float64) float64 { return left + right }, | ||
"-": func(left float64, right float64) float64 { return left - right }, | ||
"*": func(left float64, right float64) float64 { return left * right }, | ||
"/": func(left float64, right float64) float64 { | ||
if right == 0 { | ||
panic("Division by 0 is forbidden") | ||
} | ||
return left / right | ||
}, | ||
} | ||
) | ||
|
||
func init() { | ||
hof.Register("Miko's calculator", "Let's do maths") | ||
} | ||
|
||
func evaluateValidity(line string) (bool, string) { | ||
if len(line) == 0 { | ||
return false, "Invalid empty input" | ||
} // edge case empty line | ||
if strings.Index(specialCharacters, string(line[0])) != -1 || | ||
strings.Index(specialCharacters, string(line[len(line)-1])) != -1 { | ||
return false, "Invalid equation" | ||
} // edge case special character at begining or end | ||
|
||
isPriorSpecial := false | ||
countParenthesis := 0 | ||
|
||
for i := 0; i < len(line); i++ { | ||
if line[i] == '<' { | ||
countParenthesis += 1 | ||
continue | ||
} | ||
if line[i] == '>' { | ||
if isPriorSpecial == true { | ||
return false, "Invalid equation" | ||
} | ||
countParenthesis -= 1 | ||
isPriorSpecial = false | ||
continue | ||
} | ||
if strings.Index(specialCharacters, string(line[i])) != -1 { | ||
if isPriorSpecial { | ||
return false, "Invalid equation" | ||
} | ||
isPriorSpecial = true | ||
continue | ||
} | ||
if line[i] != 'p' && (line[i] < '0' || line[i] > '9') { | ||
return false, "Invalid character encountered " | ||
} | ||
isPriorSpecial = false | ||
} | ||
|
||
if countParenthesis != 0 { | ||
return false, "Invalid equation" | ||
} | ||
println(countParenthesis) | ||
return true, "" | ||
} | ||
|
||
func searchForPriority(priorityList string, line string) *Node { | ||
// for i := 0; i < len(priorityList); i++ { | ||
// idx := strings.Index(line, string(priorityList[i])) | ||
// if idx != -1 { | ||
// return &Node{string(line[idx]), createTree(line[:idx]), createTree(line[idx+1:])} | ||
// } | ||
// } | ||
|
||
countParenthesis := 0 | ||
for iPrio := 0; iPrio < len(priorityList); iPrio++ { | ||
for idx := 0; idx < len(line); idx++ { | ||
if line[idx] == '<' { | ||
countParenthesis += 1 | ||
} | ||
if line[idx] == '>' { | ||
countParenthesis -= 1 | ||
} | ||
if countParenthesis == 0 && line[idx] == priorityList[iPrio] { | ||
println("seen operator") | ||
return &Node{string(line[idx]), createTree(line[:idx]), createTree(line[idx+1:])} | ||
} | ||
|
||
} | ||
} | ||
return nil | ||
} | ||
|
||
func createTree(line string) *Node { | ||
println(line) | ||
if line[0] == '<' && line[len(line)-1] == '>' && strings.Index(line[1:], "<") == -1 { | ||
println("no parenthesis anymore") | ||
return createTree(line[1 : len(line)-1]) | ||
} | ||
node := searchForPriority(lowPriority, line) // we put the lowest priority at the top of the tree, these operations will be executed last | ||
if node != nil { | ||
return node | ||
} | ||
node = searchForPriority(topPriority, line) | ||
if node != nil { | ||
return node | ||
} | ||
|
||
// if this code is reached, the only value possible in line is a number | ||
return &Node{line, nil, nil} | ||
} | ||
|
||
func readTree(tree *Node) float64 { | ||
operation, exists := operationMap[tree.value] | ||
|
||
if exists { // check if the current node is an operator | ||
return operation(readTree(tree.left), readTree(tree.right)) | ||
} | ||
|
||
parsedValue, _ := strconv.ParseFloat(tree.value, 64) | ||
|
||
return parsedValue | ||
} | ||
|
||
// expression is the equation you want to solve (p replaces the + symbol) | ||
// exemple: 2p4/2 | ||
func ComputeResult(expression string) string { | ||
valid, errString := evaluateValidity(expression) | ||
|
||
if !valid { // If a basic error is encountered, return the expression without the = at the end and display the same expression | ||
println(errString) // display error for debug | ||
displayVal = strings.Replace(expression, "p", "+", -1) | ||
displayVal = strings.Replace(displayVal, "<", "(", -1) | ||
displayVal = strings.Replace(displayVal, ">", ")", -1) | ||
return expression | ||
} | ||
|
||
tree := createTree(expression) | ||
|
||
val = readTree(tree) | ||
displayVal = strconv.FormatFloat(val, 'g', 6, 64) | ||
return displayVal | ||
} | ||
|
||
func removeLast(path string) string { | ||
lenPath := len(path) | ||
if lenPath > 0 { | ||
path = path[:lenPath-1] | ||
} | ||
return path | ||
} | ||
|
||
func Render(path string) string { | ||
var sb strings.Builder | ||
leohhhn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
req := realmpath.Parse(path) | ||
query := req.Query | ||
expression := query.Get("expression") | ||
|
||
if expression == "" { | ||
displayVal = "0" | ||
} else { | ||
if expression[len(expression)-1] == '=' { | ||
expression = ComputeResult(expression[:len(expression)-1]) | ||
} else { | ||
displayVal = strings.Replace(expression, "p", "+", -1) | ||
displayVal = strings.Replace(displayVal, "<", "(", -1) | ||
displayVal = strings.Replace(displayVal, ">", ")", -1) | ||
} | ||
} | ||
|
||
sb.WriteString(`# Calculator page | ||
|
||
Have you ever wanted to do maths but never actually found a calculator ? | ||
Do I have the realm for you... | ||
leohhhn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Result: ` + displayVal + ` | ||
--------------- | ||
| ` + ufmt.Sprintf("[res](%s)", realmPath) + `| ` + ufmt.Sprintf("[(](%s)", realmPath+":?expression="+expression+"<") + `| ` + ufmt.Sprintf("[)](%s)", realmPath+":?expression="+expression+">") + `| ` + ufmt.Sprintf("[del](%s)", realmPath+":?expression="+removeLast(expression)) + `| | ||
|---|---|---|---| | ||
| ` + ufmt.Sprintf("[7](%s)", realmPath+":?expression="+expression+"7") + `| ` + ufmt.Sprintf("[8](%s)", realmPath+":?expression="+expression+"8") + `| ` + ufmt.Sprintf("[9](%s)", realmPath+":?expression="+expression+"9") + `| ` + ufmt.Sprintf("[+](%s)", realmPath+":?expression="+expression+"p") /* here p replaces + because of how + works in bnormal paths*/ + `| | ||
| ` + ufmt.Sprintf("[4](%s)", realmPath+":?expression="+expression+"4") + `| ` + ufmt.Sprintf("[5](%s)", realmPath+":?expression="+expression+"5") + `| ` + ufmt.Sprintf("[6](%s)", realmPath+":?expression="+expression+"6") + `| ` + ufmt.Sprintf("[-](%s)", realmPath+":?expression="+expression+"-") + `| | ||
| ` + ufmt.Sprintf("[1](%s)", realmPath+":?expression="+expression+"1") + `| ` + ufmt.Sprintf("[2](%s)", realmPath+":?expression="+expression+"2") + `| ` + ufmt.Sprintf("[3](%s)", realmPath+":?expression="+expression+"3") + `| ` + ufmt.Sprintf("[*](%s)", realmPath+":?expression="+expression+"*") + `| | ||
| ` + ufmt.Sprintf("[0](%s)", realmPath+":?expression="+expression+"0") + `| ` + ufmt.Sprintf("[.](%s)", realmPath+":?expression="+expression+".") + `| ` + ufmt.Sprintf("[=](%s)", realmPath+":?expression="+expression+"=") + `| ` + ufmt.Sprintf("[/](%s)", realmPath+":?expression="+expression+"/") + `| | ||
`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try out a library for this, such as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have used this library for the mardown, but for the columns it isn't exactly what I need as the Columns() function always put "|||' between each column, which gives another display than simply "|" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry, i meant There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. e10b516 |
||
return sb.String() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package calculator | ||
|
||
import "testing" | ||
|
||
func TestCounter_Addition(t *testing.T) { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove these newlines :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 9aaa711 |
||
// Increment the value | ||
value := ComputeResult("1p1") | ||
|
||
// Verify the value is equal to 2 | ||
if value != "2" { | ||
t.Fatalf("1 + 1 is not equal to 2") | ||
} | ||
} | ||
|
||
func TestCounter_Subtraction(t *testing.T) { | ||
|
||
// Increment the value | ||
value := ComputeResult("1-1") | ||
|
||
// Verify the value is equal to 0 | ||
if value != "0" { | ||
t.Fatalf("1 - 1 is not equal to 0") | ||
} | ||
} | ||
|
||
func TestCounter_Multiplication(t *testing.T) { | ||
|
||
// Increment the value | ||
value := ComputeResult("1*4") | ||
|
||
// Verify the value is equal to 4 | ||
if value != "4" { | ||
t.Fatalf("1 * 4 is not equal to 4") | ||
} | ||
} | ||
|
||
func TestCounter_Division(t *testing.T) { | ||
|
||
// Increment the value | ||
value := ComputeResult("4/2") | ||
|
||
// Verify the value is equal to 2 | ||
if value != "2" { | ||
t.Fatalf("4 / 2 is not equal to 2") | ||
} | ||
} | ||
|
||
func TestCounter_AdditionDecimal(t *testing.T) { | ||
|
||
// Increment the value | ||
value := ComputeResult("1.2p1.3") | ||
|
||
// Verify the value is equal to 2.5 | ||
if value != "2.5" { | ||
t.Fatalf("1.2 + 1.3 is not equal to 2.5") | ||
} | ||
} | ||
|
||
func TestCounter_SubtractionDecimal(t *testing.T) { | ||
|
||
// Increment the value | ||
value := ComputeResult("1.3-1.2") | ||
|
||
// Verify the value is equal to 0.1 | ||
if value != "0.1" { | ||
t.Fatalf("1.3 - 1.2 is not equal to 0.1") | ||
} | ||
} | ||
|
||
func TestCounter_MultiplicationDecimal(t *testing.T) { | ||
|
||
// Increment the value | ||
value := ComputeResult("3*1.5") | ||
|
||
// Verify the value is equal to 4.5 | ||
if value != "4.5" { | ||
t.Fatalf("3 * 1.5 is not equal to 4.5") | ||
} | ||
} | ||
|
||
func TestCounter_DivisionDecimal(t *testing.T) { | ||
|
||
// Increment the value | ||
value := ComputeResult("2/0.5") | ||
|
||
// Verify the value is equal to 4 | ||
if value != "4" { | ||
t.Fatalf("2 / 0.5 is not equal to 4") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/r/miko/calculator |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/r/miko/home |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package home | ||
|
||
import ( | ||
"gno.land/p/demo/ufmt" | ||
) | ||
|
||
var count int | ||
|
||
// this here serves to increment my counter | ||
func Increment() { | ||
count++ | ||
} | ||
|
||
func Decrement() { | ||
count-- | ||
} | ||
|
||
func init() { | ||
count = 0 // arbitrary value | ||
} | ||
|
||
func Render(_ string) string { | ||
out := "# Hey !\n" | ||
out += "My name is Nicolas, french student in computer science !\n\n" | ||
out += "Want to hear about me ?\n\n" | ||
out += RenderPassion() | ||
out += RenderCounter() | ||
return out | ||
} | ||
|
||
func RenderPassion() string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these can be unexported There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 7787495 |
||
out := "## Passions\n\n" | ||
out += "### I love:\n\n" | ||
out += "Crying about Hollow knight silksong never coming out\n\n" | ||
out += "" | ||
out += "\n\n" | ||
out += "Playing the award winning MMORPG Final Fantasy XIV online\n\n" | ||
out += "And when I'm not doing any of the above, I like coding in C/C++ ~( °v°)\n" | ||
return out | ||
} | ||
|
||
func RenderCounter() string { | ||
out := "## Secret counter\n\n" | ||
out += "No one really knows what this counter actually counts\n\n" | ||
out += "**" + ufmt.Sprintf("%d", count) + "**" | ||
out += "\n\n\n... but it really ain't that high\n\n" | ||
return out | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 0 division can only be detected when reading the tree, and I don't see a way of properly handling this case that doesn't involve a weird third parameter for all operation function, or a global variable