Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
440 changes: 77 additions & 363 deletions README.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/pmezard/go-difflib v1.0.0
github.com/r3labs/diff/v3 v3.0.2
github.com/ripta/hypercmd v0.6.0
github.com/ripta/reals v0.0.0-20251220032726-c99f163d5c5c
github.com/ripta/reals v0.0.0-20260614185130-b214700ec783
github.com/ripta/unihan v0.0.0-20250404091138-c307c698a880
github.com/rogpeppe/go-internal v1.15.0
github.com/spf13/cobra v1.10.2
Expand Down Expand Up @@ -58,12 +58,12 @@ require (
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/tools v0.45.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260608224507-4308a22a1bab // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260608224507-4308a22a1bab // indirect
golang.org/x/tools v0.46.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
)
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ github.com/r3labs/diff/v3 v3.0.2 h1:yVuxAY1V6MeM4+HNur92xkS39kB/N+cFi2hMkY06BbA=
github.com/r3labs/diff/v3 v3.0.2/go.mod h1:Cy542hv0BAEmhDYWtGxXRQ4kqRsVIcEjG9gChUlTmkw=
github.com/ripta/hypercmd v0.6.0 h1:lUxdqhP/cR/Spu8Yi0Ve60Tw8+LYPkBdkhaplDf7fbo=
github.com/ripta/hypercmd v0.6.0/go.mod h1:8QnmkN5AFLtPDl5LnGExQdSG/CMEhAk9GqBpYJWScrw=
github.com/ripta/reals v0.0.0-20251220032726-c99f163d5c5c h1:4bBR+jNoWIs1roinlXrVDUtmSvqjtNbrJ3cuQtFci5g=
github.com/ripta/reals v0.0.0-20251220032726-c99f163d5c5c/go.mod h1:WErCt40puDDQdpVq8Hg1DzjB0svufA8WboSYG4BI2+E=
github.com/ripta/reals v0.0.0-20260614185130-b214700ec783 h1:C9fMjkM7wA0VJFYwPf8BCmIxBnqLSqv7KNo88ugJYOo=
github.com/ripta/reals v0.0.0-20260614185130-b214700ec783/go.mod h1:HJD35VfuiXMIOHm9kFC3c637WX/sznn9gES6ajLTUKk=
github.com/ripta/unihan v0.0.0-20250404091138-c307c698a880 h1:ZzDUYlZP/LHJmkh+PtgRZHEKa+eNVefq6YR8BnUCQ2I=
github.com/ripta/unihan v0.0.0-20250404091138-c307c698a880/go.mod h1:ZLBfCas48lym/27GOsyFjRo7OGejoGHzOTdUdoRtDqU=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
Expand Down Expand Up @@ -111,12 +111,12 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q=
golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
Expand All @@ -131,12 +131,12 @@ golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
google.golang.org/genproto/googleapis/api v0.0.0-20260608224507-4308a22a1bab h1:Foefixyu0l973HSYkX8Etw/fPxAmKRhyMGwuqXFiVI0=
google.golang.org/genproto/googleapis/api v0.0.0-20260608224507-4308a22a1bab/go.mod h1:KdNqO+rCIWgFumrNBSEDlDNrkrQnpkax7Tv1WxNY8V4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260608224507-4308a22a1bab h1:cY0oV1VnAqvaim8VsR8ZyEKAudzbRJMRGwD3W/L7yOw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260608224507-4308a22a1bab/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad h1:3iLyITS/sySRwbUKoC7ogfj2Yr1Cjs0pfaRKj5U5HEw=
google.golang.org/genproto/googleapis/api v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:KdNqO+rCIWgFumrNBSEDlDNrkrQnpkax7Tv1WxNY8V4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
59 changes: 55 additions & 4 deletions pkg/calc/calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,7 @@ func init() {
return c.handleSet(args)
},
".show": func(c *Calculator, args []string) error {
c.handleShow()
return nil
return c.handleShow(args)
},
".toggle": func(c *Calculator, args []string) error {
return c.handleToggle(args)
Expand Down Expand Up @@ -424,8 +423,33 @@ func (c *Calculator) handleLoad(args []string) error {
return nil
}

// handleShow displays current settings
func (c *Calculator) handleShow() {
// showTopics maps each .show topic to its handler. Topics accept any
// unambiguous prefix, like every other meta-command.
var showTopics = map[string]func(*Calculator){
"settings": (*Calculator).showSettings,
"functions": (*Calculator).showFunctions,
}

// handleShow displays settings or the function library, selected by an optional
// topic. No topic shows settings; a topic selects its listing by unambiguous
// prefix.
func (c *Calculator) handleShow(args []string) error {
topic := "settings"
if len(args) > 0 {
topic = args[0]
}

show, err := findByPrefix(topic, showTopics)
if err != nil {
return err
}

show(c)
return nil
}

// showSettings prints each setting's current value.
func (c *Calculator) showSettings() {
fmt.Println("settings:")
for name, setting := range settingsRegistry {
switch setting.Type {
Expand All @@ -437,11 +461,38 @@ func (c *Calculator) handleShow() {
}
}

// showFunctions lists the registered functions grouped by category. Trig
// functions take and return radians; convert degrees explicitly, e.g.
// sin(45 * PI / 180).
func (c *Calculator) showFunctions() {
fns := parser.Functions()

width := 0
for _, f := range fns {
if len(f.Signature) > width {
width = len(f.Signature)
}
}

group := ""
for _, f := range fns {
if f.Group != group {
if group != "" {
fmt.Println()
}
group = f.Group
fmt.Printf("%s:\n", group)
}
fmt.Printf(" %-*s %s\n", width, f.Signature, f.Summary)
}
}

// handleHelp displays available meta-commands
func (c *Calculator) handleHelp() {
fmt.Println("Available commands:")
fmt.Println(" .set <setting> <value> - Change a setting")
fmt.Println(" .show - Show current settings")
fmt.Println(" .show functions - List available functions")
fmt.Println(" .toggle <setting> - Toggle a boolean setting")
fmt.Println(" .save [path] - Save session (default: ~/.local/state/rt/calc/session.txt)")
fmt.Println(" .load [path] - Load session (default: ~/.local/state/rt/calc/session.txt)")
Expand Down
39 changes: 39 additions & 0 deletions pkg/calc/calculator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ var handleMetaCommandTests = []handleMetaCommandTest{
cmd: ".sho",
wantErr: false,
},
{
name: ".show functions",
cmd: ".show functions",
wantErr: false,
},
{
name: ".sh functions prefix",
cmd: ".sh functions",
wantErr: false,
},
{
name: ".sh f resolves functions topic by prefix",
cmd: ".sh f",
wantErr: false,
},
{
name: ".show unknown topic",
cmd: ".show bogus",
wantErr: true,
},
}

func TestHandleMetaCommand(t *testing.T) {
Expand All @@ -86,6 +106,25 @@ func TestHandleMetaCommand(t *testing.T) {
}
}

func TestHandleShowTopics(t *testing.T) {
t.Parallel()

c := &Calculator{DecimalPlaces: 30}

if err := c.handleShow(nil); err != nil {
t.Errorf("handleShow(nil) = %v, want nil", err)
}
if err := c.handleShow([]string{"settings"}); err != nil {
t.Errorf("handleShow(settings) = %v, want nil", err)
}
if err := c.handleShow([]string{"functions"}); err != nil {
t.Errorf("handleShow(functions) = %v, want nil", err)
}
if err := c.handleShow([]string{"bogus"}); err == nil {
t.Error("handleShow(bogus) = nil, want error")
}
}

type parseBoolTest struct {
name string
input string
Expand Down
4 changes: 4 additions & 0 deletions pkg/calc/lexer/lex_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ func lexExpression(l *L) lexingState {
l.Emit(tokens.RPAREN)
return lexExpression

case r == ',':
l.Emit(tokens.COMMA)
return lexExpression

case r == '$':
return lexLineIdent

Expand Down
13 changes: 13 additions & 0 deletions pkg/calc/lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ var tokenTests = []tokenTest{
},
wantErr: "too many decimal points",
},
{
name: "function call with comma-separated arguments",
input: "max(1, 2)",
want: []tokenExpectation{
{Type: tokens.IDENT, Value: "max", Col: 1},
{Type: tokens.LPAREN, Value: "(", Col: 4},
{Type: tokens.LIT_INT, Value: "1", Col: 5},
{Type: tokens.COMMA, Value: ",", Col: 6},
{Type: tokens.WHITESPACE, Value: " ", Col: 7},
{Type: tokens.LIT_INT, Value: "2", Col: 8},
{Type: tokens.RPAREN, Value: ")", Col: 9},
},
},
{
name: "minus operator and identifier",
input: "-foo",
Expand Down
Loading