Skip to content

Commit 96d076a

Browse files
committed
Use Goal's help, allow defining new help
1 parent 27db8f3 commit 96d076a

File tree

4 files changed

+44
-209
lines changed

4 files changed

+44
-209
lines changed

README.md

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Then run `ari` for a REPL or `ari --help` to see CLI options.
1919
- [Goal] is the core language
2020
- Goal's `lib` files are loaded by default, with prefix matching their file names (see [vendor-goal](vendor-goal) folder in this repo)
2121
- Extensible CLI REPL with:
22-
- Auto-completion for:
22+
- Auto-completion with documentation for:
2323
- Built-in keywords
2424
- Built-in syntax aliases (e.g., typing "first" and TAB will show `*` and `¿` in auto-complete results)
2525
- User-defined globals
@@ -29,21 +29,13 @@ Then run `ari` for a REPL or `ari --help` to see CLI options.
2929
- Configure the output format with `--output-format` or using one of the `)output.` system commands at the REPL. Formats include CSV/TSV, JSON, Markdown, and LaTeX.
3030
- `ari.p` is bound to the previous result (value from last evaluation at the REPL)
3131
- Alternatively run `ari` with `--raw` for a simpler, raw REPL that lacks line editing, history, and auto-complete, but is better suited for interaction via an editor like (Neo)Vim, or if you prefer rlwrap or another line editor to the one that ships with ari.
32-
- Extensible help system
33-
- `help"help"` for an overview
34-
- `help"TOPIC"` similar to Goal's CLI help
35-
- `help rx/search/` to use regular expression matching to find help entries that match
36-
- `"myvar" help "My Var Help"` to extend the help system with new documentation
37-
- `help""` returns entire help dictionary (including non-Goal help for the SQL mode; use `(help"")"goal"` for just the Goal help)
38-
- Auto-complete shows abbreviated help for each result.
3932
- New Goal functions:
4033
- `http.` functions for HTTP requests using [Resty]
4134
- `sql.` functions for SQL queries and commands
4235
- `tt.` test framework
4336
- `csv.tbl` and `json.tbl` to make Goal tables from the output of `csv` and `json` respectively
4437
- _(WIP)_ `time.` functions for more extensive date/time handling
4538
- `tui.` functions for basic terminal UI styling (colors, padding/margin, borders)
46-
- `glob` and `abspath` wrapping Go's `path/filepath.Glob` and `path/filepath.Abs` functions respectively
4739
- Dedicated SQL mode
4840
- The ari CLI uses DuckDB, but the `github.com/semperos/ari` Go package doesn't directly depend on a specific SQL database driver, so you can BYODB.
4941
- Activate with `)sql` for read-only, `)sql!` for read/write modes. Execute `)goal` to return to the default Goal mode.
@@ -58,7 +50,8 @@ See [CHANGES.md](CHANGES.md) for recent changes.
5850
Non-exhaustive list:
5951

6052
- TODO: Test coverage (Go reports 14% coverage, currently minimal Goal-level testing as well)
61-
- TODO: Calculate help just in time; leverage Goal's new `HelpFunc`
53+
- TODO: System functions to switch between rich and raw REPL `)repl.rich` and `)repl.raw`
54+
- TODO: System function for extensible `)help`
6255
- TODO: Consider which of ari's functions should be pervasive; review Goal's approach and implement
6356
- TODO: BUG When using `-l` flag, loaded files aren't completely evaluated. Evaluation stops partway through and it's not immediately clear why.
6457
- TODO: BUG `json.tbl` produces improper zero values when keys are missing. Where possible, column lists should be of uniform type and not generic if the data allows for it.

cmd/ari/input.go

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -317,19 +317,15 @@ func (autoCompleter *AutoCompleter) goalAutoCompleteFn() func(v [][]rune, line,
317317
}
318318

319319
func autoCompleteGoalSyntax(autoCompleter *AutoCompleter, lword string, perCategory map[string][]acEntry) {
320+
helpFunc := autoCompleter.ariContext.Help.Func
320321
category := "Syntax"
321322
syntaxSet := make(map[string]bool, 0)
322323
for _, name := range autoCompleter.goalSyntaxKeys {
323324
chstr := autoCompleter.goalSyntaxAliases[name]
324325
if strings.HasPrefix(strings.ToLower(name), lword) {
325326
if _, ok := syntaxSet[chstr]; !ok {
326327
syntaxSet[chstr] = true
327-
var help string
328-
if val, ok := autoCompleter.goalSyntaxHelp[chstr]; ok {
329-
help = val
330-
} else {
331-
help = "Goal syntax"
332-
}
328+
help := helpFunc(chstr)
333329
perCategory[category] = append(perCategory[category], acEntry{chstr, help})
334330
}
335331
}
@@ -341,35 +337,26 @@ func autoCompleteGoalKeywords(autoCompleter *AutoCompleter, lword string, perCat
341337
if autoCompleter.goalKeywordsKeys == nil {
342338
autoCompleter.cacheGoalKeywords(autoCompleter.ariContext.GoalContext)
343339
}
340+
helpFunc := autoCompleter.ariContext.Help.Func
344341
category := "Keyword"
345342
for _, goalKeyword := range autoCompleter.goalKeywordsKeys {
346343
if strings.HasPrefix(strings.ToLower(goalKeyword), lword) {
347-
var help string
348-
if val, ok := autoCompleter.goalKeywordsHelp[goalKeyword]; ok {
349-
help = val
350-
} else {
351-
help = "A Goal keyword"
352-
}
344+
help := helpFunc(goalKeyword)
353345
perCategory[category] = append(perCategory[category], acEntry{goalKeyword, help})
354346
}
355347
}
356348
}
357349

358350
func autoCompleteGoalGlobals(autoCompleter *AutoCompleter, lword string, perCategory map[string][]acEntry) {
359351
goalContext := autoCompleter.ariContext.GoalContext
352+
helpFunc := autoCompleter.ariContext.Help.Func
360353
// Globals cannot be cached; this is what assignment in Goal creates.
361354
goalGlobals := goalContext.GlobalNames(nil)
362355
sort.Strings(goalGlobals)
363-
goalHelp := autoCompleter.ariContext.Help.Dictionary["goal"]
364356
category := "Global"
365357
for _, goalGlobal := range goalGlobals {
366358
if strings.HasPrefix(strings.ToLower(goalGlobal), lword) {
367-
var help string
368-
if val, ok := goalHelp[goalGlobal]; ok {
369-
help = val
370-
} else {
371-
help = "A Goal global binding"
372-
}
359+
help := helpFunc(goalGlobal)
373360
perCategory[category] = append(perCategory[category], acEntry{goalGlobal, help})
374361
}
375362
}

export_test.go

Lines changed: 0 additions & 68 deletions
This file was deleted.

goal.go

Lines changed: 35 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
_ "embed"
66
"fmt"
77
"os"
8-
"regexp"
98
"strings"
109

1110
"codeberg.org/anaseto/goal"
@@ -99,101 +98,28 @@ func printV(ctx *goal.Context, x goal.V) error {
9998
}
10099
}
101100

102-
// Implements Goal's help monad.
103-
func VFHelpMonad(help Help) func(_ *goal.Context, args []goal.V) goal.V {
101+
// Implements Goal's help monad + Ari's help dyad.
102+
func VFGoalHelp(help Help) func(_ *goal.Context, args []goal.V) goal.V {
104103
return func(_ *goal.Context, args []goal.V) goal.V {
105-
if len(args) >= 1 {
106-
arg, ok := args[0].BV().(goal.S)
107-
if !ok {
108-
return goal.Panicf("help x : x not a string (%s)", args[0].Type())
109-
}
110-
fmt.Fprintln(os.Stdout, strings.TrimSpace(help.Func(string(arg))))
111-
}
112-
return goal.NewI(1)
113-
}
114-
}
115-
116-
// Implements help dyad.
117-
func VFHelpFn(help Help) func(goalContext *goal.Context, args []goal.V) goal.V {
118-
return func(goalContext *goal.Context, args []goal.V) goal.V {
119-
x := args[len(args)-1]
104+
x := args[0]
120105
switch len(args) {
121106
case monadic:
122-
return helpMonadic(goalContext, help, x)
107+
return helpMonadic(help, x)
123108
case dyadic:
124109
return helpDyadic(help, x, args)
125-
case triadic:
126-
return helpTriadic(help, x, args)
127110
default:
128-
return goal.Panicf("sql.q : too many arguments (%d), expects 1 or 2 arguments", len(args))
129-
}
130-
}
131-
}
132-
133-
const noHelpString = "(no help)"
134-
135-
func helpMonadic(goalContext *goal.Context, help Help, x goal.V) goal.V {
136-
switch helpKeyword := x.BV().(type) {
137-
case goal.S:
138-
helpKeywordString := string(helpKeyword)
139-
if helpKeywordString == "" {
140-
return helpReturnAsDict(help)
141-
}
142-
return helpPrintExactMatch(help, helpKeyword, goalContext)
143-
case *goal.R:
144-
anyMatches := helpPrintRegexMatches(help, helpKeyword.Regexp())
145-
if anyMatches {
146-
return goal.NewI(1)
147-
}
148-
fmt.Fprintln(os.Stdout, "(no help matches)")
149-
return goal.NewI(0)
150-
default:
151-
return panicType("help s-or-rx", "s-or-rx", x)
152-
}
153-
}
154-
155-
func helpPrintExactMatch(help Help, helpKeyword goal.S, goalContext *goal.Context) goal.V {
156-
goalHelp := help.Dictionary["goal"]
157-
if helpString, ok := goalHelp[string(helpKeyword)]; ok {
158-
err := printV(goalContext, goal.NewS(helpString))
159-
if err != nil {
160-
return goal.NewPanicError(err)
111+
return goal.Panicf("help : too many arguments (%d), expects 1 or 2 arguments", len(args))
161112
}
162-
fmt.Fprintln(os.Stdout)
163-
return goal.NewI(1)
164113
}
165-
err := printV(goalContext, goal.NewS(noHelpString))
166-
if err != nil {
167-
return goal.NewPanicError(err)
168-
}
169-
fmt.Fprintln(os.Stdout)
170-
return goal.NewI(0)
171-
}
172-
173-
func helpReturnAsDict(help Help) goal.V {
174-
categories := make([]string, 0, len(help.Dictionary))
175-
categoryDicts := make([]goal.V, 0)
176-
for category, v := range help.Dictionary {
177-
categories = append(categories, category)
178-
categoryDicts = append(categoryDicts, stringMapToGoalDict(v))
179-
}
180-
return goal.NewD(goal.NewAS(categories), goal.NewAV(categoryDicts))
181114
}
182115

183-
func helpPrintRegexMatches(help Help, r *regexp.Regexp) bool {
184-
goalHelp := help.Dictionary["goal"]
185-
anyMatches := false
186-
for k, v := range goalHelp {
187-
// Skip roll-up help topics that have a ":" in them,
188-
// since it produces too much (and duplicated) output.
189-
if !strings.Contains(k, ":") {
190-
if r.MatchString(k) || r.MatchString(v) {
191-
fmt.Fprintln(os.Stdout, v)
192-
anyMatches = true
193-
}
194-
}
116+
func helpMonadic(help Help, x goal.V) goal.V {
117+
arg, ok := x.BV().(goal.S)
118+
if !ok {
119+
return goal.Panicf("help x : x not a string (%s)", x.Type())
195120
}
196-
return anyMatches
121+
fmt.Fprintln(os.Stdout, strings.TrimSpace(help.Func(string(arg))))
122+
return goal.NewI(1)
197123
}
198124

199125
func helpDyadic(help Help, x goal.V, args []goal.V) goal.V {
@@ -211,28 +137,25 @@ func helpDyadic(help Help, x goal.V, args []goal.V) goal.V {
211137
return goal.NewI(1)
212138
}
213139

214-
func helpTriadic(help Help, x goal.V, args []goal.V) goal.V {
215-
helpCategory, ok := x.BV().(goal.S)
216-
if !ok {
217-
return panicType("help[category;keyword;helpstring]", "category", x)
218-
}
219-
y := args[1]
220-
helpKeyword, ok := y.BV().(goal.S)
221-
if !ok {
222-
return panicType("help[category;keyword;helpstring]", "keyword", y)
223-
}
224-
z := args[0]
225-
helpString, ok := z.BV().(goal.S)
226-
if !ok {
227-
return panicType("help[category;keyword;helpstring]", "helpstring", z)
140+
// Implements rtnames monad.
141+
func VFRTNames(goalContext *goal.Context, args []goal.V) goal.V {
142+
names := goalContext.GlobalNames(nil)
143+
goalContext.Keywords(names)
144+
prims := make([]string, 0)
145+
primsSeen := make(map[string]bool, 0)
146+
for _, prim := range GoalSyntax() {
147+
if _, ok := primsSeen[prim]; !ok {
148+
prims = append(prims, prim)
149+
}
150+
primsSeen[prim] = true
228151
}
229-
categoryHelp, ok := help.Dictionary["goal"]
230-
if !ok {
231-
help.Dictionary[string(helpCategory)] = map[string]string{string(helpKeyword): string(helpString)}
232-
} else {
233-
categoryHelp[string(helpKeyword)] = string(helpString)
152+
keys := make([]string, 0, len(GoalSyntax()))
153+
for k := range GoalSyntax() {
154+
keys = append(keys, k)
234155
}
235-
return goal.NewI(1)
156+
ks := goal.NewArray([]goal.V{goal.NewS("globals"), goal.NewS("keywords"), goal.NewS("verbs")})
157+
vs := goal.NewArray([]goal.V{goal.NewAS(goalContext.GlobalNames(nil)), goal.NewAS(goalContext.Keywords(nil)), goal.NewAS(prims)})
158+
return goal.NewDict(ks, vs)
236159
}
237160

238161
// Go <> Goal helpers
@@ -288,9 +211,10 @@ func goalRegisterVariadics(ariContext *Context, goalContext *goal.Context, help
288211
gos.Import(goalContext, "")
289212
// Goal's help, exposed via HelpFunc.
290213
// Vendored in this repository to support future use-case of using glob/regex matching of help content.
291-
goalContext.RegisterMonad("help", VFHelpMonad(help))
214+
goalContext.RegisterMonad("help", VFGoalHelp(help))
292215
// Ari
293216
// Monads
217+
goalContext.RegisterMonad("rtnames", VFRTNames)
294218
goalContext.RegisterMonad("sql.close", VFSqlClose)
295219
goalContext.RegisterMonad("sql.open", VFSqlOpen)
296220
goalContext.RegisterMonad("time.day", VFTimeDay)
@@ -314,7 +238,6 @@ func goalRegisterVariadics(ariContext *Context, goalContext *goal.Context, help
314238
goalContext.RegisterMonad("time.zoneoffset", VFTimeZoneOffset)
315239
goalContext.RegisterMonad("url.encode", VFUrlEncode)
316240
// Dyads
317-
// goalContext.RegisterDyad("help", VFHelpFn(help))
318241
goalContext.RegisterDyad("http.client", VFHTTPClientFn())
319242
goalContext.RegisterDyad("http.delete", VFHTTPMaker(ariContext, "DELETE"))
320243
goalContext.RegisterDyad("http.get", VFHTTPMaker(ariContext, "GET"))
@@ -479,11 +402,11 @@ func GoalSyntax() map[string]string {
479402
"deepat": ".",
480403
"atrowkey": ".",
481404
"setglobal": "::",
482-
"amend": "@[",
483-
"tryat": "@[",
484-
"deepamend": ".[",
485-
"try": ".[",
486-
"cond": "?[",
405+
"amend": "@",
406+
"tryat": "@",
407+
"deepamend": ".",
408+
"try": ".",
409+
"cond": "?",
487410
}
488411
}
489412

0 commit comments

Comments
 (0)