diff --git a/AGENTS.md b/AGENTS.md index 29146b539f..18793c0aa5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -210,6 +210,10 @@ hash not found". After adding a ref: and `Darklang.SCM.Branch.mainBranchId` resolve; `SCM.Branch.mainBranchId` doesn't. Impl: `backend/src/LibParser/NameResolver.fs` and `packages/darklang/languageTools/nameResolver.dark`. +**No `PACKAGE.` source prefix.** `PACKAGE.` is internal runtime/debug notation, not a +Dark namespace. Write `Stdlib.List.map` or `Darklang.Stdlib.List.map`, never +`PACKAGE.Darklang.Stdlib.List.map`; the same rule applies to search queries. + **Enum construction across modules.** Name the DU type, not just the module: `ProgramTypes.Reference.PackageFn hash` works, `ProgramTypes.PackageFn hash` doesn't. In a match arm the bare case is fine, since the matched value's type resolves it. diff --git a/Run result.md b/Run result.md new file mode 100644 index 0000000000..a4f9ea4890 --- /dev/null +++ b/Run result.md @@ -0,0 +1,316 @@ +Run result + +All 18 trials passed (9 tasks × python + dark). Every dark trial succeeded — the cost isn't failure, it's time: dark took 5.0× longer than python on +identical tasks (1165s vs 233s total), ranging from 3.7× (unit-convert, url-shortener) to 8.9× (todo-list-cli). Turn counts tell the same story (python 4–8 + turns, dark 13–38). + +Where dark time goes (aggregated across all 9 tasks, ~1198s) + +┌───────────────────────┬───────┬───────┬────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Phase │ Share │ Calls │ What it is │ +├───────────────────────┼───────┼───────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ EXPLORE — docs/search │ 39% │ 47 │ docs for-ai, docs signatures , search — finding which stdlib fn exists & its signature │ +├───────────────────────┼───────┼───────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ EVAL/test │ 30% │ 75 │ eval / ./run — exercising functions, the only place type errors appear │ +├───────────────────────┼───────┼───────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ WRITE fn/type │ 16% │ 19 │ run-cli fn heredoc creations │ +├───────────────────────┼───────┼───────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ SCM / branch / commit │ 5% │ 10 │ │ +├───────────────────────┼───────┼───────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ Bash(other) │ 4% │ 23 │ cd, env, file shuffling │ +├───────────────────────┼───────┼───────┼────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ run-cli status │ 4% │ 2 │ one call cost 20–25s in two tasks │ +└───────────────────────┴───────┴───────┴────────────────────────────────────────────────────────────────────────────────────────────────┘ + +The two dominant sinks — discovery (39%) and eval (30%) — are two halves of the same problem: the language gives no feedback until runtime. The agent can't + find functions statically (so it reads whole module dumps), and can't type-check at fn-creation (so it must eval after every function). Together they're +~70% of every dark run. On the more complex tasks discovery alone dominates: github-stats 64%, todo-list-cli 55%, fizzbuzz 50%. + +Issues the agent actually hit + +Tooling/runtime errors caught in transcripts (3 across the run): +- IncorrectArgs / Unresolved references: Bool.negate — the agent reached for Stdlib.Bool.not/Bool.negate in pipes and lambdas; they crashed or didn't +resolve, forcing a rewrite to Stdlib.String.length x > 0L. (hangman, todo-list-cli) +- Parse error on tuple destructuring — let (a, b) = t in ... was rejected; no let-binding tuple destructure, must use match. (todo-list-cli) +- search "Bool" returned LSP/protocol noise — search surfaced Darklang.LanguageServerProtocol... modules instead of the stdlib Bool, so search actively +misled discovery. (hangman) + +Self-reported friction, ranked by how many of the 9 tasks flagged it: + +┌───────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Tasks │ Friction │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 5/9 │ --branch required on every call + cd to repo root before each — verbose, easy to forget, "No such file" if missed │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 4/9 │ Can't discover functions by intent/return-type → read whole 50+ fn module signatures │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 4/9 │ Type errors only surface at eval, never at fn-creation │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 4/9 │ String interpolation won't coerce Int64 ($"n={x}" needs Stdlib.Int64.toString) │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 4/9 │ No ;/do — every side effect needs its own let _ = │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 4/9 │ match arm naming (bare Ok vs qualified Stdlib.Result.Result.Ok) confusion │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 3/9 │ Result/Option unwrap ceremony; no unwrapOr/mapError/? │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 2/9 │ Opaque runtime errors — stack traces show Package Function f1595c95a3… hashes, not function names │ +├───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ 2/9 │ No infix float ops; 1/9 L suffix │ +└───────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + +Bottom line + +The agent isn't failing in Dark — it's paying a 5× discovery-and-verification tax. The single highest-leverage fixes, in order: (1) searchable stdlib by +intent/return-type (and stop search from returning LSP modules) — attacks the 39% discovery cost; (2) type-check at fn-creation instead of only at eval — +collapses the 30% eval-loop; (3) persist --branch/cwd context across calls — the most-flagged friction, costing turns to nearly every task. The ergonomic +gripes (no infix, let _ =, interpolation coercion) are real but cheap by comparison. + +Caveats: one trial per task, self-graded PASS, and the per-phase split attributes model-thinking time to the call it precedes, so shares are directional +(±a few points), not exact. The full friction report is regenerated at bench/report.md. + + + +No way to search by return type or by intent ("functions that parse strings into parts") to find String.splitFirst +avail docs in search so agent doesn't have to eval + + + + 1. Stdlib discovery dominates every Dark trial (30–50% of turns). Every trial opens the same way: cat ./dark, cat ./run, ./dark docs for-ai | head -300, then 3–10 ./dark search ... calls before any code is written. + - Fizzbuzz: 11 search/docs calls before first fn (search range, print, parseInt, println, mod, %, docs Stdlib.Cli, search Stdout, writeLine, docs Stdlib.Cli.Stdout, print --fn). + - Hangman: 12 search calls (fileWrite, fileRead, getEnvVar, random, printLine, contains, toLowercase, toList, isLetter, length, join, Json). + - Url-shortener: 13 search calls before first fn (hash, sha, base62, hexEncode, sha256, toBlob, slice, Dir.create, …). + + 2. Stdlib.println is undiscoverable via the obvious search. ./dark search "print" returns pretty-printer module hits first. Multiple trials (fizzbuzz, hangman, todo-list, password-gen) try print, println, Stdout, writeLine, docs signatures + Stdlib.Cli.Stdout before stumbling onto --fn filtering or guessing printLine directly. + + 3. % / mod is hidden behind module-name noise. ./dark search "mod" returns Module, Modal, Model, MCP modules before reaching the % maps to mod note. + + 4. Stdlib.Cli.File vs Stdlib.Cli.FileSystem duplication. Both exist with overlapping readFile/readText operations and different error types (Posix.Error vs FileSystem.FileError). todo-list-cli and url-shortener both burned searches reconciling them. + + 5. Real parser/resolver failures the agent had to recover from: + - Hangman: Bool.not produced an "Unresolved references" warning at fn-creation time — agent had to detect it from output and rewrite with the Stdlib. prefix. + - Github-stats: | Ok (Object fields) -> (nested enum constructor pattern in match) failed at the parser (line 31 col 14). Agent had to refactor into a separate helper function with a second match. + + 6. Float arithmetic has no infix operators. unit-convert: Stdlib.Float.add (Stdlib.Float.multiply c 1.8) 32.0 instead of c * 1.8 + 32.0. Also a silent-misdispatch trap: c * 1.8 parses but routes to Int64 ops. + + 7. Every task that takes string input pays the parse boilerplate tax. match Stdlib.Int64.parse s with | Ok n -> … | Error _ -> … appears in 6/9 dark trials. ParseError is opaque, so the Error branch can't differentiate "not a number" from + "overflow". + + 8. HTTP → JSON requires double Blob→String→parse unwrapping. github-stats: HttpClient.get returns body: Blob, Blob.toString returns Result, so each HTTP+JSON flow needs two nested match blocks. + + 9. String⇄Char round-trips are awkward. hangman: no String.toChar or String.fromList; agent had to go via toList + | c :: _ -> c pattern match. + + 10. ./dark eval double-prints when functions also printLine. hangman: agent had to choose between losing return-value visibility (Unit-returning fns) or duplicate output. + + Per-task numbers (this run) + + ┌─────────────────┬───────────────────┬───────────────────┬──────────┐ + │ task │ python │ dark │ dark/py │ + ├───────────────────┼───────────────────┼───────────────────┼──────────┤ + │ countdown-timer │ 5 turns 28s $0.13 │ 15 76s $0.28 │ 3×/3×/2× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ even-or-odd-cli │ 5 / 18s / $0.11 │ 12 / 59s / $0.27 │ 2×/3×/2× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ fizzbuzz │ 5 / 16s / $0.10 │ 24 / 86s / $0.38 │ 5×/5×/4× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ github-stats │ 4 / 25s / $0.12 │ 23 / 132s / $0.48 │ 6×/5×/4× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ hangman │ 10 / 55s / $0.22 │ 41 / 213s / $0.88 │ 4×/4×/4× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ password-gen │ 7 / 21s / $0.12 │ 22 / 96s / $0.41 │ 3×/5×/3× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ todo-list-cli │ 5 / 25s / $0.13 │ 28 / 118s / $0.52 │ 6×/5×/4× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ unit-convert │ 9 / 28s / $0.14 │ 23 / 55s / $0.27 │ 3×/2×/2× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ url-shortener-cli │ 6 / 32s / $0.15 │ 27 / 106s / $0.42 │ 5×/3×/3× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ totals │ 56 / 248s / $1.23 │ 246 / 1043s / $4.45 │ 4.4×/4.2×/3.6× │ + └───────────────────┴───────────────────┴─────────────────────┴────────────────┘ + + What the agent rarely got stuck on (observation, not friction note) + + fn creation via heredoc + immediate ./dark eval testing + commit was reliable end-to-end. No trial needed more than two attempts at the same function. The cost is overwhelmingly upfront discovery, not iteration on broken code. + + The single highest-leverage fix you could ship + + If ./dark search ranked Stdlib.* function hits above module/type hits — and the for-ai doc inlined printLine / % / Stdlib.Float.add / the Int64.parse Result-match snippet — the average dark trial would likely drop from ~20 turns to ~10. The + fizzbuzz/hangman/todo/url-shortener pattern is the same loop repeated: agent knows what it wants, can't find it. + +✻ Crunched for 21m 13s · 1 monitor still running + + Error _ -> … appears in 6/9 dark trials. ParseError is opaque, so the Error branch can't differentiate "not a number" + from "overflow". + + 8. HTTP → JSON requires double Blob→String→parse unwrapping. github-stats: HttpClient.get returns body: Blob, + Blob.toString returns Result, so each HTTP+JSON flow needs two nested match blocks. + + 9. String⇄Char round-trips are awkward. hangman: no String.toChar or String.fromList; agent had to go via toList + | c + :: _ -> c pattern match. + + 10. ./dark eval double-prints when functions also printLine. hangman: agent had to choose between losing return-value + visibility (Unit-returning fns) or duplicate output. + + Per-task numbers (this run) + + ┌───────────────────┬───────────────────┬─────────────────────┬────────────────┐ + │ task │ python │ dark │ dark/py │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ countdown-timer │ 5 turns 28s $0.13 │ 15 76s $0.28 │ 3×/3×/2× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ even-or-odd-cli │ 5 / 18s / $0.11 │ 12 / 59s / $0.27 │ 2×/3×/2× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ fizzbuzz │ 5 / 16s / $0.10 │ 24 / 86s / $0.38 │ 5×/5×/4× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ github-stats │ 4 / 25s / $0.12 │ 23 / 132s / $0.48 │ 6×/5×/4× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ hangman │ 10 / 55s / $0.22 │ 41 / 213s / $0.88 │ 4×/4×/4× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ password-gen │ 7 / 21s / $0.12 │ 22 / 96s / $0.41 │ 3×/5×/3× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ todo-list-cli │ 5 / 25s / $0.13 │ 28 / 118s / $0.52 │ 6×/5×/4× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ unit-convert │ 9 / 28s / $0.14 │ 23 / 55s / $0.27 │ 3×/2×/2× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ url-shortener-cli │ 6 / 32s / $0.15 │ 27 / 106s / $0.42 │ 5×/3×/3× │ + ├───────────────────┼───────────────────┼─────────────────────┼────────────────┤ + │ totals │ 56 / 248s / $1.23 │ 246 / 1043s / $4.45 │ 4.4×/4.2×/3.6× │ + └───────────────────┴───────────────────┴─────────────────────┴────────────────┘ + + What the agent rarely got stuck on (observation, not friction note) + + fn creation via heredoc + immediate ./dark eval testing + commit was reliable end-to-end. No trial needed more than + two attempts at the same function. The cost is overwhelmingly upfront discovery, not iteration on broken code. + + fn creation via heredoc + immediate ./dark eval testing + commit was reliable end-to-end. No trial needed more than two attempts at the same + function. The cost is overwhelmingly upfront discovery, not iteration on broken code. + + The single highest-leverage fix you could ship + + If ./dark search ranked Stdlib.* function hits above module/type hits — and the for-ai doc inlined printLine / % / Stdlib.Float.add / the + Int64.parse Result-match snippet — the average dark trial would likely drop from ~20 turns to ~10. The fizzbuzz/hangman/todo/url-shortener pattern + is the same loop repeated: agent knows what it wants, can't find it. + + + + +Strings & I/O +- "\n" is a literal backslash-n; getting a real newline requires Stdlib.Blob.fromList [10uy] +- \n inside $"…" interpolated strings is silently dropped (no warning) +- No eprintLine / stderr print function — must use Builtin.posixFdWrite 2L blob manually +- run-cli's 2>&1 | tee merges stderr into stdout, so even when you write to fd 2 the caller can't observe it +- run @fn always prints the return value, so a printLine inside the function duplicates output — forces you to return the string instead of +printing + +Function definition / cleanup +- Inline fn 'Name body…' doesn't work for multi-line bodies; must use fn name - < even for trivially-ASCII inputs, forcing Option.withDefault for a literal newline +- Blob.toHex returns uppercase (works, but undocumented; affects code stability) +- No Blob.toHex in stdlib path — only blobToHex builtin + +Workflow +- Every run-cli call needs --branch "$BENCH_DARK_BRANCH" repeated; easy to forget and target main +- Branch context doesn't persist between invocations + + + + +fn ergonomics +- Multi-line bodies require fn Name - <<'EOF' heredoc; inline form fails with confusing error message ("Inline function definition required" — implies the opposite of what's true) + +Output / run quirks +- run @fn always prints the return value, including () for Unit. Forces consumers to head -1 or makes you return the value instead of printing +- Two printLine functions: Stdlib.println and Builtin.printLine — same behavior, unclear which to prefer, easy to accidentally mix + +Commit / workflow +- commit prompts y/n interactively, no --yes flag → must pipe echo y +- DARK_ACCOUNT env var requirement is undocumented in docs for-ai; first commit fails with "No account set" with no hint about the env var + +Type signatures / docs +- Stdlib.Int64.mod behavior for negative inputs is correct but undocumented; no doc comment on whether it's remainder or true modulo + +Friction report comparison + + +1. Stdlib module naming surprises — Stdlib.File, Stdlib.Env don't exist; everything filesystem-/process-related is under Stdlib.Cli.*. 3+ wasted commands per trial that needs file I/O. +2. Heavy upfront discovery — 4–12 commands of docs signatures / search before any fn is created. This is rational behavior but expensive. Could be cut by giving the model a curated "task-relevant stdlib quick-ref" up front. +3. Branch flag repetition — model still calls this out as friction even though it's documented. Real ergonomic issue. dark use-branch for session default would fix it. +4. String.digest opaque output format — model has to test it experimentally because no Stdlib.Crypto.sha256 exists with documented hex output. +5. No string interpolation — ++ chains for path building (stateDir ++ "/" ++ code) are noisy. +6. Env.get returns Option but File.* returns Result — inconsistent, model uses different match shapes for "might fail" cases. +7. No ? operator — every Result handling is full match Ok | Error. + +8. Int64.mod negative-number behavior undocumented — model has to verify experimentally. (docs signatures don't include function's doc comment.) + +9. No function overloading — handling missing CLI args (Unit) vs given args (String) requires either two functions or accepting a runtime type error. + +10. Integer L suffix — model still slips on this occasionally in match patterns. + + + +- No infix arithmetic for Float (unit-convert): Stdlib.Float.add (Stdlib.Float.multiply c 1.8) 32.0 instead of c * 1.8 + 32.0. Major verbosity tax for math. +- Int64.mod not % (even-or-odd, 2 of 4 trials) +- L suffix on every Int64 literal +- Stdlib.String.random returns Result (password-gen) — forces extra match for what's really infallible on non-negative input +- run @fn always prints () for Unit returns (every dark trial mentions this) +- String.digest algorithm undocumented (url-shortener) +- writeText vs writeAtomic distinction undocumented +- search doesn't show signatures — finding functions requires search → view → docs signatures, three commands minimum + + + 🔴 1. Json.parse rejects extra fields → forced regex/string-splitting + + Stdlib.Json.parse "{\"total_count\": 42, \"items\": [...]}" + → Error: extra fields not in schema + + GitHub API responses have ~30 fields per object. The model can't define a record matching all of them, so Json.parse is unusable for real APIs. + Workaround forced: hand-rolled regex / string-splitting. Cost: ~10–15 turns. + + This is the highest-impact Dark fix surfaced by the bench. If Json.parse either (a) ignored extra fields by default or (b) had a Json.parseLoose + variant, this trial would have been ~30 turns instead of 100. + + 🔴 2. Regex.find silently fails on \d shorthand + + Stdlib.Regex.find body "\"total_count\": \\d+" + → None (no error, no match) + + The model spent ~20 turns iterating on regex patterns before discovering it has to write [0-9]+ instead. Should error or document — silent + failure is the worst case. + + 🔴 3. String.replaceAll silently fails on " from HTTP body + + Stdlib.String.replaceAll body "\"" "" + → body unchanged, even though byte 34 is present + + Per the friction note, the same operation works on string literals but not on output of Stdlib.String.fromBlobWithReplacement. Possibly a + string-normalization difference between literal-Strings and Blob-decoded-Strings. Real bug. + + Carryover bugs that re-fired in github-stats + + - delete fn still broken (Stdlib.List.concat not found) — ~2 turns wasted + - discard needs echo y | (no --yes) — same papercut as commit was + - run @fn always prints () for Unit returns — generic noise + - \n not interpreted in $"..." interpolation — workarounds in multiple trials + + Other tasks' new friction (smaller signals) + + - unit-convert: confirms Stdlib.Float.add/multiply/divide chains are a major verbosity tax for math (no infix operators) + - url-shortener: let _ = to discard Result is awkward; missing ignore or statement-level discard + - password-gen: Stdlib.String.random returns Result even for known-valid inputs + + What this tells you about Dark + + The bench has now exercised: integer math, float math, randomness, persistent state, file I/O, HTTP, JSON, regex. The first six are within ~3-5× + of Python. HTTP+JSON+Regex is 17.6× — this is where Dark's stdlib needs the most work. + + Concrete priorities surfaced by this run: + 1. Json.parse schema strictness — breaks real-world API consumption + 2. Regex.find silent failure on \d — very dangerous footgun + 3. String.replaceAll Blob-vs-literal asymmetry — looks like a real bug + 4. delete fn broken + discard no --yes — workflow papercuts (already known) + 5. No infix operators for Float/Int64 — readability tax across all tasks diff --git a/backend/src/Builtins/Builtins.Language/Libs/Parser.fs b/backend/src/Builtins/Builtins.Language/Libs/Parser.fs index b5826c9bc9..c5d5758cb2 100644 --- a/backend/src/Builtins/Builtins.Language/Libs/Parser.fs +++ b/backend/src/Builtins/Builtins.Language/Libs/Parser.fs @@ -1293,6 +1293,62 @@ let fns () : List = sqlSpec = NotQueryable previewable = Impure capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + { name = fn "parserParsePackageToWrittenTypes" 0 + typeParams = [] + parameters = [ Param.make "sourceCode" TString "" ] + returnType = + TCustomType( + NR.ok ( + FQTypeName.fqPackage (LibExecution.PackageRefs.Type.Stdlib.option ()) + ), + [ TCustomType(NR.ok (FQTypeName.fqPackage (WTRefs.parsedFile ())), []) ] + ) + description = + "Parses Darklang package source with declaration semantics at the file root." + fn = + (function + | _, _, _, [| DString sourceCode |] -> + let pfKT = KTCustomType(FQTypeName.fqPackage (WTRefs.parsedFile ()), []) + let result = + match (P.parsePackage sourceCode).parsed with + | Some parsed -> + Dval.optionSome + pfKT + (WrittenTypesToDarkTypes.parsedFileToDT parsed) + | None -> Dval.optionNone pfKT + Ply result + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Impure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + { name = fn "parserParsePackageDiagnostics" 0 + typeParams = [] + parameters = [ Param.make "sourceCode" TString "" ] + returnType = + TList( + TTuple( + TCustomType(NR.ok (FQTypeName.fqPackage (PackageRefs.range ())), []), + TString, + [] + ) + ) + description = + "Package-source diagnostics using declaration semantics at the file root." + fn = + (function + | _, _, _, [| DString sourceCode |] -> + Ply( + WrittenTypesToDarkTypes.diagnosticsToDT + (P.parsePackage sourceCode).diagnostics + ) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Impure + capabilities = LibExecution.Capabilities.noCaps deprecated = NotDeprecated } ] diff --git a/backend/src/LibDB/ProgramTypes.fs b/backend/src/LibDB/ProgramTypes.fs index d1632fc95a..902935ce21 100644 --- a/backend/src/LibDB/ProgramTypes.fs +++ b/backend/src/LibDB/ProgramTypes.fs @@ -240,10 +240,12 @@ let search let isQualified = query.text.Contains "." // Multi-token searches require every significant token to match either the - // item name or doc comment. Single unqualified queries of 3+ chars also search docs. + // qualified location or doc comment. This also applies to dotted queries: + // `AltJson.get` should find `AltJson.Helpers.getInt`, even though the parent + // module and operation are not contiguous in the fully-qualified name. + // Single unqualified queries of 3+ chars also search docs. let tokens = tokenizeQuery query.text - let useTokenSearch = - (not query.exactMatch) && (not isQualified) && (List.length tokens > 1) + let useTokenSearch = (not query.exactMatch) && (List.length tokens > 1) // Ignore short filler tokens in multi-token searches, unless every token is short. let matchTokens = @@ -254,7 +256,15 @@ let search if useTokenSearch then matchTokens |> List.mapi (fun i _ -> - $"(l.name LIKE '%%' || @tok{i} || '%%' OR c.description LIKE '%%' || @tok{i} || '%%')") + // A conceptual query commonly combines a module with an operation, + // such as `string toList` or `json parse`. Match each token against + // the qualified location as well as the name and docs; otherwise the + // module token can only match when somebody happened to repeat it in + // the function's doc comment. + let qualifiedName = "(l.owner || '.' || l.modules || '.' || l.name)" + + $"({qualifiedName} LIKE '%%' || @tok{i} || '%%' " + + $"OR c.description LIKE '%%' || @tok{i} || '%%')") |> String.concat " AND " elif query.exactMatch then if isQualified then diff --git a/backend/src/LibParser/Parser.fs b/backend/src/LibParser/Parser.fs index dd87108f53..88b8f4f6a3 100644 --- a/backend/src/LibParser/Parser.fs +++ b/backend/src/LibParser/Parser.fs @@ -1390,7 +1390,16 @@ and parseLet (state : ParserState) (i : int) : WT.Expr * int = else errExpected state afterRet "'=' in function binding" (zeroWidthAtEnd (rng state afterRet), afterRet) - let (fnBody, m1) = parseBlock state m0 + // If an `if` starts on the same line as `=`, its `else` is commonly + // aligned with the binding rather than the inline `if` token. + let (fnBody, m1) = + if + tok state m0 = TIf + && (rng state m0).start.row = symbolEquals.end_.row + then + parseIf state keywordLet.start.column m0 + else + parseBlock state m0 let m = if tok state m1 = TIn then m1 + 1 else m1 let (body, p) = parseBlock state m let z = zeroWidthAtEnd keywordLet // no real `fun`/`->` tokens in this sugar @@ -1435,7 +1444,14 @@ and parseLet (state : ParserState) (i : int) : WT.Expr * int = // the value is an offside block, not a single expr, so a multi-statement // binding (`let x =\n doThing ()\n result`) sequences instead of gluing // the following statement onto the first as an application argument. - let (value, m) = parseBlock state k + let (value, m) = + if + tok state k = TIf + && (rng state k).start.row = symbolEquals.end_.row + then + parseIf state keywordLet.start.column k + else + parseBlock state k // `in` is optional let m = if tok state m = TIn then m + 1 else m let (body, p) = parseBlock state m @@ -1770,7 +1786,10 @@ and parseInterpString (state : ParserState) (i : int) : WT.Expr * int = |> List.map (fun (t : SpannedToken) -> { t with range = offRange t.range }) |> List.toArray - let subResult = parseTokensAt (state.interpDepth + 1) offToks + // Interpolation contents are expressions even when the surrounding + // file is being parsed as package declarations. + let subResult = + parseTokensAt (state.interpDepth + 1) false offToks // surface parse errors from inside the interpolation `{…}` (their // ranges are already offset to the outer source) rather than dropping them subResult.diagnostics |> List.iter state.diagnostics.Add @@ -3072,9 +3091,9 @@ and parseItemsBody if k = before then if tok state k = TEOF then go <- false else k <- k + 1 (List.ofSeq decls, List.ofSeq exprs, k) -and parseFile (state : ParserState) : ParseResult = +and parseFile (topLevelIsModule : bool) (state : ParserState) : ParseResult = validateLiterals state - let (topDecls, topExprs, _) = parseItems state false 0 0 + let (topDecls, topExprs, _) = parseItems state topLevelIsModule 0 0 let fileRange = if state.tokenCount > 1 then span (rng state 0) (rng state (state.tokenCount - 2)) @@ -3086,7 +3105,11 @@ and parseFile (state : ParserState) : ParseResult = /// Parse a pre-tokenized stream. Part of the rec chain so string interpolation /// can recursively parse the (range-offset) sub-tokens of each `{expr}`. -and parseTokensAt (interpDepth : int) (toks : SpannedToken[]) : ParseResult = +and parseTokensAt + (interpDepth : int) + (topLevelIsModule : bool) + (toks : SpannedToken[]) + : ParseResult = let scopes = System.Collections.Generic.Stack() scopes.Push { stmtCol = -1; stmtExact = false } let state = @@ -3102,11 +3125,18 @@ and parseTokensAt (interpDepth : int) (toks : SpannedToken[]) : ParseResult = abandoned = false steps = 0 interpDepth = interpDepth } - parseFile state + parseFile topLevelIsModule state + +and parseTokens (toks : SpannedToken[]) : ParseResult = + parseTokensAt 0 false toks -and parseTokens (toks : SpannedToken[]) : ParseResult = parseTokensAt 0 toks +let private parsePackageTokens (toks : SpannedToken[]) : ParseResult = + parseTokensAt 0 true toks -let private parseSyntax (source : string) : ParseResult = +let private parseSyntaxWithMode + (topLevelIsModule : bool) + (source : string) + : ParseResult = match tokenize source with | Error e -> { parsed = None @@ -3121,7 +3151,11 @@ let private parseSyntax (source : string) : ParseResult = | Ok(toksList, lexDiags) -> // lexical-recovery diagnostics (malformed lexemes the tokenizer recovered from) // are surfaced alongside the parser's own diagnostics. - let result = parseTokens (List.toArray toksList) + let result = + if topLevelIsModule then + parsePackageTokens (List.toArray toksList) + else + parseTokens (List.toArray toksList) let lexDiagnostics = lexDiags |> List.map (fun (r, m) -> @@ -3136,7 +3170,7 @@ let private parseSyntax (source : string) : ParseResult = /// Parse for tooling: return a recoverable tree and include mode-independent /// structural diagnostics after a clean syntax pass. let parse (source : string) : ParseResult = - let result = parseSyntax source + let result = parseSyntaxWithMode false source let syntaxDiagnostics = result.diagnostics // Tree-wide rules have one implementation in Validation. Run them only // after a clean syntax pass so recovery holes do not create cascaded errors. @@ -3149,13 +3183,30 @@ let parse (source : string) : ParseResult = | _ -> [] { result with diagnostics = syntaxDiagnostics @ structuralDiagnostics } +/// Parse package/module source where the file root is declaration scope. +/// A root `let value = ...` is therefore diagnosed as a package value that +/// must use `val`, rather than being consumed as a script-local binding whose +/// missing continuation is eventually reported at EOF. +let parsePackage (source : string) : ParseResult = + let result = parseSyntaxWithMode true source + let syntaxDiagnostics = result.diagnostics + let structuralDiagnostics = + match syntaxDiagnostics, result.parsed with + | [], Some(WT.SourceFile sourceFile) -> + sourceFile + |> Validation.validateStructure + |> List.map diagnosticOfValidationIssue + | _ -> [] + { result with diagnostics = syntaxDiagnostics @ structuralDiagnostics } + /// Parse for execution: syntax, structural, and file-purpose validation run /// once, and only a validated source file can be returned on success. let parseFor (mode : Validation.Mode) (source : string) : Result> = - let result = parseSyntax source + let result = + parseSyntaxWithMode (mode = Validation.Package) source match result.diagnostics, result.parsed with | [], Some(WT.SourceFile sourceFile) -> match Validation.validate mode sourceFile with diff --git a/backend/src/LibParser/WrittenTypesToProgramTypes.fs b/backend/src/LibParser/WrittenTypesToProgramTypes.fs index d9e5efb925..8338dd8276 100644 --- a/backend/src/LibParser/WrittenTypesToProgramTypes.fs +++ b/backend/src/LibParser/WrittenTypesToProgramTypes.fs @@ -94,6 +94,18 @@ let private enumTypeName (q.modules |> List.map (fun (m, _) -> m.name)) @ [ q.typ.name ] |> List.filter (fun s -> s <> "") +// Option and Result are language prelude types: their cases are valid bare in +// both patterns and expressions. Other enum cases still need `Type.Case`, since +// without an expected-type inference pass there is no type name to resolve. +let private enumTypeNameForCase + (q : WT.QualifiedTypeIdentifier) + (caseName : string) + : WT.UnresolvedEnumTypeName = + match enumTypeName q, caseName with + | [], ("Some" | "None") -> [ "Stdlib"; "Option"; "Option" ] + | [], ("Ok" | "Error") -> [ "Stdlib"; "Result"; "Result" ] + | names, _ -> names + module InfixFnName = let toPT (name : WT.InfixFnName) : PT.InfixFnName = match name with @@ -697,7 +709,7 @@ module Expr = // EVariable so name resolution disambiguates. Left as an EEnum, a DB or // variable ref would be forced into enum-case resolution and fail. | WT.EEnum(_, tn, (_, caseName), fields, _) when - List.isEmpty tn.modules && tn.typ.name = "" && List.isEmpty fields + List.isEmpty (enumTypeNameForCase tn caseName) && List.isEmpty fields -> return! toPT context (WT.EVariable(WT.synthRange, caseName)) | WT.EEnum(_, tn, (_, caseName), fields, _) -> @@ -708,7 +720,7 @@ module Expr = onMissing branchId currentModule - (enumTypeName tn) + (enumTypeNameForCase tn caseName) caseName let! exprs = Ply.List.mapSequentially (toPT context) fields let! typeArgs = @@ -885,7 +897,7 @@ module Expr = onMissing branchId currentModule - (enumTypeName tn) + (enumTypeNameForCase tn caseName) caseName let! fields = Ply.List.mapSequentially (toPT context) fields return PT.EPipeEnum(id, typeName, caseName, fields) diff --git a/backend/src/Wasm/wwwroot/index.html b/backend/src/Wasm/wwwroot/index.html index 3c79fccdd1..a2863aae4a 100644 --- a/backend/src/Wasm/wwwroot/index.html +++ b/backend/src/Wasm/wwwroot/index.html @@ -66,7 +66,7 @@

Darklang REPL

"x * 2", "let double (n: Int) : Int =\n n * 2", "double 21", - 'Stdlib.printLine "hello"', + 'Stdlib.println "hello"', 'match 3 with\n| 1 -> "one"\n| _ -> "many"', ]; // #input is a textarea so multi-line examples survive; grow it with content. diff --git a/backend/testfiles/execution/stdlib/list.dark b/backend/testfiles/execution/stdlib/list.dark index bcec03d957..d65b748c1c 100644 --- a/backend/testfiles/execution/stdlib/list.dark +++ b/backend/testfiles/execution/stdlib/list.dark @@ -205,9 +205,9 @@ Stdlib.List.map2shortest [ 10L; 20L ] [ 1L; 2L; 3L ] (fun a b -> a - b) = [ 9L; Stdlib.List.map2shortest [] [ 1L; 2L; 3L ] (fun a b -> a - b) = [] Stdlib.List.map2shortest [ 1L; 2L; 3L ] [] (fun a b -> a - b) = [] -Stdlib.List.member [ 1L; 2L; 3L ] 2L = true -Stdlib.List.member [ 1L; 2L; 3L ] 4L = false -Stdlib.List.member [] 1L = false +Stdlib.List.contains [ 1L; 2L; 3L ] 2L = true +Stdlib.List.contains [ 1L; 2L; 3L ] 4L = false +Stdlib.List.contains [] 1L = false module Partition = Stdlib.List.partition [ -20L; 5L; 9L ] (fun x -> x > 0L) = ([ 5L; 9L ], [ -20L ]) diff --git a/backend/tests/Tests/LibParser.Tests.fs b/backend/tests/Tests/LibParser.Tests.fs index 31955322cc..47cb57fcd4 100644 --- a/backend/tests/Tests/LibParser.Tests.fs +++ b/backend/tests/Tests/LibParser.Tests.fs @@ -304,6 +304,31 @@ let private parserStructureTests = "migration hint" | other -> failtest $"expected one diagnostic, got {other}") + testCase "package roots diagnose let values without an EOF cascade" (fun _ -> + let source = + "let chars = \"abc\"\n\nlet length (s: String) : Int = Stdlib.String.length s" + let result = P.parsePackage source + match result.diagnostics with + | [ diagnostic ] -> + Expect.equal + diagnostic.message + "Module value declarations must use 'val'; 'let' is reserved for functions and local bindings" + "focused package-value diagnostic" + Expect.isFalse + (diagnostic.message.Contains "end of file") + "the package-mode diagnostic identifies the root cause" + | other -> failtest $"expected one diagnostic, got {other}") + + testCase "inline binding if allows else aligned with let" (fun _ -> + let source = + "let choose (condition: Bool) : Int =\n let result = if condition then\n 1\n else\n 2\n result" + let result = P.parsePackage source + Expect.isEmpty result.diagnostics "ordinary binding-aligned else parses" + match result.parsed with + | Some(WT.SourceFile { declarations = [ WT.DFunction _ ]; exprsToEval = [] }) -> + () + | other -> failtest $"function declaration: {other}") + testCase "val cannot declare a function" (fun _ -> let result = P.parse "val f (x: Int64) : Int64 = x" Expect.exists @@ -669,6 +694,19 @@ let private desugarTests = match toPT (lowerExpr "XDB") with | PT.EVariable(_, "XDB") -> () | other -> failtest $"expected EVariable XDB, got: {other}") + testCase "bare prelude constructors lower to their enum types" (fun _ -> + match toPT (lowerExpr "Some 5L"), toPT (lowerExpr "None") with + | (PT.EEnum(_, someType, [], "Some", [ PT.EInt64 _ ]), + PT.EEnum(_, noneType, [], "None", [])) -> + Expect.equal + someType.originalName + [ "Stdlib"; "Option"; "Option" ] + "Some type" + Expect.equal + noneType.originalName + [ "Stdlib"; "Option"; "Option" ] + "None type" + | other -> failtest $"expected Option constructors, got: {other}") testCase "a statement sequence lowers to EStatement" (fun _ -> match toPT (fnBody "let f () : Unit =\n foo ()\n bar ()") with | PT.EStatement(_, PT.EApply _, PT.EApply _) -> () diff --git a/backend/tests/Tests/WrittenTypesLoweringParity.Tests.fs b/backend/tests/Tests/WrittenTypesLoweringParity.Tests.fs index 0a0626afbc..971bb96b84 100644 --- a/backend/tests/Tests/WrittenTypesLoweringParity.Tests.fs +++ b/backend/tests/Tests/WrittenTypesLoweringParity.Tests.fs @@ -315,7 +315,11 @@ let tests = [ "M" ], [], [ "x" ], - "x |> Stdlib.Json.serialize") ] + "x |> Stdlib.Json.serialize") + ("bare-some", [ "M" ], [], [], "Some 5") + ("bare-none", [ "M" ], [], [], "None") + ("bare-ok", [ "M" ], [], [], "Ok 5") + ("bare-error", [ "M" ], [], [], "Error \"no\"") ] let mismatches = ResizeArray() for (label, cmod, cfn, prms, snip) in cases do let ctx : WT2PT.Context = diff --git a/packages/darklang/cli/ai/agent.dark b/packages/darklang/cli/ai/agent.dark index fdcd653272..432124f37a 100644 --- a/packages/darklang/cli/ai/agent.dark +++ b/packages/darklang/cli/ai/agent.dark @@ -170,11 +170,11 @@ let runCliCommand match parts with | [] -> Stdlib.Result.Result.Error "command required" | commandName :: _ -> - let isWriteCommand = Stdlib.List.member writeCommands commandName + let isWriteCommand = Stdlib.List.contains writeCommands commandName let writesAllowed = writeMode != WriteMode.Denied let allCommands = Stdlib.List.append readOnlyCommands writeCommands - let commandExists = Stdlib.List.member allCommands commandName + let commandExists = Stdlib.List.contains allCommands commandName // Check confirmation for write commands let userRejected = @@ -193,7 +193,7 @@ let runCliCommand match findCliExecutable with | Some exe -> let fullCmd = exe ++ " " ++ branchArg ++ " " ++ cmd - Stdlib.printLine (Colors.dimText ("[agent] cli> " ++ cmd)) + Stdlib.println (Colors.dimText ("[agent] cli> " ++ cmd)) let result = Stdlib.Cli.execute fullCmd match result.exitCode with @@ -203,12 +203,12 @@ let runCliCommand Stdlib.Result.Result.Error (Stdlib.String.trim output) else let preview = if (Stdlib.String.length output) > 100 then (Stdlib.String.slice output 0 100) ++ "..." else output - Stdlib.printLine (Colors.dimText (" -> " ++ preview)) + Stdlib.println (Colors.dimText (" -> " ++ preview)) Stdlib.Result.Result.Ok (Darklang.LLM.Agent.ToolOutput.text output) | exitCode -> let details = if result.stderr != "" then result.stderr else if result.stdout != "" then result.stdout else "no output" let errorMsg = "CLI command failed (exit " ++ (Stdlib.Int.toString exitCode) ++ "): " ++ (Stdlib.String.trim details) - Stdlib.printLine (Colors.error (" -> " ++ errorMsg)) + Stdlib.println (Colors.error (" -> " ++ errorMsg)) Stdlib.Result.Result.Error errorMsg | None -> Stdlib.Result.Result.Error "Could not find CLI executable" @@ -251,25 +251,25 @@ let printAgentInfo (provider: String) (requestedProvider: Stdlib.Option.Option $"[agent] provider: {providerName}" | None -> $"[agent] provider: {providerName} (auto-detected)" - Stdlib.printLine (Colors.dimText providerLine) - Stdlib.printLine (Colors.dimText $"[agent] model: {model.name}") - Stdlib.printLine (Colors.dimText $"[agent] branch: {branchName}") + Stdlib.println (Colors.dimText providerLine) + Stdlib.println (Colors.dimText $"[agent] model: {model.name}") + Stdlib.println (Colors.dimText $"[agent] branch: {branchName}") let printResult (result: Stdlib.Result.Result) : Unit = match result with | Ok response -> if Stdlib.Bool.not (Stdlib.List.isEmpty response.toolCalls) then - Stdlib.printLine "" - Stdlib.printLine (Colors.info "Actions:") + Stdlib.println "" + Stdlib.println (Colors.info "Actions:") Stdlib.List.iter response.toolCalls (fun tc -> let status = if tc.isError then Colors.error "error" else Colors.success "ok" - Stdlib.printLine $" - {tc.name} [{status}]") - Stdlib.printLine "" - Stdlib.printLine response.response + Stdlib.println $" - {tc.name} [{status}]") + Stdlib.println "" + Stdlib.println response.response | Error e -> - Stdlib.printLine (Colors.error e) + Stdlib.println (Colors.error e) if Stdlib.String.contains e "API key" then - Stdlib.printLine (Colors.dimText "Tip: set ANTHROPIC_API_KEY or OPENAI_API_KEY, or run with --provider local") + Stdlib.println (Colors.dimText "Tip: set ANTHROPIC_API_KEY or OPENAI_API_KEY, or run with --provider local") let resolveModel (provider: String) (requestedModel: Stdlib.Option.Option) : Darklang.LLM.Models.Model = match requestedModel with @@ -277,7 +277,7 @@ let resolveModel (provider: String) (requestedModel: Stdlib.Option.Option m | None -> - Stdlib.printLine (Colors.warning $"Unknown model '{name}', using default for {provider}") + Stdlib.println (Colors.warning $"Unknown model '{name}', using default for {provider}") defaultModelForProvider provider | None -> defaultModelForProvider provider @@ -297,7 +297,7 @@ let runAgent (state: Cli.AppState) (provider: String) (requestedProvider: Stdlib let agent = createAgent model branchName writeMode printAgentInfo provider requestedProvider model branchName let modeLabel = writeModeLabel writeMode - Stdlib.printLine (Colors.dimText $"[agent] mode: {modeLabel}") + Stdlib.println (Colors.dimText $"[agent] mode: {modeLabel}") printResult (Darklang.LLM.Agent.run agent task) let runInteractiveLoop @@ -312,16 +312,16 @@ let runInteractiveLoop else if input == "exit" || input == "quit" || input == ":q" then () else - Stdlib.printLine (Colors.dimText "[agent] thinking...") + Stdlib.println (Colors.dimText "[agent] thinking...") match Darklang.LLM.Agent.runWithHistory agent history input with | Ok response -> - Stdlib.printLine (Colors.info "agent> ") - Stdlib.printLine response.response + Stdlib.println (Colors.info "agent> ") + Stdlib.println response.response // Use full message history including tool calls/results for context continuity let _ = runInteractiveLoop agent response.messages () | Error e -> - Stdlib.printLine (Colors.error e) + Stdlib.println (Colors.error e) runInteractiveLoop agent history let runInteractive (state: Cli.AppState) (provider: String) (requestedProvider: Stdlib.Option.Option) (requestedModel: Stdlib.Option.Option) (writeMode: WriteMode) : Unit = @@ -330,8 +330,8 @@ let runInteractive (state: Cli.AppState) (provider: String) (requestedProvider: let agent = createAgent model branchName writeMode printAgentInfo provider requestedProvider model branchName let modeLabel = writeModeLabel writeMode - Stdlib.printLine (Colors.dimText $"[agent] mode: {modeLabel}") - Stdlib.printLine (Colors.dimText "[agent] interactive mode. Type ':q' to exit.") + Stdlib.println (Colors.dimText $"[agent] mode: {modeLabel}") + Stdlib.println (Colors.dimText "[agent] interactive mode. Type ':q' to exit.") runInteractiveLoop agent [] @@ -353,19 +353,19 @@ let executeAsk (state: Cli.AppState) (provider: String) (options: AgentOptions) let executeCode (state: Cli.AppState) (provider: String) (options: AgentOptions) : Unit = let task = Stdlib.String.join options.args " " if Stdlib.String.isEmpty (Stdlib.String.trim task) then - Stdlib.printLine (Colors.error "Usage: agent code [--yes|-y]") + Stdlib.println (Colors.error "Usage: agent code [--yes|-y]") else runAgent state provider options.provider options.model task options.writeMode let executeReview (state: Cli.AppState) (provider: String) (options: AgentOptions) : Unit = match options.args with | [ itemName ] -> runAgent state provider options.provider options.model (reviewTaskPrompt itemName) WriteMode.Denied - | _ -> Stdlib.printLine (Colors.error "Usage: agent review ") + | _ -> Stdlib.println (Colors.error "Usage: agent review ") let executeFix (state: Cli.AppState) (provider: String) (options: AgentOptions) : Unit = match options.args with | [ itemName ] -> runAgent state provider options.provider options.model (fixTaskPrompt itemName) options.writeMode - | _ -> Stdlib.printLine (Colors.error "Usage: agent fix [--yes|-y]") + | _ -> Stdlib.println (Colors.error "Usage: agent fix [--yes|-y]") type Subcommand = { name: String; description: String; usage: String; execute: Cli.AppState -> String -> AgentOptions -> Unit } @@ -382,8 +382,8 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = let options = parseOptions args defaultOptions let provider = resolveProvider options.provider - if Stdlib.Bool.not (Stdlib.List.member validProviders provider) then - Stdlib.printLine (Colors.error $"Unknown provider: {provider}. Use: anthropic, openai, local") + if Stdlib.Bool.not (Stdlib.List.contains validProviders provider) then + Stdlib.println (Colors.error $"Unknown provider: {provider}. Use: anthropic, openai, local") state else match options.args with @@ -398,7 +398,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | None -> let nameList = Stdlib.List.map subcommands (fun s -> s.name) let names = Stdlib.String.join nameList "|" - Stdlib.printLine (Colors.error $"Unknown subcommand. Use: agent {names}") + Stdlib.println (Colors.error $"Unknown subcommand. Use: agent {names}") state let help (state: Cli.AppState) : Cli.AppState = diff --git a/packages/darklang/cli/apps/command.dark b/packages/darklang/cli/apps/command.dark index c27ee113f0..971cb41bb3 100644 --- a/packages/darklang/cli/apps/command.dark +++ b/packages/darklang/cli/apps/command.dark @@ -48,24 +48,24 @@ let row (app: Model.App) : String = /// `apps run ` — run a foreground app attached, dispatching its CLI command in-process. let runApp (state: Cli.AppState) (slug: String) : Cli.AppState = match Registry.findBySlug state.currentBranchId slug with - | None -> Stdlib.printLine (Colors.error $"no app '{slug}' (see `apps list-available`)") + | None -> Stdlib.println (Colors.error $"no app '{slug}' (see `apps list-available`)") | Some app -> match app.target with | Foreground command -> Cli.Registry.executeCommand command state [] | Daemon _ -> - Stdlib.printLine (Colors.dimText $"'{slug}' is a daemon — start it with `apps start {slug}`") + Stdlib.println (Colors.dimText $"'{slug}' is a daemon — start it with `apps start {slug}`") state /// `apps start ` — launch a daemon detached (verified) and report its pid. let startDaemon (state: Cli.AppState) (slug: String) : Cli.AppState = if Service.platform () == Service.Platform.Unsupported then - Stdlib.printLine + Stdlib.println (Colors.error "Background daemons need Linux or macOS — they're not supported on this platform yet.") state else match Registry.findBySlug state.currentBranchId slug with - | None -> Stdlib.printLine (Colors.error $"no app '{slug}' (see `apps list-available`)") + | None -> Stdlib.println (Colors.error $"no app '{slug}' (see `apps list-available`)") | Some app -> match app.target with | Daemon entrypoint -> @@ -74,32 +74,32 @@ let startDaemon (state: Cli.AppState) (slug: String) : Cli.AppState = match Stdlib.Cli.Daemon.start slug expr with | Ok pid -> - Stdlib.printLine (Colors.success $"✓ {app.name} started (pid {Stdlib.Int.toString pid})") - | Error e -> Stdlib.printLine (Colors.error e) + Stdlib.println (Colors.success $"✓ {app.name} started (pid {Stdlib.Int.toString pid})") + | Error e -> Stdlib.println (Colors.error e) | Foreground _ -> - Stdlib.printLine (Colors.dimText $"'{slug}' is a foreground app — run it with `apps run {slug}`") + Stdlib.println (Colors.dimText $"'{slug}' is a foreground app — run it with `apps run {slug}`") state /// `apps inspect ` — label, slug, target, installed?, and (daemons) status + recent logs. let inspect (state: Cli.AppState) (slug: String) : Cli.AppState = match Registry.findBySlug state.currentBranchId slug with - | None -> Stdlib.printLine (Colors.error $"no app '{slug}' (see `apps list-available`)") + | None -> Stdlib.println (Colors.error $"no app '{slug}' (see `apps list-available`)") | Some app -> - Stdlib.printLine $"{app.name} [{Model.kindTag app.target}]" - Stdlib.printLine (Colors.dimText $" {app.slug} — {app.description}") - Stdlib.printLine $" target: {Model.reference app.target}" + Stdlib.println $"{app.name} [{Model.kindTag app.target}]" + Stdlib.println (Colors.dimText $" {app.slug} — {app.description}") + Stdlib.println $" target: {Model.reference app.target}" let installedStr = if Registry.isInstalled slug then "yes" else "no" - Stdlib.printLine $" installed: {installedStr}" + Stdlib.println $" installed: {installedStr}" match app.target with | Daemon _ -> - Stdlib.printLine $" status: {Stdlib.Cli.Daemon.statusLine slug}" + Stdlib.println $" status: {Stdlib.Cli.Daemon.statusLine slug}" let logs = Stdlib.Cli.Daemon.tailLog slug 5 if Stdlib.List.isEmpty logs then () else - Stdlib.printLine " recent logs:" + Stdlib.println " recent logs:" logs |> Stdlib.List.map (fun l -> " " ++ l) |> Stdlib.printLines | Foreground _ -> () @@ -114,17 +114,17 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | [ "list" ] -> let apps = Registry.installed state.currentBranchId if Stdlib.List.isEmpty apps then - Stdlib.printLine + Stdlib.println (Colors.dimText "No apps installed. `apps list-available` shows the catalog; `apps add ` adds one.") else - Stdlib.printLine "Installed apps:" + Stdlib.println "Installed apps:" apps |> Stdlib.List.map (fun a -> row a) |> Stdlib.printLines state | [ "list-available" ] | [ "available" ] -> - Stdlib.printLine "Available apps:" + Stdlib.println "Available apps:" (Registry.catalog state.currentBranchId) |> Stdlib.List.map (fun a -> let installedTag = @@ -132,40 +132,40 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = (row a) ++ installedTag) |> Stdlib.printLines - Stdlib.printLine "" - Stdlib.printLine (Colors.dimText " `apps add ` to add one to this instance") + Stdlib.println "" + Stdlib.println (Colors.dimText " `apps add ` to add one to this instance") state | [ "add"; slug ] -> match Registry.findBySlug state.currentBranchId slug with | None -> - Stdlib.printLine (Colors.error $"no app '{slug}' (see `apps list-available`)") + Stdlib.println (Colors.error $"no app '{slug}' (see `apps list-available`)") state | Some app -> if Registry.isInstalled slug then - Stdlib.printLine (Colors.dimText $"'{app.name}' is already installed") + Stdlib.println (Colors.dimText $"'{app.name}' is already installed") state else Registry.addInstalled slug - Stdlib.printLine (Colors.success $"✓ added {app.name}") + Stdlib.println (Colors.success $"✓ added {app.name}") // Foreground apps get a PATH shim so `` runs from the shell; daemons are start/stop-managed. match app.target with | Foreground _ -> match Install.writeAlias app with | Ok path -> - Stdlib.printLine (Colors.dimText $" installed shim {path} — run `{slug}` from the shell (or `apps run {slug}`)") + Stdlib.println (Colors.dimText $" installed shim {path} — run `{slug}` from the shell (or `apps run {slug}`)") | Error e -> - Stdlib.printLine (Colors.dimText $" (couldn't write shell shim: {e}) — run it with `apps run {slug}`") - | Daemon _ -> Stdlib.printLine (Colors.dimText $" start it with `apps start {slug}`") + Stdlib.println (Colors.dimText $" (couldn't write shell shim: {e}) — run it with `apps run {slug}`") + | Daemon _ -> Stdlib.println (Colors.dimText $" start it with `apps start {slug}`") state | [ "remove"; slug ] -> if Registry.isInstalled slug then Registry.removeInstalled slug Install.removeAlias slug - Stdlib.printLine (Colors.success $"✓ removed '{slug}'") + Stdlib.println (Colors.success $"✓ removed '{slug}'") else - Stdlib.printLine (Colors.dimText $"'{slug}' is not installed") + Stdlib.println (Colors.dimText $"'{slug}' is not installed") state @@ -176,8 +176,8 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | [ "stop"; slug ] -> match Stdlib.Cli.Daemon.stop slug with - | Ok msg -> Stdlib.printLine (Colors.success msg) - | Error e -> Stdlib.printLine (Colors.error e) + | Ok msg -> Stdlib.println (Colors.success msg) + | Error e -> Stdlib.println (Colors.error e) state @@ -192,10 +192,10 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = Registry.isInstalled a.slug || Stdlib.Cli.Daemon.isRunning a.slug) if Stdlib.List.isEmpty daemons then - Stdlib.printLine ( + Stdlib.println ( Colors.dimText "No daemons running or installed. Start one, e.g.: dark apps start sync") else - Stdlib.printLine "Daemon status:" + Stdlib.println "Daemon status:" daemons |> Stdlib.List.map (fun a -> " " ++ Stdlib.Cli.Daemon.statusLine a.slug) |> Stdlib.printLines @@ -203,14 +203,14 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = state | [ "status"; slug ] -> - Stdlib.printLine (" " ++ Stdlib.Cli.Daemon.statusLine slug) + Stdlib.println (" " ++ Stdlib.Cli.Daemon.statusLine slug) state | [ "logs"; slug ] -> let lines = Stdlib.Cli.Daemon.tailLog slug 20 if Stdlib.List.isEmpty lines then - Stdlib.printLine (Colors.dimText $"no logs for '{slug}' (has it run?)") + Stdlib.println (Colors.dimText $"no logs for '{slug}' (has it run?)") else Stdlib.printLines lines @@ -220,18 +220,18 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | [ "enable"; slug ] -> match Registry.findBySlug state.currentBranchId slug with - | None -> Stdlib.printLine (Colors.error $"no app '{slug}' (see `apps list-available`)") + | None -> Stdlib.println (Colors.error $"no app '{slug}' (see `apps list-available`)") | Some app -> match Service.enable app with - | Enabled msg -> Stdlib.printLine (Colors.success $"✓ {msg}") - | WroteOnly msg -> Stdlib.printLine (Colors.colorize Colors.yellow $"⚠ {msg}") - | Failed e -> Stdlib.printLine (Colors.error e) + | Enabled msg -> Stdlib.println (Colors.success $"✓ {msg}") + | WroteOnly msg -> Stdlib.println (Colors.colorize Colors.yellow $"⚠ {msg}") + | Failed e -> Stdlib.println (Colors.error e) state | [ "disable"; slug ] -> match Service.disable slug with - | Ok msg -> Stdlib.printLine (Colors.success $"✓ {msg}") - | Error e -> Stdlib.printLine (Colors.error e) + | Ok msg -> Stdlib.println (Colors.success $"✓ {msg}") + | Error e -> Stdlib.println (Colors.error e) state | _ -> Command.help state @@ -272,9 +272,9 @@ let helpLine (usage: String) (desc: String) : String = " " ++ pad ++ Colors.dimText desc let help (state: Cli.AppState) : Cli.AppState = - Stdlib.printLine (Colors.boldText "dark apps") - Stdlib.printLine (Colors.dimText " See, curate, run, and manage the Dark apps this instance cares about.") - Stdlib.printLine "" + Stdlib.println (Colors.boldText "dark apps") + Stdlib.println (Colors.dimText " See, curate, run, and manage the Dark apps this instance cares about.") + Stdlib.println "" // Shared — work on any app. [ helpLine "apps" "Interactive browser: review the catalog, install, start/stop" helpLine "apps list" "Apps you've installed (empty by default)" @@ -284,12 +284,12 @@ let help (state: Cli.AppState) : Cli.AppState = helpLine "apps inspect " "Detail: target, installed?, status + logs" ] |> Stdlib.printLines - Stdlib.printLine "" - Stdlib.printLine (Colors.boldText " Foreground apps") + Stdlib.println "" + Stdlib.println (Colors.boldText " Foreground apps") [ helpLine "apps run " "Run attached in your terminal" ] |> Stdlib.printLines - Stdlib.printLine "" - Stdlib.printLine (Colors.boldText " Daemons") + Stdlib.println "" + Stdlib.println (Colors.boldText " Daemons") [ helpLine "apps start " "Launch detached (alias: spawn)" helpLine "apps stop " "Stop a running daemon" helpLine "apps status [slug]" "Running / stopped / stale (+ pid)" diff --git a/packages/darklang/cli/apps/editor.dark b/packages/darklang/cli/apps/editor.dark index d961d7f13f..d9a048166b 100644 --- a/packages/darklang/cli/apps/editor.dark +++ b/packages/darklang/cli/apps/editor.dark @@ -57,7 +57,7 @@ let refreshPresentation (state: State) : State = else [] AppPresentation { - installed = Stdlib.List.member installed app.slug + installed = Stdlib.List.contains installed app.slug daemonState = daemonState recentLogs = recentLogs }) @@ -448,5 +448,5 @@ let launch (cliState: Cli.AppState) : Cli.AppState = { cliState with currentPage = Cli.Page.SubApp (makeSubApp session) } | Error message -> - Stdlib.printLine message + Stdlib.println message cliState diff --git a/packages/darklang/cli/apps/examples.dark b/packages/darklang/cli/apps/examples.dark index ac716b3733..ad2d360114 100644 --- a/packages/darklang/cli/apps/examples.dark +++ b/packages/darklang/cli/apps/examples.dark @@ -12,7 +12,7 @@ let beat (name: String) (intervalMs: Int) (remaining: Int) : Int = 0 else let pid = Stdlib.Cli.Sys.currentPid () - Stdlib.printLine $"{name}: beat (pid {Stdlib.Int.toString pid})" + Stdlib.println $"{name}: beat (pid {Stdlib.Int.toString pid})" Stdlib.Cli.Posix.sleep (Stdlib.Int.toFloat intervalMs) beat name intervalMs (remaining - 1) @@ -26,9 +26,9 @@ let heartbeat (name: String) : Int = let _logged = match Stdlib.Cli.Daemon.claimPidfile name with - | Error e -> Stdlib.printLine $"{name}: could not write pidfile: {e}" + | Error e -> Stdlib.println $"{name}: could not write pidfile: {e}" | Ok _ -> - Stdlib.printLine + Stdlib.println $"{name}: started (pid {Stdlib.Int.toString (Stdlib.Cli.Sys.currentPid ())}, interval {Stdlib.Int.toString intervalMs}ms)" beat name intervalMs 1000000000 @@ -60,7 +60,7 @@ module TextEdit = Component.launch cliState component Outliner.TextEditor.empty let help (state: Cli.AppState) : Cli.AppState = - Stdlib.printLine "A tiny text editor (demo) — type, Enter or Esc to exit." + Stdlib.println "A tiny text editor (demo) — type, Enter or Esc to exit." state let complete diff --git a/packages/darklang/cli/apps/registry.dark b/packages/darklang/cli/apps/registry.dark index c526737806..b72506e52b 100644 --- a/packages/darklang/cli/apps/registry.dark +++ b/packages/darklang/cli/apps/registry.dark @@ -71,7 +71,7 @@ let catalog (branchId: Uuid) : List = let seedSlugs = (available ()) |> Stdlib.List.map (fun a -> a.slug) let extra = (discover branchId) - |> Stdlib.List.filter (fun a -> Stdlib.Bool.not (Stdlib.List.member seedSlugs a.slug)) + |> Stdlib.List.filter (fun a -> Stdlib.Bool.not (Stdlib.List.contains seedSlugs a.slug)) Stdlib.List.append (available ()) extra @@ -94,7 +94,7 @@ let installedSlugs () : List = | None -> [] let isInstalled (slug: String) : Bool = - Stdlib.List.member (installedSlugs ()) slug + Stdlib.List.contains (installedSlugs ()) slug let saveInstalled (slugs: List) : Unit = let cfg = Config.readConfig () @@ -113,4 +113,4 @@ let removeInstalled (slug: String) : Unit = /// installed discovered app still resolves. Empty by default. let installed (branchId: Uuid) : List = let slugs = installedSlugs () - (catalog branchId) |> Stdlib.List.filter (fun a -> Stdlib.List.member slugs a.slug) + (catalog branchId) |> Stdlib.List.filter (fun a -> Stdlib.List.contains slugs a.slug) diff --git a/packages/darklang/cli/apps/servers.dark b/packages/darklang/cli/apps/servers.dark index 3f5ca80c30..cc5588e1e8 100644 --- a/packages/darklang/cli/apps/servers.dark +++ b/packages/darklang/cli/apps/servers.dark @@ -16,12 +16,12 @@ let httpDaemon let port = Config.getInt ("apps." ++ name ++ ".port") defaultPort let announce () : Unit = - Stdlib.printLine + Stdlib.println $"{name}: serving {label} on http://localhost:{Stdlib.Int.toString port}" match Stdlib.HttpServer.serve (Stdlib.HttpServer.Config.defaults port) router announce with | Ok _ -> () - | Error msg -> Stdlib.printLine msg + | Error msg -> Stdlib.println msg // `dark-packages` — the package API server that powers wip.darklang.com/packages. Defaults to 9090, a diff --git a/packages/darklang/cli/apps/views/app.dark b/packages/darklang/cli/apps/views/app.dark index e5e65f00bc..357fb4a052 100644 --- a/packages/darklang/cli/apps/views/app.dark +++ b/packages/darklang/cli/apps/views/app.dark @@ -269,17 +269,17 @@ let execute match args with // Non-interactive: `views list` prints available views. | [ "list" ] -> - Stdlib.printLine + Stdlib.println (Darklang.Cli.Colors.boldText "Available views:") - Stdlib.printLine "" + Stdlib.println "" views |> Stdlib.List.iter (fun entry -> - Stdlib.printLine + Stdlib.println (" " ++ Darklang.Cli.Colors.boldText (slug entry) ++ " " ++ Darklang.Cli.Colors.dimText entry.description)) - Stdlib.printLine "" + Stdlib.println "" cliState // Non-interactive: `views ` prints one pure dashboard. @@ -295,10 +295,10 @@ let execute rowsAtSize entry.kind size |> Stdlib.printLines cliState | None -> - Stdlib.printLine + Stdlib.println (Darklang.Cli.Colors.error ("Unknown view: " ++ viewName)) - Stdlib.printLine + Stdlib.println "Use 'views list' to see available views." cliState @@ -306,7 +306,7 @@ let execute | _ -> match views with | [] -> - Stdlib.printLine + Stdlib.println (Darklang.Cli.Colors.dimText "No views available.") cliState | _ -> @@ -327,7 +327,7 @@ let execute currentPage = Darklang.Cli.Page.SubApp (makeSubApp session) } | Error message -> - Stdlib.printLine message + Stdlib.println message cliState diff --git a/packages/darklang/cli/auth/login.dark b/packages/darklang/cli/auth/login.dark index 21f8b203f1..45df5b6a97 100644 --- a/packages/darklang/cli/auth/login.dark +++ b/packages/darklang/cli/auth/login.dark @@ -10,23 +10,23 @@ let execute (state: AppState) (args: List) : AppState = | [] -> let accounts = SCM.Account.list () if Stdlib.List.isEmpty accounts then - Stdlib.printLine + Stdlib.println (Colors.error "No accounts. Seed via the .sql migration / LocalExec init.") state else - Stdlib.printLine "Available accounts:" + Stdlib.println "Available accounts:" (accounts) - |> Stdlib.List.iter (fun a -> Stdlib.printLine $" {a.name}") - Stdlib.printLine "" - Stdlib.printLine "Run `login ` to log in." + |> Stdlib.List.iter (fun a -> Stdlib.println $" {a.name}") + Stdlib.println "" + Stdlib.println "Run `login ` to log in." state | [ name ] -> match SCM.Account.getByName name with | None -> - Stdlib.printLine (Colors.error $"No account '{name}'.") - Stdlib.printLine "Run `login` to see the available accounts." + Stdlib.println (Colors.error $"No account '{name}'.") + Stdlib.println "Run `login` to see the available accounts." state | Some account -> // Persist to cli-config.json so the next CLI invocation @@ -39,13 +39,13 @@ let execute (state: AppState) (args: List) : AppState = (Stdlib.Uuid.toString account.id) Config.writeConfig updated - Stdlib.printLine (Colors.success $"Logged in as {account.name}.") + Stdlib.println (Colors.success $"Logged in as {account.name}.") { state with accountID = Stdlib.Option.Option.Some account.id accountName = account.name } | _ -> - Stdlib.printLine (Colors.error "Usage: login []") + Stdlib.println (Colors.error "Usage: login []") state diff --git a/packages/darklang/cli/auth/logout.dark b/packages/darklang/cli/auth/logout.dark index 3358dd1190..94cf24c598 100644 --- a/packages/darklang/cli/auth/logout.dark +++ b/packages/darklang/cli/auth/logout.dark @@ -16,15 +16,15 @@ let execute (state: AppState) (args: List) : AppState = match state.accountID with | None -> - Stdlib.printLine "Not logged in." + Stdlib.println "Not logged in." state | Some _ -> - Stdlib.printLine $"Logged out (was {state.accountName})." + Stdlib.println $"Logged out (was {state.accountName})." { state with accountID = Stdlib.Option.Option.None accountName = "" } | _ -> - Stdlib.printLine (Colors.error "Usage: logout") + Stdlib.println (Colors.error "Usage: logout") state diff --git a/packages/darklang/cli/builtins.dark b/packages/darklang/cli/builtins.dark index 34cf1a8963..5cafcb5980 100644 --- a/packages/darklang/cli/builtins.dark +++ b/packages/darklang/cli/builtins.dark @@ -159,8 +159,8 @@ let execute (state: AppState) (args: List): AppState = |> Stdlib.List.iter (fun pair -> let group = Stdlib.Tuple2.first pair let fns = Stdlib.Tuple2.second pair - Stdlib.printLine "" - Stdlib.printLine (Colors.colorize Colors.bold (getDisplayName group)) + Stdlib.println "" + Stdlib.println (Colors.colorize Colors.bold (getDisplayName group)) fns |> Stdlib.List.iter (fun fn -> @@ -179,7 +179,7 @@ let execute (state: AppState) (args: List): AppState = let suffix = if withPurity then $" {purityTag fn.purity}" else "" - Stdlib.printLine + Stdlib.println $" {fn.name.name}{versionSuffix fn.name.version}({params}) -> {returnTypeStr}{suffix}")) else // Compact output: comma-separated names @@ -196,8 +196,8 @@ let execute (state: AppState) (args: List): AppState = $"{fn.name.name}{versionSuffix fn.name.version}{suffix}") |> Stdlib.String.join ", " - Stdlib.printLine "" - Stdlib.printLine $"{Colors.colorize Colors.bold (getDisplayName group)}: {names}") + Stdlib.println "" + Stdlib.println $"{Colors.colorize Colors.bold (getDisplayName group)}: {names}") let totalCount = filteredGroups @@ -215,7 +215,7 @@ let execute (state: AppState) (args: List): AppState = |> Stdlib.printLines if Stdlib.Bool.not withSigs then - Stdlib.printLine (Colors.hint "Use --with-sigs to see full function signatures, --with-purity to tag pure/impure") + Stdlib.println (Colors.hint "Use --with-sigs to see full function signatures, --with-purity to tag pure/impure") state diff --git a/packages/darklang/cli/caps/app.dark b/packages/darklang/cli/caps/app.dark index 340b0fce1c..0a956ce045 100644 --- a/packages/darklang/cli/caps/app.dark +++ b/packages/darklang/cli/caps/app.dark @@ -80,7 +80,7 @@ let execute (cliState: Darklang.Cli.AppState) (_args: List) : Darklang.C { cliState with currentPage = Darklang.Cli.Page.SubApp (makeSubApp session) } | Error message -> - Stdlib.printLine message + Stdlib.println message cliState let help (state: Darklang.Cli.AppState) : Darklang.Cli.AppState = diff --git a/packages/darklang/cli/caps/command.dark b/packages/darklang/cli/caps/command.dark index e826547a60..658659c0ba 100644 --- a/packages/darklang/cli/caps/command.dark +++ b/packages/darklang/cli/caps/command.dark @@ -100,16 +100,16 @@ let revokeDomain (domain: String) : Bool = let show (state: Cli.AppState) : Cli.AppState = let specs = currentSpecs () - Stdlib.printLine "Capabilities (this instance grants):" + Stdlib.println "Capabilities (this instance grants):" if Stdlib.List.isEmpty specs then - Stdlib.printLine (Colors.dimText " (none — NONE is the default)") + Stdlib.println (Colors.dimText " (none — NONE is the default)") else Stdlib.printLines ((PrettyPrinter.Capabilities.lines specs) |> Stdlib.List.map (fun l -> " " ++ l)) - Stdlib.printLine "" - Stdlib.printLine + Stdlib.println "" + Stdlib.println (Colors.dimText " adjust: `caps grant http-client GET` · `caps revoke ` · `caps grant-for-fn `") - Stdlib.printLine + Stdlib.println (Colors.dimText " replace: `caps set ; …` · `caps clear` · `caps edit` (TUI)") state @@ -117,41 +117,41 @@ let show (state: Cli.AppState) : Cli.AppState = // Show what a fn (transitively) needs — the shared nice rendering (used by `caps needed-for `). let showFnNeeds (state: Cli.AppState) (fnName: String) : Unit = match Cli.Packages.Location.parseRelativeTo state.packageData.currentLocation fnName with - | Error e -> Stdlib.printLine (Colors.error e) + | Error e -> Stdlib.println (Colors.error e) | Ok location -> match LanguageTools.PackageManager.findAny state.currentBranchId location with | Some((hash, Fn)) -> let specs = effectiveCapsForHash (LanguageTools.ProgramTypes.hashToString hash) if Stdlib.List.isEmpty specs then - Stdlib.printLine (Colors.success $"{fnName} ✓ pure — needs no capabilities") + Stdlib.println (Colors.success $"{fnName} ✓ pure — needs no capabilities") else - Stdlib.printLine $"{fnName} needs:" + Stdlib.println $"{fnName} needs:" Stdlib.printLines ((PrettyPrinter.Capabilities.lines specs) |> Stdlib.List.map (fun l -> " " ++ l)) - Stdlib.printLine + Stdlib.println (Colors.dimText $" grant them all with `caps grant-for-fn {fnName}`") - | Some((_, _)) -> Stdlib.printLine (Colors.error "caps analysis is for functions") - | None -> Stdlib.printLine (Colors.error $"'{fnName}' not found at this location") + | Some((_, _)) -> Stdlib.println (Colors.error "caps analysis is for functions") + | None -> Stdlib.println (Colors.error $"'{fnName}' not found at this location") // `caps grant-for-fn ` — grant exactly the capabilities a fn (transitively) needs. let grantForFn (state: Cli.AppState) (fnName: String) : Cli.AppState = match Cli.Packages.Location.parseRelativeTo state.packageData.currentLocation fnName with - | Error e -> Stdlib.printLine (Colors.error e) + | Error e -> Stdlib.println (Colors.error e) | Ok location -> match LanguageTools.PackageManager.findAny state.currentBranchId location with | Some((hash, Fn)) -> let specs = effectiveCapsForHash (LanguageTools.ProgramTypes.hashToString hash) if Stdlib.List.isEmpty specs then - Stdlib.printLine (Colors.dimText $"{fnName} needs no capabilities (pure) — nothing to grant") + Stdlib.println (Colors.dimText $"{fnName} needs no capabilities (pure) — nothing to grant") else Stdlib.List.iter specs (fun spec -> match Command.grantSpec spec with - | Ok() -> Stdlib.printLine (Colors.success $"✓ granted {spec}") - | Error bad -> Stdlib.printLine (Colors.error $"couldn't grant '{bad}'")) - | Some((_, _)) -> Stdlib.printLine (Colors.error "caps analysis is for functions") - | None -> Stdlib.printLine (Colors.error $"'{fnName}' not found at this location") + | Ok() -> Stdlib.println (Colors.success $"✓ granted {spec}") + | Error bad -> Stdlib.println (Colors.error $"couldn't grant '{bad}'")) + | Some((_, _)) -> Stdlib.println (Colors.error "caps analysis is for functions") + | None -> Stdlib.println (Colors.error $"'{fnName}' not found at this location") state @@ -166,9 +166,9 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | "grant" :: specWords -> let spec = Stdlib.String.join specWords " " match Command.grantSpec spec with - | Ok() -> Stdlib.printLine (Colors.success $"✓ granted {spec}") + | Ok() -> Stdlib.println (Colors.success $"✓ granted {spec}") | Error bad -> - Stdlib.printLine + Stdlib.println (Colors.error $"couldn't parse '{bad}' — see `caps help` for the grammar") state @@ -181,22 +181,22 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = |> Stdlib.List.filter (fun s -> s != "") if Stdlib.List.isEmpty specs then // an empty set would silently wipe the grant — make the user say `caps clear` for that - Stdlib.printLine (Colors.error "usage: caps set ; … — to revoke everything use `caps clear`") + Stdlib.println (Colors.error "usage: caps set ; … — to revoke everything use `caps clear`") state else match writeSpecs specs with | None -> - Stdlib.printLine (Colors.success "✓ capabilities set") + Stdlib.println (Colors.success "✓ capabilities set") Command.show state | Some badSpec -> - Stdlib.printLine + Stdlib.println (Colors.error $"couldn't parse '{badSpec}' — nothing changed. See `caps help` for the grammar.") state // `caps clear` — REPLACE the grant with NONE (revoke everything). | [ "clear" ] | [ "none" ] -> let _ = writeSpecs [] - Stdlib.printLine (Colors.success "✓ cleared — all capabilities revoked (NONE)") + Stdlib.println (Colors.success "✓ cleared — all capabilities revoked (NONE)") state // `caps grant-all` — REPLACE the grant with everything (unrestricted). The one-command "let it all @@ -205,28 +205,28 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = // `LanguageTools.Capabilities.all`. It's a convenience, not a security control. | [ "grant-all" ] | [ "allow-all" ] -> Command.writeCaps (LanguageTools.Capabilities.all ()) - Stdlib.printLine (Colors.success "✓ granted ALL capabilities — this instance is now unrestricted") + Stdlib.println (Colors.success "✓ granted ALL capabilities — this instance is now unrestricted") Command.show state | [ "revoke"; domain ] -> if Command.revokeDomain domain then - Stdlib.printLine (Colors.success $"✓ revoked {domain}") + Stdlib.println (Colors.success $"✓ revoked {domain}") else - Stdlib.printLine (Colors.dimText $"'{domain}' was not granted (nothing to revoke)") + Stdlib.println (Colors.dimText $"'{domain}' was not granted (nothing to revoke)") state | [ "grant-for-fn"; fnName ] -> Command.grantForFn state fnName | [ "profile" ] -> - Stdlib.printLine + Stdlib.println "Capability profiles — `caps profile ` REPLACES the grant with that posture:" Stdlib.List.iter Command.profiles (fun (name, specs) -> let label = match Stdlib.String.padEnd name " " 12 with | Ok r -> r | Error _ -> name - Stdlib.printLine $" {label}{Colors.dimText (PrettyPrinter.Capabilities.compact specs)}") - Stdlib.printLine + Stdlib.println $" {label}{Colors.dimText (PrettyPrinter.Capabilities.compact specs)}") + Stdlib.println (Colors.dimText " (to layer one on top of your grant, apply it then `caps grant …` the extras)") state @@ -234,15 +234,15 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | [ "profile"; name ] -> match Stdlib.List.findFirst Command.profiles (fun (n, _) -> n == name) with | None -> - Stdlib.printLine (Colors.error $"no profile '{name}' — see `caps profile`") + Stdlib.println (Colors.error $"no profile '{name}' — see `caps profile`") state | Some((_, specs)) -> match writeSpecs specs with | None -> - Stdlib.printLine (Colors.success $"✓ capabilities set to profile '{name}'") + Stdlib.println (Colors.success $"✓ capabilities set to profile '{name}'") Command.show state | Some badSpec -> - Stdlib.printLine + Stdlib.println (Colors.error $"profile '{name}' has a spec that didn't parse ('{badSpec}') — nothing changed") state @@ -252,7 +252,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = state | _ -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: caps [list] | grant | set … | clear | grant-all | revoke | grant-for-fn | needed-for ") state diff --git a/packages/darklang/cli/caps/main.dark b/packages/darklang/cli/caps/main.dark index 08a5eb8f69..b6d4de20db 100644 --- a/packages/darklang/cli/caps/main.dark +++ b/packages/darklang/cli/caps/main.dark @@ -50,13 +50,13 @@ let load () : State = dirty = false screen = Screen.Browsing } -let isFlag (domain: String) : Bool = Stdlib.List.member flagDomains domain +let isFlag (domain: String) : Bool = Stdlib.List.contains flagDomains domain // the visible rows: the flag checklist, then one row per scoped/coupled rule spec let rows (state: State) : List = let flagRows = Stdlib.List.map flagDomains (fun d -> - let granted = Stdlib.List.member state.specs d + let granted = Stdlib.List.contains state.specs d Row.FlagRow(d, granted)) let ruleRows = state.specs @@ -69,7 +69,7 @@ let rows (state: State) : List = let rowCount (state: State) : Int = (Stdlib.List.length (rows state)) let toggleFlag (state: State) (domain: String) : State = - if Stdlib.List.member state.specs domain then + if Stdlib.List.contains state.specs domain then let next = Stdlib.List.filter state.specs (fun s -> s != domain) { state with specs = next; dirty = true } else diff --git a/packages/darklang/cli/component.dark b/packages/darklang/cli/component.dark index d1573baf45..9fb4f3315b 100644 --- a/packages/darklang/cli/component.dark +++ b/packages/darklang/cli/component.dark @@ -73,5 +73,5 @@ let launch (cliState: Cli.AppState) (c: Component<'s>) (init: 's) : Cli.AppState { cliState with currentPage = Cli.Page.SubApp (toSubApp c init terminal) } | Error message -> - Stdlib.printLine message + Stdlib.println message cliState diff --git a/packages/darklang/cli/config.dark b/packages/darklang/cli/config.dark index 63168de8ae..953abe35f2 100644 --- a/packages/darklang/cli/config.dark +++ b/packages/darklang/cli/config.dark @@ -52,18 +52,18 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = let config = readConfig () match Stdlib.Dict.get config key with | Some value -> - Stdlib.printLine $"{key} = {value}" + Stdlib.println $"{key} = {value}" state | None -> - Stdlib.printLine (Colors.error $"Configuration key not found: {key}") + Stdlib.println (Colors.error $"Configuration key not found: {key}") state | ["set"; key; value] -> let config = readConfig () let updatedConfig = Stdlib.Dict.setOverridingDuplicates config key value writeConfig updatedConfig - Stdlib.printLine (Colors.success $"Set {key} = {value}") + Stdlib.println (Colors.success $"Set {key} = {value}") // Show the implied local URL for well-formed app port settings. match Stdlib.String.split key "." with @@ -71,7 +71,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match Stdlib.Int.parse value with | Ok port -> if port >= 1 && port <= 65535 then - Stdlib.printLine (Colors.dimText $" → http://localhost:{value}") + Stdlib.println (Colors.dimText $" → http://localhost:{value}") else () | Error _ -> () @@ -93,8 +93,8 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = state | _ -> - Stdlib.printLine (Colors.error "Invalid arguments") - Stdlib.printLine "" + Stdlib.println (Colors.error "Invalid arguments") + Stdlib.println "" help state state diff --git a/packages/darklang/cli/conflicts.dark b/packages/darklang/cli/conflicts.dark index f73e2f2342..6891b0ebc9 100644 --- a/packages/darklang/cli/conflicts.dark +++ b/packages/darklang/cli/conflicts.dark @@ -48,14 +48,14 @@ let resolveCmd match openAt location with | Some c -> if act c then - Stdlib.printLine (Colors.success (okMsg c)) + Stdlib.println (Colors.success (okMsg c)) else - Stdlib.printLine (Colors.error (noneMsg location)) - | None -> Stdlib.printLine (Colors.error (noneMsg location)) + Stdlib.println (Colors.error (noneMsg location)) + | None -> Stdlib.println (Colors.error (noneMsg location)) state | _ -> - Stdlib.printLine (Colors.error $"usage: dark conflicts {verb} ") + Stdlib.println (Colors.error $"usage: dark conflicts {verb} ") state @@ -146,25 +146,25 @@ let printOpenList : AppState = let n = Stdlib.List.length cs let noun = if n == 1 then "conflict" else "conflicts" - Stdlib.printLine $"{Stdlib.Int.toString n} unreviewed {noun}:" + Stdlib.println $"{Stdlib.Int.toString n} unreviewed {noun}:" let branchId = state.currentBranchId Stdlib.List.iter cs (fun c -> let yoursV = renderInline branchId c.location c.itemKind c.localHash let theirsV = renderInline branchId c.location c.itemKind c.incomingHash let activeSide = if c.chosenHash == c.incomingHash then "theirs" else "yours" - Stdlib.printLine "" - Stdlib.printLine $" {c.location} ({c.itemKind})" - Stdlib.printLine (Colors.hint $" yours: {yoursV}") - Stdlib.printLine (Colors.hint $" theirs: {theirsV}") - Stdlib.printLine (Colors.hint $" active: {activeSide} ({c.resolvedBy})")) - - Stdlib.printLine "" - Stdlib.printLine (Colors.hint " What you can do:") - Stdlib.printLine (Colors.hint " dark conflicts show see both versions in full") - Stdlib.printLine (Colors.hint " dark conflicts keep-mine keep yours (syncs to peers)") - Stdlib.printLine (Colors.hint " dark conflicts keep-theirs keep theirs (syncs to peers)") - Stdlib.printLine (Colors.hint " dark conflicts ok accept the auto pick, review done") + Stdlib.println "" + Stdlib.println $" {c.location} ({c.itemKind})" + Stdlib.println (Colors.hint $" yours: {yoursV}") + Stdlib.println (Colors.hint $" theirs: {theirsV}") + Stdlib.println (Colors.hint $" active: {activeSide} ({c.resolvedBy})")) + + Stdlib.println "" + Stdlib.println (Colors.hint " What you can do:") + Stdlib.println (Colors.hint " dark conflicts show see both versions in full") + Stdlib.println (Colors.hint " dark conflicts keep-mine keep yours (syncs to peers)") + Stdlib.println (Colors.hint " dark conflicts keep-theirs keep theirs (syncs to peers)") + Stdlib.println (Colors.hint " dark conflicts ok accept the auto pick, review done") state // Step through each conflict: show both sides, pick mine / theirs / skip. Shown for a bare `dark conflicts` @@ -178,17 +178,17 @@ let resolveInteractively let branchId = state.currentBranchId let n = Stdlib.List.length cs let noun = if n == 1 then "conflict" else "conflicts" - Stdlib.printLine $"{Stdlib.Int.toString n} {noun} to review — pick a side for each (or skip):" + Stdlib.println $"{Stdlib.Int.toString n} {noun} to review — pick a side for each (or skip):" Stdlib.List.iter cs (fun c -> let yoursActive = if c.chosenHash == c.localHash then " (auto pick)" else "" let theirsActive = if c.chosenHash == c.incomingHash then " (auto pick)" else "" - Stdlib.printLine "" - Stdlib.printLine $" {c.location} ({c.itemKind})" - Stdlib.printLine $" yours {short c.localHash}{yoursActive}" - Stdlib.printLine (renderContent branchId c.location c.itemKind c.localHash) - Stdlib.printLine $" theirs {short c.incomingHash}{theirsActive}" - Stdlib.printLine (renderContent branchId c.location c.itemKind c.incomingHash) + Stdlib.println "" + Stdlib.println $" {c.location} ({c.itemKind})" + Stdlib.println $" yours {short c.localHash}{yoursActive}" + Stdlib.println (renderContent branchId c.location c.itemKind c.localHash) + Stdlib.println $" theirs {short c.incomingHash}{theirsActive}" + Stdlib.println (renderContent branchId c.location c.itemKind c.incomingHash) let choice = Stdlib.Cli.UI.Prompt.select @@ -198,33 +198,33 @@ let resolveInteractively match choice with | "mine" -> if Darklang.Sync.Conflicts.keep c.location c.localHash then - Stdlib.printLine (Colors.success " kept yours — syncs to peers") + Stdlib.println (Colors.success " kept yours — syncs to peers") else - Stdlib.printLine (Colors.error " couldn't apply — still open") + Stdlib.println (Colors.error " couldn't apply — still open") | "theirs" -> if Darklang.Sync.Conflicts.keep c.location c.incomingHash then - Stdlib.printLine (Colors.success " kept theirs — syncs to peers") + Stdlib.println (Colors.success " kept theirs — syncs to peers") else - Stdlib.printLine (Colors.error " couldn't apply — still open") - | _ -> Stdlib.printLine (Colors.hint " skipped — still open")) + Stdlib.println (Colors.error " couldn't apply — still open") + | _ -> Stdlib.println (Colors.hint " skipped — still open")) - Stdlib.printLine "" - Stdlib.printLine (Colors.hint "Run `dark sync` to share your picks with peers.") + Stdlib.println "" + Stdlib.println (Colors.hint "Run `dark sync` to share your picks with peers.") state let help (state: AppState) : AppState = - Stdlib.printLine "dark conflicts — review + resolve sync divergences" - Stdlib.printLine "" - Stdlib.printLine " dark conflicts review them one at a time (a plain list if piped)" - Stdlib.printLine " dark conflicts list just print the unreviewed conflicts" - Stdlib.printLine " dark conflicts show the two candidate contents" - Stdlib.printLine " dark conflicts ok acknowledge (the auto choice stands)" - Stdlib.printLine " dark conflicts keep-mine override: keep your version (syncs)" - Stdlib.printLine " dark conflicts keep-theirs override: keep their version (syncs)" - Stdlib.printLine "" - Stdlib.printLine + Stdlib.println "dark conflicts — review + resolve sync divergences" + Stdlib.println "" + Stdlib.println " dark conflicts review them one at a time (a plain list if piped)" + Stdlib.println " dark conflicts list just print the unreviewed conflicts" + Stdlib.println " dark conflicts show the two candidate contents" + Stdlib.println " dark conflicts ok acknowledge (the auto choice stands)" + Stdlib.println " dark conflicts keep-mine override: keep your version (syncs)" + Stdlib.println " dark conflicts keep-theirs override: keep their version (syncs)" + Stdlib.println "" + Stdlib.println " `ok` reviews it HERE only — the auto pick stands and nothing tells your peers." - Stdlib.printLine + Stdlib.println " `keep-mine`/`keep-theirs` mint a synced Resolution, so the conflict clears on peers too." state @@ -233,7 +233,7 @@ let execute (state: AppState) (args: List) : AppState = | [] -> match unreviewed () with | [] -> - Stdlib.printLine (Colors.success "No conflicts — everything's converged.") + Stdlib.println (Colors.success "No conflicts — everything's converged.") state // Bare `dark conflicts` in a session walks you through each; one-shot/piped prints the list to act on. | cs -> @@ -242,7 +242,7 @@ let execute (state: AppState) (args: List) : AppState = | [ "list" ] -> match unreviewed () with | [] -> - Stdlib.printLine (Colors.success "No conflicts — everything's converged.") + Stdlib.println (Colors.success "No conflicts — everything's converged.") state | cs -> printOpenList state cs @@ -255,24 +255,24 @@ let execute (state: AppState) (args: List) : AppState = let theirsActive = if c.chosenHash == c.incomingHash then " (active — auto:last-writer-wins)" else "" - Stdlib.printLine $"{c.location} — two versions ({c.itemKind}):" - Stdlib.printLine "" - Stdlib.printLine $" yours {short c.localHash}{yoursActive}" - Stdlib.printLine (renderContent branchId c.location c.itemKind c.localHash) - Stdlib.printLine "" - Stdlib.printLine $" theirs {short c.incomingHash}{theirsActive}" - Stdlib.printLine (renderContent branchId c.location c.itemKind c.incomingHash) - Stdlib.printLine "" + Stdlib.println $"{c.location} — two versions ({c.itemKind}):" + Stdlib.println "" + Stdlib.println $" yours {short c.localHash}{yoursActive}" + Stdlib.println (renderContent branchId c.location c.itemKind c.localHash) + Stdlib.println "" + Stdlib.println $" theirs {short c.incomingHash}{theirsActive}" + Stdlib.println (renderContent branchId c.location c.itemKind c.incomingHash) + Stdlib.println "" - Stdlib.printLine ( + Stdlib.println ( Colors.hint $" keep yours: dark conflicts keep-mine {location}") - Stdlib.printLine ( + Stdlib.println ( Colors.hint $" keep theirs: dark conflicts keep-theirs {location}") state | None -> - Stdlib.printLine (Colors.error (noneMsg location)) + Stdlib.println (Colors.error (noneMsg location)) state | "ok" :: rest -> diff --git a/packages/darklang/cli/core.dark b/packages/darklang/cli/core.dark index cd6a0377a5..5703a45c1a 100644 --- a/packages/darklang/cli/core.dark +++ b/packages/darklang/cli/core.dark @@ -106,7 +106,7 @@ let resolveBranchName (name: String) (source: String) : Uuid = match SCM.Branch.getByName name with | Some b -> b.id | None -> - Stdlib.printLine + Stdlib.println (Colors.error $"Warning: {source} '{name}' not found, using main.") SCM.Branch.mainBranchId diff --git a/packages/darklang/cli/deps.dark b/packages/darklang/cli/deps.dark index 4ca64378be..53c9c0bfec 100644 --- a/packages/darklang/cli/deps.dark +++ b/packages/darklang/cli/deps.dark @@ -35,17 +35,17 @@ let showDependents match dependents with | [] -> - Stdlib.printLine $"No dependents found for {entityName}" + Stdlib.println $"No dependents found for {entityName}" | deps -> let count = Stdlib.List.length deps - Stdlib.printLine $"Found {Stdlib.Int.toString count} dependents of {entityName}:" - Stdlib.printLine "" + Stdlib.println $"Found {Stdlib.Int.toString count} dependents of {entityName}:" + Stdlib.println "" deps |> Stdlib.List.iter (fun (_sourceHash, sourceLoc, refType) -> let name = PrettyPrinter.ProgramTypes.PackageLocation.packageLocation sourceLoc let kind = Darklang.Cli.Packages.Propagate.itemKindToString refType - Stdlib.printLine $" [{kind}] {name}") + Stdlib.println $" [{kind}] {name}") /// Helper for transitive traversal — collects dependents via batched @@ -66,7 +66,7 @@ let collectTransitiveDependents pending |> Stdlib.List.filter (fun (loc, kind) -> let key = $"{fqn loc}:{Darklang.Cli.Packages.Propagate.itemKindToString kind}" - Stdlib.Bool.not (Stdlib.List.member processed key)) + Stdlib.Bool.not (Stdlib.List.contains processed key)) match toProcess with | [] -> accumulated @@ -118,17 +118,17 @@ let showTransitiveDependents match allDependents with | [] -> - Stdlib.printLine $"Nothing depends on {entityName}" + Stdlib.println $"Nothing depends on {entityName}" | deps -> let count = Stdlib.List.length deps - Stdlib.printLine $"Found {Stdlib.Int.toString count} items that could break if {entityName} changes:" - Stdlib.printLine "" + Stdlib.println $"Found {Stdlib.Int.toString count} items that could break if {entityName} changes:" + Stdlib.println "" deps |> Stdlib.List.iter (fun (sourceLoc, refType) -> let name = PrettyPrinter.ProgramTypes.PackageLocation.packageLocation sourceLoc let kind = Darklang.Cli.Packages.Propagate.itemKindToString refType - Stdlib.printLine $" [{kind}] {name}") + Stdlib.println $" [{kind}] {name}") /// Display dependencies (what this entity uses) @@ -141,11 +141,11 @@ let showDependencies match dependencies with | [] -> - Stdlib.printLine $"No dependencies found for {entityName}" + Stdlib.println $"No dependencies found for {entityName}" | deps -> let count = Stdlib.List.length deps - Stdlib.printLine $"Found {Stdlib.Int.toString count} dependencies of {entityName}:" - Stdlib.printLine "" + Stdlib.println $"Found {Stdlib.Int.toString count} dependencies of {entityName}:" + Stdlib.println "" let hashes = deps |> Stdlib.List.map (fun (hash, _) -> hash) let namesDict = resolveNames branchId hashes @@ -153,7 +153,7 @@ let showDependencies deps |> Stdlib.List.iter (fun (targetHash, refType) -> let name = getName namesDict targetHash - Stdlib.printLine $" [{refType}] {name}") + Stdlib.println $" [{refType}] {name}") /// Resolve a path to (hash, location, kind, displayName). @@ -192,7 +192,7 @@ let resolvePath let execute (state: AppState) (args: List) : AppState = // Check for --deep flag - let deep = Stdlib.List.member args "--deep" + let deep = Stdlib.List.contains args "--deep" let filteredArgs = args |> Stdlib.List.filter (fun arg -> arg != "--deep") let branchId = state.currentBranchId @@ -212,7 +212,7 @@ let execute (state: AppState) (args: List) : AppState = | ["uses"; pathArg] -> match resolvePath branchId state.packageData.currentLocation pathArg with | Error errMsg -> - Stdlib.printLine (Colors.error errMsg) + Stdlib.println (Colors.error errMsg) state | Ok result -> let (hash, _loc, name, _entityType) = result @@ -222,7 +222,7 @@ let execute (state: AppState) (args: List) : AppState = | ["usedby"; pathArg] -> match resolvePath branchId state.packageData.currentLocation pathArg with | Error errMsg -> - Stdlib.printLine (Colors.error errMsg) + Stdlib.println (Colors.error errMsg) state | Ok result -> let (_hash, loc, name, entityType) = result @@ -238,12 +238,12 @@ let execute (state: AppState) (args: List) : AppState = // Default to showing both uses and usedby match resolvePath branchId state.packageData.currentLocation pathArg with | Error errMsg -> - Stdlib.printLine (Colors.error errMsg) + Stdlib.println (Colors.error errMsg) state | Ok result -> let (hash, loc, name, entityType) = result showDependencies branchId hash name - Stdlib.printLine "" + Stdlib.println "" if deep then showTransitiveDependents branchId loc entityType name diff --git a/packages/darklang/cli/devices.dark b/packages/darklang/cli/devices.dark index 00cece1d4c..001ba9b424 100644 --- a/packages/darklang/cli/devices.dark +++ b/packages/darklang/cli/devices.dark @@ -7,16 +7,16 @@ module Darklang.Cli.Devices // take next. `devices status` is the clean, pipeable form (just the list). let showStatus () : Unit = match Darklang.Tailscale.status () with - | Ok out -> Stdlib.printLine out - | Error e -> Stdlib.printLine (Colors.error $"devices: status failed: {e}") + | Ok out -> Stdlib.println out + | Error e -> Stdlib.println (Colors.error $"devices: status failed: {e}") let execute (state: AppState) (args: List) : AppState = match args with | [] -> // interactive landing — what's on the tailnet + what you can do showStatus () - Stdlib.printLine "" - Stdlib.printLine (Colors.hint "actions: devices serve devices ping ") + Stdlib.println "" + Stdlib.println (Colors.hint "actions: devices serve devices ping ") state | [ "status" ] -> @@ -28,20 +28,20 @@ let execute (state: AppState) (args: List) : AppState = | Ok port -> match Darklang.Tailscale.serve port with | Ok() -> - Stdlib.printLine + Stdlib.println (Colors.success $"serving local :{portStr} over the tailnet (TLS on 443)") - | Error e -> Stdlib.printLine (Colors.error $"devices: serve failed: {e}") - | Error _ -> Stdlib.printLine (Colors.error "Usage: devices serve ") + | Error e -> Stdlib.println (Colors.error $"devices: serve failed: {e}") + | Error _ -> Stdlib.println (Colors.error "Usage: devices serve ") state | [ "ping"; peer ] -> match Darklang.Tailscale.ping peer with - | Ok out -> Stdlib.printLine out - | Error e -> Stdlib.printLine (Colors.error $"devices: ping failed: {e}") + | Ok out -> Stdlib.println out + | Error e -> Stdlib.println (Colors.error $"devices: ping failed: {e}") state | _ -> - Stdlib.printLine "Usage: devices [status] | serve | ping " + Stdlib.println "Usage: devices [status] | serve | ping " state diff --git a/packages/darklang/cli/docs/command.dark b/packages/darklang/cli/docs/command.dark index b3dac3328f..40652415ee 100644 --- a/packages/darklang/cli/docs/command.dark +++ b/packages/darklang/cli/docs/command.dark @@ -256,7 +256,7 @@ let dispatchLive let execute (state: Cli.AppState) (args: List) : Cli.AppState = match args with | [] -> - Stdlib.printLine (Topics.formatTopicList ()) + Stdlib.println (Topics.formatTopicList ()) state | topicName :: rest -> @@ -266,12 +266,12 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match Topics.findTopic topicName with | Some topic -> let contentFn = topic.content - Stdlib.printLine (contentFn ()) + Stdlib.println (contentFn ()) state | None -> - Stdlib.printLine (Colors.error $"Unknown topic: {topicName}") - Stdlib.printLine "" - Stdlib.printLine (Topics.formatTopicList ()) + Stdlib.println (Colors.error $"Unknown topic: {topicName}") + Stdlib.println "" + Stdlib.println (Topics.formatTopicList ()) state diff --git a/packages/darklang/cli/docs/enums.dark b/packages/darklang/cli/docs/enums.dark index d78f61002d..13dbd35a2b 100644 --- a/packages/darklang/cli/docs/enums.dark +++ b/packages/darklang/cli/docs/enums.dark @@ -6,11 +6,12 @@ let content () : String = ## Define type Status = | Pending | Done of Int -## Construct (need type prefix) +## Construct Status.Pending Status.Done 42 - Option.Some 5 - Result.Ok "yes" + Some 5 # Option/Result are prelude constructors + Ok "yes" + MyType.Case value # other enums need the type prefix ## Match (no type prefix) match status with @@ -21,7 +22,9 @@ let content () : String = | Some v -> v | None -> default -## Gotcha: Some with tuple +## Gotcha: a tuple inside a constructor needs DOUBLE parens match x with - | Some ((a, b)) -> ... # double parens! - | None -> ...""" + | Some((a, b)) -> ... # correct: ONE field that is a tuple + | None -> ... + # `Some (a, b)` (single parens) is read as a TWO-field Some and rejected. + # `Ctor(a, b)` always means separate fields, so double-paren the tuple.""" diff --git a/packages/darklang/cli/docs/errors.dark b/packages/darklang/cli/docs/errors.dark index afa4d490da..5039ee8501 100644 --- a/packages/darklang/cli/docs/errors.dark +++ b/packages/darklang/cli/docs/errors.dark @@ -13,12 +13,14 @@ let content () : String = ## Type mismatch - Wrong literal suffix (5 vs 5.0) - Param order wrong - - Missing Option.Some wrapper + - Missing `Some` wrapper - Missing list wrapper [x] ## "Function not found" - Use full path: Stdlib.List.map - Check spelling/case + - Never prefix a source name with `PACKAGE.`; remove it. Prefer `Stdlib.*` + for standard-library names. ## Operator errors - Numeric operators require matching numeric operand types @@ -26,11 +28,12 @@ let content () : String = - List concat: use Stdlib.List.append ## Enum errors - - Constructing: need TypeName.Case + - Option/Result expressions use bare `Some`, `None`, `Ok`, and `Error` + - Other enum expressions use `TypeName.Case` - Matching: just Case (no prefix) ## Some + tuple - - Use Some ((a, b)) with double parens + - Use `Some((a, b))` with double parens and no space ## Debug 1. Simplify expression diff --git a/packages/darklang/cli/docs/for-ai.dark b/packages/darklang/cli/docs/for-ai.dark index 84c3822eea..f96aacd058 100644 --- a/packages/darklang/cli/docs/for-ai.dark +++ b/packages/darklang/cli/docs/for-ai.dark @@ -3,178 +3,64 @@ module Darklang.Cli.Docs.ForAI let content () : String = """# Darklang AI Quick Reference -## IMPORTANT: Darklang is Different -Darklang is a LIVE PROGRAMMING ENVIRONMENT (like Smalltalk). -You don't write scripts - you build a persistent package tree. -- Use `fn` to CREATE functions in the tree -- Use `eval` to TEST those functions -- Use `commit` to SAVE your work -- CLI state doesn't persist between calls - the PACKAGE TREE does +Darklang is a persistent package tree, not a directory of scripts. CLI process +state resets between calls; package changes persist. ## Workflow - 1. fn myFn # create function (stored in package tree) - 2. eval myFn # test it - 3. status # see changes - 4. commit "msg" # save to SCM - -## First Commands - tree # see package structure - docs stdlib # list top-level Stdlib modules - docs signatures # signatures for one module - builtins # list F# builtins (lower-level than stdlib) - search # find anything - view # inspect entity (full source + docs) - -## Finding a function - search # ranked hits across all modules (best matches + - # Stdlib first), with parameter names, e.g. - # `List.map (l: List<'a>) (fn: 'a -> 'b) -> List<'b>` - search --with-docs # also show doc comments - search --fn # functions only (also --type, --val) - search --exact # exact name match (default is fuzzy) - docs signatures # browse every signature in a module you already know - view # full source + docs for one entity - -`search` matches the term against names and (for multi-word or 3+ char queries) -doc comments, so a concept usually resolves even when the name differs: `search -map`, `search toList`, `search modulo` (finds `mod`). Operator symbols map to -their operation: `search %` -> `mod`, `search ++` -> concat, `search /` -> -`divide`. 1-2 searches is usually enough; drill into the module if unsure. - -## Creating Code (use these, not eval!) - fn # create function - MAIN WAY TO WRITE CODE - type # create type - val # create value - -Names need full paths (owner.module.name), e.g.: - fn Darklang.Testing.fib (n: Int): Int = ... -Inside the body, use short names (e.g., `fib` for recursion). - -For multi-line bodies, use `-` and pipe via stdin (heredoc form): - ./scripts/run-cli fn Darklang.Foo.bar - <<'EOF' - (n: Int): Int = - let x = n + 1 - x * 2 + + search --batch "file read" "json parse" --fn # compact API discovery + module /Darklang.Example - # declarations from stdin + eval Darklang.Example.main "arg" # test an expression + status # inspect changes + commit "message" -y # save them + +Batch search returns two ranked signatures and a one-line doc per query. Exact +qualified matches return one. Use `--all` to expand an ambiguous query, +`--with-docs` for full comments, `docs signatures ` to browse a known +module, and `view ` for source. + +## Authoring + +Create several declarations atomically: + + module /Darklang.Example - <<'EOF' + type State = { count: Int } + let double (n: Int) : Int = n * 2 + let main (n: Int) : Int = double n + EOF + +For one function, stdin accepts ordinary source (the shorter signature/body +fragment is also valid): + + fn /Darklang.Example.double - <<'EOF' + let double (n: Int) : Int = n * 2 EOF -## Running/Testing - eval [args] # call a function / evaluate an expression - run # execute a .dark script file (not for calling fns) - run --sandbox # ...with NO capabilities (deny-all, for untrusted scripts) - -## Capabilities (effectful operations may be gated) -Effectful builtins — http, filesystem, subprocess, env vars, datastores, clock, randomness, llm — run -under a per-instance capability GRANT. A call the grant doesn't cover fails with `capability denied: …`. -Pure code never needs a grant. A fresh instance is permissive; once you configure a grant, both `eval` -and `run` obey it. - - caps # show the current grant - caps needed-for # what a fn (transitively) needs to run - caps grant # allow one — e.g. `caps grant http-client GET`, `caps grant file read`, `caps grant llm` - caps grant-for-fn # grant exactly what a fn needs - caps grant-all # remove ALL restrictions - caps clear # revoke everything (NONE) - -If a capability denial is blocking you and you trust this environment, run `caps grant-all` to proceed. - -## Navigation - nav # go to module (cd) - ls / back # list / go back - -## SCM (version control) - status # see current branch + uncommitted changes - commit [-y] # commit changes; -y/--yes skips the y/n prompt - log # history - discard # undo uncommitted changes - -`commit` records who you are, so log in first: `login ` (run `login` with no name to -list accounts). The account persists across runs. For non-interactive use, log in once, then -`./scripts/run-cli commit "msg" -y`. - -## Deprecation (mark items as unused / dangerous / obsolete) - deprecate --kind [options] - kinds: superseded-by (requires --replacement), harmful, obsolete - delete [options] # sugar for --kind obsolete - options: --ignore-dependents; --force = --ignore-dependents + --yes - undo # reverse a WIP deprecate - eval --allow-harmful # opt out of harmful halt - (see: docs deprecation) - -## Branches (use --branch flag!) -Branch context does NOT persist between CLI invocations. -Always pass --branch when working on non-main branches: - - dark branch create feature - dark --branch feature fn "Darklang.Math.fib ..." - dark --branch feature status - dark --branch feature commit "done" - dark --branch feature rebase - dark --branch feature merge - -Without --branch, commands run on main. - (see: docs scm) - -## Name Resolution -Names resolve relative to the current module, searching from -most-specific to least-specific. - -In `module Darklang.Stdlib.List`, writing `Option.Option` tries: - 1. Darklang.Stdlib.List.Option.Option - 2. Darklang.Stdlib.Option.Option <-- found here - 3. Darklang.Option.Option - 4. Option.Option - -Rules: - - Same module: no qualifier needed - (in Darklang.Stdlib.List, `map` finds Darklang.Stdlib.List.map) - - Parent/sibling modules: use relative name - (in Darklang.Stdlib.List, `Option.Option` finds - Darklang.Stdlib.Option.Option) - - `Stdlib.X` shortcut: always expands to `Darklang.Stdlib.X` - (works from any module, even outside Darklang) - - `Builtin.X`: resolves to F# builtins (fns/values only, not types) - - Cross-owner: must use full path - (from Tests.*, write `Darklang.SCM.Branch.mainBranchId`) - -## Critical Syntax -- Whitespace-sensitive (like Python) -- Nested function definitions need PARENTHESIZED params: `let helper (x) = ...`, - annotations optional. The parser desugars this to a local lambda, so use it for - task-local helpers. A nested fn can call one defined above it; a FORWARD reference - to one defined below fails. -- NO binding modules to variables (`let c = Darklang.Cli.Colors` is invalid — - modules are not first-class values. Use the full path each time.) -- Pipe |> feeds the left value as the FIRST argument of the right fn; any - args written after the fn come after it: `[1; 2] |> Stdlib.List.map toString`, - `n |> Stdlib.Int.add 1`. Wrap a complex left side in parens: (complex expr) |> fn -- Lists: [1; 2; 3] (semicolons) -- Char literals use single quotes: 'a', '\n', '\t', 'A' (a String uses - double quotes; "a" is not 'a') -- Tuples destructure inline in `let` and in lambda params: - `let (a, b) = pair`, `fun (name, done) -> ...` -- String concat: ++ (not @) -- String interpolation: $"x = {expr}; total = {a ++ b}" — does NOT auto-coerce - non-strings; wrap with Stdlib.Int.toString / Stdlib.Float.toString -- Numeric operators (+ - * / % ^ < > <= >= and unary -) are polymorphic over - numeric types; operands must match (`1 + 2`, `1.0 + 2.0`). `/` is integer - division on ints; float division on floats. A bare `1` is an arbitrary- - precision `Int` and never overflows. - -## Records - MyRecord { a = 1; b = 2 } - # { must not be left of type name - -## Enums - Option.Some 5 # construct: TypeName.Case - | Some x -> ... # match: just Case (bare form OK at top level of an arm) - # NESTED in a tuple or another constructor, parenthesize with NO space: - | (Ok(x), Ok(y)) -> ... # tuple of constructors (bare `Ok x` fails here) - | Some(Ok(v)) -> ... # constructor of a constructor - | Some((a, b)) -> ... # constructor wrapping a tuple - -## Troubleshooting -If commands hang or fail silently, check logs: - rundir/logs/cli.log # main CLI log - rundir/logs/ # other logs - -## More: docs syntax|types|operators|stdlib|errors|cli|scm""" +At module scope values use `val name = value`; `let` declares functions. Inside +a function, local values use `let`. Nested helpers need full annotations: +`let helper (x: Int) : Int = ...`. + +## Names and syntax + +- `Stdlib.X` expands to `Darklang.Stdlib.X`. Cross-owner names need a full path. +- Never write `PACKAGE.` in source or search; it is internal notation. +- Lists use semicolons: `[1; 2]`; chars use single quotes: `'a'`. +- `|>` passes its left value as the first argument. Parenthesize a complex left side. +- Strings concatenate with `++`. Interpolation does not coerce non-strings. +- Bare integers are arbitrary-precision `Int`; numeric operands must have one type. +- Local values use `let max = 6` without annotations; package values use `val`. +- Record construction is `State { count = 0 }`; update is `{ state with count = 1 }`. +- Prelude cases work bare: `Some x`, `None`, `Ok x`, `Error e`. +- User enum construction uses `TypeName.Case`; match cases are bare. +- Nested constructor patterns need no-space parentheses: `Some(Ok(x))`. +- A one-field tuple payload needs double parentheses: `Some((a, b))`. + +Effectful code may need `caps grant-for-fn ` or `caps grant-all`. Pass a +non-main branch on every call: `dark --branch feature ...`. + +## Targeted help + + docs syntax | types | operators | errors | packages + docs stdlib | cli | scm | deprecation + docs signatures + help """ diff --git a/packages/darklang/cli/docs/functions.dark b/packages/darklang/cli/docs/functions.dark index b4fe389504..52c5105854 100644 --- a/packages/darklang/cli/docs/functions.dark +++ b/packages/darklang/cli/docs/functions.dark @@ -15,7 +15,8 @@ let content () : String = let double (x: Int) : Int = x * 2 double n - # Params need parens - `let double x = x * 2` is a parse error. + # Nested params and the return need types. `let double (x) = ...` and + # `let double x = ...` are parse errors. # A nested fn sees the bindings above it, including other nested fns. # Calling one defined further down doesn't work; use a top-level fn for that. @@ -32,4 +33,4 @@ let content () : String = addFive 3 # 8 ## Unit return - let log (msg: String) : Unit = Stdlib.printLine msg""" + let log (msg: String) : Unit = Stdlib.println msg""" diff --git a/packages/darklang/cli/docs/packages.dark b/packages/darklang/cli/docs/packages.dark index 7eb160f31d..8f75a93265 100644 --- a/packages/darklang/cli/docs/packages.dark +++ b/packages/darklang/cli/docs/packages.dark @@ -24,6 +24,10 @@ let content () : String = Stdlib.List.map list fn Stdlib.Json.parse str +`PACKAGE.` is internal runtime/debug notation and is never valid in Dark source +or search queries. Write `Stdlib.List.map` (preferred) or the full source name +`Darklang.Stdlib.List.map`, never `PACKAGE.Darklang.Stdlib.List.map`. + ## Files packages///.dark diff --git a/packages/darklang/cli/docs/stdlibOverview.dark b/packages/darklang/cli/docs/stdlibOverview.dark index 8bf7ebee2e..930ad5514a 100644 --- a/packages/darklang/cli/docs/stdlibOverview.dark +++ b/packages/darklang/cli/docs/stdlibOverview.dark @@ -45,11 +45,11 @@ let execute (state: Cli.AppState) (_args: List) : Cli.AppState = |> Stdlib.List.filter (fun k -> k != "(root)") |> Stdlib.List.sort - Stdlib.printLine "# Darklang.Stdlib top-level modules" - Stdlib.printLine "" - topLevels |> Stdlib.List.iter (fun m -> Stdlib.printLine $" {m}") - Stdlib.printLine "" - Stdlib.printLine "Use `docs signatures ` for full signatures of one," - Stdlib.printLine "or `view ` for source + docs of a single item." + Stdlib.println "# Darklang.Stdlib top-level modules" + Stdlib.println "" + topLevels |> Stdlib.List.iter (fun m -> Stdlib.println $" {m}") + Stdlib.println "" + Stdlib.println "Use `docs signatures ` for full signatures of one," + Stdlib.println "or `view ` for source + docs of a single item." state diff --git a/packages/darklang/cli/docs/syntax.dark b/packages/darklang/cli/docs/syntax.dark index 36dfd8d551..f764cde6b5 100644 --- a/packages/darklang/cli/docs/syntax.dark +++ b/packages/darklang/cli/docs/syntax.dark @@ -9,9 +9,15 @@ let content () : String = if cond then a else b // comment -## Functions (module-level only, no nesting) +## Functions let greet (name: String) : String = $"Hello, {name}" +Nested functions are supported in a function body, but require parameter and +return annotations: + let outer (n: Int) : Int = + let double (x: Int) : Int = x * 2 + double n + ## Match match x with | Some v -> v @@ -36,7 +42,7 @@ let content () : String = Unit # type ## Printing multiple lines - // When printing 3+ lines, prefer printLines over repeated printLine: + // When printing 3+ lines, prefer printLines over repeated println: [ "line 1" "line 2" "line 3" diff --git a/packages/darklang/cli/docs/terminalUi.dark b/packages/darklang/cli/docs/terminalUi.dark index 21da905b1a..f9cef06619 100644 --- a/packages/darklang/cli/docs/terminalUi.dark +++ b/packages/darklang/cli/docs/terminalUi.dark @@ -57,7 +57,7 @@ changing the renderer, these are the things not to break. 3. One buffered write per frame. Every render path builds a single string and hands it to one `Stdlib.print`. No - `printLine`-per-row, so there is no window where a half-drawn frame is visible. + `println`-per-row, so there is no window where a half-drawn frame is visible. 4. Synchronized output markers. Each non-empty update is wrapped in `ESC [ ?2026h` ... `ESC [ ?2026l`. Terminals that support DEC @@ -93,7 +93,7 @@ across the screen": `界` is one grapheme occupying two columns, and so is a ZWJ 1. Rows contain text and SGR only. Everything else belongs to the renderer. 2. View functions are pure. If you need the world, read it during the state transition and stash the result on the state. The renderer may call your view function and diff it. -3. One write per frame. Don't interleave `printLine` with a live session. +3. One write per frame. Don't interleave `println` with a live session. 4. Measure with `Tui.Text.displayWidth` for plain text, `styledWidth` once SGR is in the row - never `Stdlib.String.length`. Passing a row containing control sequences to `displayWidth` is not valid. 5. Release the inline region before printing to scrollback. `releasePromptRegion` / diff --git a/packages/darklang/cli/docs/types.dark b/packages/darklang/cli/docs/types.dark index dff611c5ee..8d74d271f9 100644 --- a/packages/darklang/cli/docs/types.dark +++ b/packages/darklang/cli/docs/types.dark @@ -11,12 +11,12 @@ let content () : String = Char 'a' ## Option - Option.Some 5 - Option.None + Type: Stdlib.Option.Option + Construct: Some 5, None ## Result - Result.Ok value - Result.Error msg + Type: Stdlib.Result.Result + Construct: Ok value, Error msg ## Collections List [1; 2] diff --git a/packages/darklang/cli/entry.dark b/packages/darklang/cli/entry.dark index d4402381ac..07417d038a 100644 --- a/packages/darklang/cli/entry.dark +++ b/packages/darklang/cli/entry.dark @@ -37,13 +37,13 @@ let parseBranchFlag (args: List) : (Stdlib.Option.Option * List (Stdlib.Option.Option.None, rest) | Some "" -> - Stdlib.printLine "Warning: --branch requires a name, using main." + Stdlib.println "Warning: --branch requires a name, using main." (Stdlib.Option.Option.Some SCM.Branch.mainBranchId, rest) | Some n -> match SCM.Branch.getByName n with | Some b -> (Stdlib.Option.Option.Some b.id, rest) | None -> - Stdlib.printLine $"Warning: branch '{n}' not found, using main." + Stdlib.println $"Warning: branch '{n}' not found, using main." (Stdlib.Option.Option.Some SCM.Branch.mainBranchId, rest) @@ -75,7 +75,7 @@ let executeCliCommand (args: List) : Int = | _ -> false if classic then - Stdlib.printLine (View.formatWelcome ()) + Stdlib.println (View.formatWelcome ()) runInteractiveLoop initialState else let workbenchState = Workbench.execute initialState [] diff --git a/packages/darklang/cli/execution/eval.dark b/packages/darklang/cli/execution/eval.dark index 3362be3883..aa2065a22a 100644 --- a/packages/darklang/cli/execution/eval.dark +++ b/packages/darklang/cli/execution/eval.dark @@ -20,7 +20,7 @@ let evaluate let execute (state: AppState) (args: List) : AppState = let allowHarmful = - Stdlib.List.member args "--allow-harmful" + Stdlib.List.contains args "--allow-harmful" let args = args |> Stdlib.List.filter (fun a -> a != "--allow-harmful") @@ -37,11 +37,11 @@ let execute (state: AppState) (args: List) : AppState = | parts -> Stdlib.String.join parts " " match evaluate state expr allowHarmful with | Ok (Some s) -> - Stdlib.printLine s + Stdlib.println s state | Ok None -> state | Error message -> - Stdlib.printLine $"Error: {message}" + Stdlib.println $"Error: {message}" state diff --git a/packages/darklang/cli/execution/repl.dark b/packages/darklang/cli/execution/repl.dark index 73691247e5..7b0460ee33 100644 --- a/packages/darklang/cli/execution/repl.dark +++ b/packages/darklang/cli/execution/repl.dark @@ -135,7 +135,7 @@ let wantsAnotherLine (text: String) : Bool = |> Stdlib.List.head |> Stdlib.Option.withDefault "" (operators |> Stdlib.List.any (fun s -> Stdlib.String.endsWith lastLine s)) - || (Stdlib.List.member keywords lastToken) + || (Stdlib.List.contains keywords lastToken) /// Recognize a top-level `let = ` typed at the REPL, returning (name, expr). Only a single /// simple name binds; anything else (a pattern, a `let` with no `=`, a bare expression) returns None and is diff --git a/packages/darklang/cli/execution/run.dark b/packages/darklang/cli/execution/run.dark index c123537874..a8dad73b97 100644 --- a/packages/darklang/cli/execution/run.dark +++ b/packages/darklang/cli/execution/run.dark @@ -6,11 +6,11 @@ module Darklang.Cli.Run let execute (state: AppState) (args: List) : AppState = let allowHarmful = - Stdlib.List.member args "--allow-harmful" + Stdlib.List.contains args "--allow-harmful" // `dark run` respects the host's configured grant by default (same as `eval`). // `--sandbox` drops to NO capabilities, for running untrusted scripts. let sandbox = - Stdlib.List.member args "--sandbox" + Stdlib.List.contains args "--sandbox" let args = args |> Stdlib.List.filter (fun a -> @@ -52,9 +52,9 @@ let executeScript $" `run` executes a script; that looks like a package function. Try: {evalCmd}" ] |> Stdlib.printLines else - Stdlib.printLine $"Script not found: {scriptPath}" - | PermissionDenied -> Stdlib.printLine $"Permission denied: {scriptPath}" - | Other msg -> Stdlib.printLine $"Could not read script {scriptPath}: {msg}" + Stdlib.println $"Script not found: {scriptPath}" + | PermissionDenied -> Stdlib.println $"Permission denied: {scriptPath}" + | Other msg -> Stdlib.println $"Could not read script {scriptPath}: {msg}" state | Ok script -> let scriptSourceCode = Stdlib.String.fromBlobWithReplacement script @@ -73,11 +73,11 @@ let executeScript if exitCode == 0 then state else - Stdlib.printLine $"Script exited with code {Stdlib.Int.toString exitCode}" + Stdlib.println $"Script exited with code {Stdlib.Int.toString exitCode}" state | Error e -> let pretty = ExecutionError.toString state.currentBranchId e - Stdlib.printLine $"Script error: {pretty}" + Stdlib.println $"Script error: {pretty}" state diff --git a/packages/darklang/cli/exportSeed.dark b/packages/darklang/cli/exportSeed.dark index 2a3cfa816b..0fa4b7ef50 100644 --- a/packages/darklang/cli/exportSeed.dark +++ b/packages/darklang/cli/exportSeed.dark @@ -5,13 +5,13 @@ let execute (state: AppState) (args: List): AppState = | [outputPath] -> match Builtin.pmSeedExport outputPath with | Ok _ -> - Stdlib.printLine $"Seed exported to {outputPath}" + Stdlib.println $"Seed exported to {outputPath}" state | Error err -> - Stdlib.printLine (View.formatError $"Export failed: {err}") + Stdlib.println (View.formatError $"Export failed: {err}") state | _ -> - Stdlib.printLine (View.formatError "Usage: export-seed ") + Stdlib.println (View.formatError "Usage: export-seed ") state let help (state: AppState): AppState = diff --git a/packages/darklang/cli/help.dark b/packages/darklang/cli/help.dark index 88e382c6e2..87beffa98a 100644 --- a/packages/darklang/cli/help.dark +++ b/packages/darklang/cli/help.dark @@ -4,13 +4,13 @@ module Darklang.Cli.Help let execute (state: AppState) (args: List) : AppState = match args with | [] -> - Stdlib.printLine (Registry.getDetailedCommandList ()) + Stdlib.println (Registry.getDetailedCommandList ()) state | [commandName] -> Registry.executeCommandHelp commandName state | _ -> - Stdlib.printLine (Colors.error "Usage: help [command]") - Stdlib.printLine "Too many arguments. Use 'help' for general help or 'help ' for specific help." + Stdlib.println (Colors.error "Usage: help [command]") + Stdlib.println "Too many arguments. Use 'help' for general help or 'help ' for specific help." state diff --git a/packages/darklang/cli/http/serve.dark b/packages/darklang/cli/http/serve.dark index 2cbf080fbd..18403ebd95 100644 --- a/packages/darklang/cli/http/serve.dark +++ b/packages/darklang/cli/http/serve.dark @@ -75,12 +75,12 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match parsed.error with | Some err -> - Stdlib.printLine (Colors.error err) + Stdlib.println (Colors.error err) printUsage () state | None -> if parsed.routerPath == "" then - Stdlib.printLine + Stdlib.println (Colors.error "Missing router path. Usage: serve [--port N] [--max-body-bytes N]") state @@ -138,18 +138,18 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match Stdlib.HttpServer.serve config router announce with | Ok _ -> () - | Error msg -> Stdlib.printLine (Colors.error msg) + | Error msg -> Stdlib.println (Colors.error msg) state | Error msg -> - Stdlib.printLine ( + Stdlib.println ( Colors.error $"{msg} — is it defined and committed on this branch?") state let help (state: Cli.AppState) : Cli.AppState = - Stdlib.printLine "Start an HTTP server backed by a Darklang router." - Stdlib.printLine "" + Stdlib.println "Start an HTTP server backed by a Darklang router." + Stdlib.println "" printUsage () state diff --git a/packages/darklang/cli/installation/README.md b/packages/darklang/cli/installation/README.md index bd8b464fa8..f46c8b356f 100644 --- a/packages/darklang/cli/installation/README.md +++ b/packages/darklang/cli/installation/README.md @@ -44,7 +44,7 @@ To run the CLI executable against the local package manager: Stdlib.Result.Result.Ok() // don't update _too_ often else if hasUpdatedInLastDay configPath then - Stdlib.printLine + Stdlib.println "Skipping self-update because we've updated in the last 24 hours" Stdlib.Result.Result.Ok() @@ -57,7 +57,7 @@ To run the CLI executable against the local package manager: Installation.Install.installOrUpdateLatestRelease host) | Error e -> Stdlib.Result.Result.Error e - if Stdlib.List.member_v0 args "--skip-self-update" then + if Stdlib.List.contains_v0 args "--skip-self-update" then let newArgs = args |> Stdlib.List.filter (fun arg -> arg != "--skip-self-update") @@ -66,6 +66,6 @@ To run the CLI executable against the local package manager: match Installation.selfUpdateIfRelevant () with | Ok _ -> processNormally args | Error e -> - Stdlib.printLine $"Failed to run self-update: {e}\nProceeding anyway." + Stdlib.println $"Failed to run self-update: {e}\nProceeding anyway." processNormally args ``` \ No newline at end of file diff --git a/packages/darklang/cli/installation/install.dark b/packages/darklang/cli/installation/install.dark index 8faf8bed16..bd5baa4864 100644 --- a/packages/darklang/cli/installation/install.dark +++ b/packages/darklang/cli/installation/install.dark @@ -3,7 +3,7 @@ module Darklang.Cli.Installation.Install /// Check if a flag is present in args (supports both --flag and -f forms) let hasFlag (args: List) (longFlag: String) (shortFlag: String) : Bool = - (Stdlib.List.member args longFlag) || (Stdlib.List.member args shortFlag) + (Stdlib.List.contains args longFlag) || (Stdlib.List.contains args shortFlag) let execute (state: AppState) (args: List) : AppState = @@ -12,29 +12,29 @@ let execute (state: AppState) (args: List) : AppState = // Parse flags let autoConfirm = hasFlag args "--yes" "-y" - let uninstallFirst = Stdlib.List.member args "--uninstall-if-installed" + let uninstallFirst = Stdlib.List.contains args "--uninstall-if-installed" match currentMode with | Installed -> if uninstallFirst then // Uninstall first, then reinstall - Stdlib.printLine "Uninstalling existing installation..." + Stdlib.println "Uninstalling existing installation..." match Installation.System.uninstallWithConfirmation host true with | Ok _ -> - Stdlib.printLine "Reinstalling..." + Stdlib.println "Reinstalling..." // After uninstall, we're effectively in portable mode, copy the binary match Installation.System.installFromCurrentBinary host autoConfirm with | Ok message -> - Stdlib.printLine (View.formatSuccess message) + Stdlib.println (View.formatSuccess message) state | Error e -> - Stdlib.printLine (View.formatError e) + Stdlib.println (View.formatError e) state | Error e -> - Stdlib.printLine (View.formatError $"Uninstall failed: {e}") + Stdlib.println (View.formatError $"Uninstall failed: {e}") state else - Stdlib.printLine (View.formatSuccess "Already installed globally") + Stdlib.println (View.formatSuccess "Already installed globally") state | Portable -> @@ -43,52 +43,52 @@ let execute (state: AppState) (args: List) : AppState = if Installation.System.globalInstallationExists host then if uninstallFirst then // Uninstall existing global, then install from current binary - Stdlib.printLine "Uninstalling existing global installation..." + Stdlib.println "Uninstalling existing global installation..." match Installation.System.uninstallWithConfirmation host true with | Ok _ -> - Stdlib.printLine "Installing from current binary..." + Stdlib.println "Installing from current binary..." match Installation.System.installFromCurrentBinary host autoConfirm with | Ok message -> - Stdlib.printLine (View.formatSuccess message) + Stdlib.println (View.formatSuccess message) state | Error e -> - Stdlib.printLine (View.formatError e) + Stdlib.println (View.formatError e) state | Error e -> - Stdlib.printLine (View.formatError $"Uninstall failed: {e}") + Stdlib.println (View.formatError $"Uninstall failed: {e}") state else let homeDir = Config.getDarklangHomeDir host let message = $"Detected portable mode - you're running from {currentDir}/.darklang\nFound existing global installation at {homeDir}\n\nIf you'd like to update your global installation, please run `dark` rather than this portable executable.\n\nOr use --uninstall-if-installed to replace the existing installation." - Stdlib.printLine (View.formatSuccess message) + Stdlib.println (View.formatSuccess message) state else let choice = if autoConfirm then "1" // Default to copying current binary with --yes else - Stdlib.printLine "Choose installation method:" - Stdlib.printLine "1. Copy this binary (for testing local changes)" - Stdlib.printLine "2. Download latest release from GitHub" - Stdlib.printLine "Choose option (1 or 2): " + Stdlib.println "Choose installation method:" + Stdlib.println "1. Copy this binary (for testing local changes)" + Stdlib.println "2. Download latest release from GitHub" + Stdlib.println "Choose option (1 or 2): " (Builtin.stdinReadLine ()) |> Stdlib.String.trim if choice == "1" then match Installation.System.installFromCurrentBinary host autoConfirm with | Ok message -> - Stdlib.printLine (View.formatSuccess message) + Stdlib.println (View.formatSuccess message) state | Error e -> - Stdlib.printLine (View.formatError e) + Stdlib.println (View.formatError e) state else - Stdlib.printLine "Installing globally..." + Stdlib.println "Installing globally..." match Installation.System.install host autoConfirm with | Ok message -> - Stdlib.printLine (View.formatSuccess message) + Stdlib.println (View.formatSuccess message) state | Error e -> - Stdlib.printLine (View.formatError e) + Stdlib.println (View.formatError e) state diff --git a/packages/darklang/cli/installation/status.dark b/packages/darklang/cli/installation/status.dark index 757de44e68..6c8a858338 100644 --- a/packages/darklang/cli/installation/status.dark +++ b/packages/darklang/cli/installation/status.dark @@ -21,7 +21,7 @@ let execute (state: AppState) (args: List) : AppState = installationStatus ++ "\n\n" ++ "Type " ++ Colors.hint "'help'" ++ " for available commands" - Stdlib.printLine statusText + Stdlib.println statusText state diff --git a/packages/darklang/cli/installation/system.dark b/packages/darklang/cli/installation/system.dark index 8ce835d1b2..60086f2d92 100644 --- a/packages/darklang/cli/installation/system.dark +++ b/packages/darklang/cli/installation/system.dark @@ -143,7 +143,7 @@ let installFromCurrentBinary (host: Stdlib.Cli.Host.Host) (autoConfirm: Bool) : if autoConfirm then "fresh" else - Stdlib.printLine "Would you like to copy the data from this portable directory to the installation, or start fresh? (copy/fresh, default: fresh): " + Stdlib.println "Would you like to copy the data from this portable directory to the installation, or start fresh? (copy/fresh, default: fresh): " (Builtin.stdinReadLine ()) |> Stdlib.String.trim if migrateChoice == "copy" then @@ -169,11 +169,11 @@ let install (host: Stdlib.Cli.Host.Host) (autoConfirm: Bool) : Stdlib.Result.Res if autoConfirm then "1" else - Stdlib.printLine "Found existing portable installation with data." - Stdlib.printLine "Installation options:" - Stdlib.printLine "1. Fresh install (start with clean database)" - Stdlib.printLine "2. Migrate current data to global installation" - Stdlib.printLine "Choose option (1 or 2, default: 1): " + Stdlib.println "Found existing portable installation with data." + Stdlib.println "Installation options:" + Stdlib.println "1. Fresh install (start with clean database)" + Stdlib.println "2. Migrate current data to global installation" + Stdlib.println "Choose option (1 or 2, default: 1): " (Builtin.stdinReadLine ()) |> Stdlib.String.trim match Download.installOrUpdateLatestRelease host with @@ -199,9 +199,9 @@ let updateIfAvailable (currentVersion: String) (host: Stdlib.Cli.Host.Host) (aut if autoConfirm then "y" else - Stdlib.printLine "You're currently running a locally-installed binary (from development)." - Stdlib.printLine $"Latest official release is {latestVersion}." - Stdlib.printLine "Update to the official release? (y/n): " + Stdlib.println "You're currently running a locally-installed binary (from development)." + Stdlib.println $"Latest official release is {latestVersion}." + Stdlib.println "Update to the official release? (y/n): " (Builtin.stdinReadLine ()) |> Stdlib.String.trim if choice == "y" || choice == "Y" then @@ -230,11 +230,11 @@ let uninstallWithConfirmation (host: Stdlib.Cli.Host.Host) (autoConfirm: Bool) : if autoConfirm then "y" else - Stdlib.printLine "Are you sure you want to uninstall the CLI? (y/n): " + Stdlib.println "Are you sure you want to uninstall the CLI? (y/n): " Builtin.stdinReadLine () if response == "y" || response == "Y" then - Stdlib.printLine "Uninstalling..." + Stdlib.println "Uninstalling..." match Uninstall.runUninstall host with | Ok _ -> Stdlib.Result.Result.Ok("Uninstall complete") | Error e -> Stdlib.Result.Result.Error e diff --git a/packages/darklang/cli/installation/uninstall.dark b/packages/darklang/cli/installation/uninstall.dark index 483378a037..6b1fb1c713 100644 --- a/packages/darklang/cli/installation/uninstall.dark +++ b/packages/darklang/cli/installation/uninstall.dark @@ -44,21 +44,21 @@ let execute (state: AppState) (args: List) : AppState = // Parse flags let autoConfirm = - (Stdlib.List.member args "--yes") || (Stdlib.List.member args "-y") + (Stdlib.List.contains args "--yes") || (Stdlib.List.contains args "-y") match currentMode with | Installed -> match Installation.System.uninstallWithConfirmation host autoConfirm with | Ok message -> - Stdlib.printLine (View.formatSuccess message) + Stdlib.println (View.formatSuccess message) // Exit after successful uninstall since the executable is being removed { state with isExiting = true } | Error e -> - Stdlib.printLine (View.formatError e) + Stdlib.println (View.formatError e) state | Portable -> - Stdlib.printLine (View.formatError "Cannot uninstall - running in portable mode") - Stdlib.printLine "To remove this portable installation, simply delete the current directory" + Stdlib.println (View.formatError "Cannot uninstall - running in portable mode") + Stdlib.println "To remove this portable installation, simply delete the current directory" state diff --git a/packages/darklang/cli/installation/update.dark b/packages/darklang/cli/installation/update.dark index 90bd8ff4af..0d9a87f2a7 100644 --- a/packages/darklang/cli/installation/update.dark +++ b/packages/darklang/cli/installation/update.dark @@ -8,21 +8,21 @@ let execute (state: AppState) (args: List) : AppState = // Parse flags let autoConfirm = - (Stdlib.List.member args "--yes") || (Stdlib.List.member args "-y") + (Stdlib.List.contains args "--yes") || (Stdlib.List.contains args "-y") match currentMode with | Installed -> - Stdlib.printLine $"Checking for updates from Darklang CLI {currentVersion}..." + Stdlib.println $"Checking for updates from Darklang CLI {currentVersion}..." match Installation.System.updateIfAvailable currentVersion host autoConfirm with | Ok message -> - Stdlib.printLine (View.formatSuccess message) + Stdlib.println (View.formatSuccess message) state | Error e -> - Stdlib.printLine (View.formatError e) + Stdlib.println (View.formatError e) state | Portable -> let message = $"Running in portable mode from {Builtin.directoryCurrent ()}/.darklang\n\nTo update this portable executable, please download the latest release manually from:\nhttps://github.com/darklang/dark/releases\n\nFor automatic updates, consider running 'install' to set up global installation." - Stdlib.printLine (View.formatSuccess message) + Stdlib.println (View.formatSuccess message) state diff --git a/packages/darklang/cli/installation/version.dark b/packages/darklang/cli/installation/version.dark index 452805d803..52d3a7aafc 100644 --- a/packages/darklang/cli/installation/version.dark +++ b/packages/darklang/cli/installation/version.dark @@ -4,7 +4,7 @@ module Darklang.Cli.Installation.Version let execute (state: AppState) (args: List) : AppState = // Checks GitHub for a newer release, which dominates the command. Deliberate: knowing you're out of // date is the point of the command, not a side errand. - Stdlib.printLine (Helpers.versionInfoWithUpdateCheck ()) + Stdlib.println (Helpers.versionInfoWithUpdateCheck ()) // Store-format coordinate — the on-disk data shape this Dark speaks vs what your local data is stamped at. // (This is what the boot-time migrator + sync's wire-version key off; see `dark update`.) It is NOT a product @@ -27,9 +27,9 @@ let execute (state: AppState) (args: List) : AppState = | _ -> "?" if exeRel == storeRel then - Stdlib.printLine $"alpha — store format v{exeRel}, your data is up to date" + Stdlib.println $"alpha — store format v{exeRel}, your data is up to date" else - Stdlib.printLine + Stdlib.println $"alpha — store format v{exeRel}, your data is v{storeRel} (run `dark update`)" state diff --git a/packages/darklang/cli/loop.dark b/packages/darklang/cli/loop.dark index 5e5999566a..722caf37b5 100644 --- a/packages/darklang/cli/loop.dark +++ b/packages/darklang/cli/loop.dark @@ -90,12 +90,12 @@ let handleKeyInput (state: AppState) (key: Stdlib.Cli.Stdin.Key.Key) (modifiers: // Execute the current command if Stdlib.String.isEmpty (Stdlib.String.trim state.prompt.text) then let released = releasePromptRegion state - Stdlib.printLine "" + Stdlib.println "" { released with prompt = Prompt.Editing.clear state.prompt } else let released = releasePromptRegion state - Stdlib.printLine "" + Stdlib.println "" let commandToExecute = Stdlib.String.trim state.prompt.text let tCmd = Telemetry.now () let newState = @@ -132,7 +132,7 @@ let handleKeyInput (state: AppState) (key: Stdlib.Cli.Stdin.Key.Key) (modifiers: | multiple -> // Multiple completions - open interactive picker let released = releasePromptRegion state - Stdlib.printLine "" + Stdlib.println "" let pickerState = CompletionPicker.create state.prompt.text multiple let size = Tui.TerminalSession.currentSize () match CompletionPicker.startAtSize pickerState size with @@ -140,7 +140,7 @@ let handleKeyInput (state: AppState) (key: Stdlib.Cli.Stdin.Key.Key) (modifiers: { released with currentPage = Page.CompletionPicker session } | Error message -> - Stdlib.printLine message + Stdlib.println message released | UpArrow -> @@ -213,7 +213,7 @@ let handleKeyInput (state: AppState) (key: Stdlib.Cli.Stdin.Key.Key) (modifiers: currentPage = Page.MainPrompt } | Unavailable message -> let _stopped = CompletionPicker.stop session - Stdlib.printLine message + Stdlib.println message { state with currentPage = Page.MainPrompt } @@ -405,7 +405,7 @@ let runInteractiveLoop (state: AppState) : Int = promptTerminal = Stdlib.Option.Option.Some terminal } | Error message -> - Stdlib.printLine message + Stdlib.println message { state with isExiting = true } | InteractiveNav _ -> diff --git a/packages/darklang/cli/ops.dark b/packages/darklang/cli/ops.dark index b90c4c783b..729d053d5b 100644 --- a/packages/darklang/cli/ops.dark +++ b/packages/darklang/cli/ops.dark @@ -60,7 +60,7 @@ let executeList (state: AppState) (limit: Int64) : AppState = else $"{Stdlib.Int64.toString total} ops on branch {branchName} — showing the {Stdlib.Int.toString shown} most recent" - Stdlib.printLine (Colors.success header) + Stdlib.println (Colors.success header) // A commit message belongs to the COMMIT, not to each op — repeating it down every row was noise that made // 20 ops of one commit look like 20 unrelated things. It prints once per run of ops sharing a commit, so a @@ -101,7 +101,7 @@ let executeList (state: AppState) (limit: Int64) : AppState = state | Error e -> - Stdlib.printLine (Colors.error $"Couldn't read the op log: {e}") + Stdlib.println (Colors.error $"Couldn't read the op log: {e}") state let help (state: AppState) : AppState = @@ -114,7 +114,7 @@ let help (state: AppState) : AppState = "" "The log is the `package_ops` table; this reads it through Stdlib.Sqlite — the same generic interface a" "peer's wire read uses (and a future Dark-managed fold would). Read-only." ] - (fun line -> Stdlib.printLine line) + (fun line -> Stdlib.println line) state diff --git a/packages/darklang/cli/outliner/app.dark b/packages/darklang/cli/outliner/app.dark index ad15ef267a..e4a4382d90 100644 --- a/packages/darklang/cli/outliner/app.dark +++ b/packages/darklang/cli/outliner/app.dark @@ -81,7 +81,7 @@ let execute (cliState: Darklang.Cli.AppState) (_args: List) : Darklang.C { cliState with currentPage = Darklang.Cli.Page.SubApp (makeSubApp session) } | Error message -> - Stdlib.printLine message + Stdlib.println message cliState diff --git a/packages/darklang/cli/outliner/tests.dark b/packages/darklang/cli/outliner/tests.dark index a13039ff02..c61504c285 100644 --- a/packages/darklang/cli/outliner/tests.dark +++ b/packages/darklang/cli/outliner/tests.dark @@ -441,9 +441,9 @@ let allTests () : List<(String * TestFunction)> = let runAllTests () : Int = let tests = allTests () - Stdlib.printLine "" - Stdlib.printLine "Outliner Tests" - Stdlib.printLine "==============" + Stdlib.println "" + Stdlib.println "Outliner Tests" + Stdlib.println "==============" let initialSummary = TestSummary @@ -461,22 +461,22 @@ let runAllTests () : Int = match testFn () with | Pass -> - Stdlib.printLine $" PASS {name}" + Stdlib.println $" PASS {name}" { newSummary with passedTests = newSummary.passedTests + 1 } | Fail message -> - Stdlib.printLine $" FAIL {name}: {message}" + Stdlib.println $" FAIL {name}: {message}" { newSummary with failedTests = newSummary.failedTests + 1 failedTestNames = Stdlib.List.append newSummary.failedTestNames [name] }) - Stdlib.printLine "" - Stdlib.printLine $"Results: {Stdlib.Int.toString finalSummary.passedTests}/{Stdlib.Int.toString finalSummary.totalTests} passed" + Stdlib.println "" + Stdlib.println $"Results: {Stdlib.Int.toString finalSummary.passedTests}/{Stdlib.Int.toString finalSummary.totalTests} passed" if finalSummary.failedTests == 0 then - Stdlib.printLine "All tests passed!" + Stdlib.println "All tests passed!" 0 else - Stdlib.printLine "Failed:" + Stdlib.println "Failed:" finalSummary.failedTestNames - |> Stdlib.List.iter (fun name -> Stdlib.printLine $" - {name}") + |> Stdlib.List.iter (fun name -> Stdlib.println $" - {name}") 1 diff --git a/packages/darklang/cli/packages/back.dark b/packages/darklang/cli/packages/back.dark index 7cd74c3508..a6069fef42 100644 --- a/packages/darklang/cli/packages/back.dark +++ b/packages/darklang/cli/packages/back.dark @@ -6,7 +6,7 @@ let execute (state: AppState) (args: List) : AppState = match Stdlib.List.last state.packageData.locationHistory with | Some previousLocation -> let pathStr = formatLocation previousLocation - Stdlib.printLine (Colors.success ("Back to: " ++ pathStr)) + Stdlib.println (Colors.success ("Back to: " ++ pathStr)) { state with prompt = Prompt.Editing.clear state.prompt @@ -18,7 +18,7 @@ let execute (state: AppState) (args: List) : AppState = } | None -> - Stdlib.printLine "No previous location in history" + Stdlib.println "No previous location in history" state diff --git a/packages/darklang/cli/packages/db.dark b/packages/darklang/cli/packages/db.dark index 43e3c2673b..28d61c1557 100644 --- a/packages/darklang/cli/packages/db.dark +++ b/packages/darklang/cli/packages/db.dark @@ -20,20 +20,20 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = queryDB state dbName filterExpr | [ dbName ] -> - Stdlib.printLine (Colors.error "Error: Type name required") - Stdlib.printLine "" - Stdlib.printLine "Usage: db " - Stdlib.printLine "" - Stdlib.printLine "Example: db UsersDB MyApp.User" + Stdlib.println (Colors.error "Error: Type name required") + Stdlib.println "" + Stdlib.println "Usage: db " + Stdlib.println "" + Stdlib.println "Example: db UsersDB MyApp.User" state | [ dbName; typeName ] -> createDB state dbName typeName | _ -> - Stdlib.printLine (Colors.error "Error: Invalid command") - Stdlib.printLine "" - Stdlib.printLine "Run 'db help' for usage information" + Stdlib.println (Colors.error "Error: Invalid command") + Stdlib.println "" + Stdlib.println "Run 'db help' for usage information" state @@ -79,17 +79,17 @@ let listDBs (state: Cli.AppState) : Cli.AppState = match dbs with | [] -> - Stdlib.printLine "" - Stdlib.printLine "No databases." - Stdlib.printLine "" - Stdlib.printLine "Create one with: db " - Stdlib.printLine "" + Stdlib.println "" + Stdlib.println "No databases." + Stdlib.println "" + Stdlib.println "Create one with: db " + Stdlib.println "" state | _ -> - Stdlib.printLine "" - Stdlib.printLine "Databases:" - Stdlib.printLine "" + Stdlib.println "" + Stdlib.println "Databases:" + Stdlib.println "" // Print table header let nameHeader = @@ -97,8 +97,8 @@ let listDBs (state: Cli.AppState) : Cli.AppState = let typeHeader = "Type" - Stdlib.printLine $"{nameHeader}{typeHeader}" - Stdlib.printLine (Stdlib.String.repeat "─" 60) + Stdlib.println $"{nameHeader}{typeHeader}" + Stdlib.println (Stdlib.String.repeat "─" 60) // Print each DB dbs @@ -108,16 +108,16 @@ let listDBs (state: Cli.AppState) : Cli.AppState = let nameCol = (Stdlib.String.padEnd name " " 20) |> Builtin.unwrap - Stdlib.printLine $"{nameCol}{typeName}") + Stdlib.println $"{nameCol}{typeName}") - Stdlib.printLine "" + Stdlib.println "" state let viewDB (state: Cli.AppState) (dbName: String) : Cli.AppState = match getDBTypeName state dbName with | None -> - Stdlib.printLine (Colors.error $"Error: Database not found: {dbName}") + Stdlib.println (Colors.error $"Error: Database not found: {dbName}") state | Some typeName -> let fieldNames = getDBFieldNames state typeName @@ -149,7 +149,7 @@ let viewDB (state: Cli.AppState) (dbName: String) : Cli.AppState = match Builtin.cliEvaluateExpression state.accountID state.currentBranchId expr false with | Ok resultOpt -> let result = resultOpt |> Stdlib.Option.withDefault "" - Stdlib.printLine "" + Stdlib.println "" let colWidth = 15 @@ -163,17 +163,17 @@ let viewDB (state: Cli.AppState) (dbName: String) : Cli.AppState = (Stdlib.String.padEnd name " " colWidth) |> Builtin.unwrap) |> Stdlib.String.join "" - Stdlib.printLine $"{keyHeader}{fieldHeaders}" + Stdlib.println $"{keyHeader}{fieldHeaders}" let totalWidth = colWidth + ((Stdlib.List.length fieldNames) * colWidth) - Stdlib.printLine (Stdlib.String.repeat "─" totalWidth) + Stdlib.println (Stdlib.String.repeat "─" totalWidth) if Stdlib.String.isEmpty result then - Stdlib.printLine " (empty)" - Stdlib.printLine "" - Stdlib.printLine "0 row(s)" + Stdlib.println " (empty)" + Stdlib.println "" + Stdlib.println "0 row(s)" else let rows = Stdlib.String.split result "<<>>" @@ -187,18 +187,18 @@ let viewDB (state: Cli.AppState) (dbName: String) : Cli.AppState = (Stdlib.String.padEnd part " " colWidth) |> Builtin.unwrap) |> Stdlib.String.join "" - Stdlib.printLine formatted) + Stdlib.println formatted) - Stdlib.printLine "" + Stdlib.println "" let rowCount = Stdlib.List.length rows - Stdlib.printLine $"{Stdlib.Int.toString rowCount} row(s)" + Stdlib.println $"{Stdlib.Int.toString rowCount} row(s)" - Stdlib.printLine "" + Stdlib.println "" state | Error err -> - Stdlib.printLine (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) + Stdlib.println (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) state @@ -211,11 +211,11 @@ let getDBValue match Builtin.cliEvaluateExpression state.accountID state.currentBranchId expr false with | Ok (Some result) -> - Stdlib.printLine result + Stdlib.println result state | Ok None -> state | Error err -> - Stdlib.printLine (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) + Stdlib.println (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) state @@ -227,7 +227,7 @@ let setDBValue : Cli.AppState = match getDBTypeName state dbName with | None -> - Stdlib.printLine (Colors.error $"Error: Database not found: {dbName}") + Stdlib.println (Colors.error $"Error: Database not found: {dbName}") state | Some typeName -> let expr = @@ -235,10 +235,10 @@ let setDBValue match Builtin.cliEvaluateExpression state.accountID state.currentBranchId expr false with | Ok _ -> - Stdlib.printLine (Colors.success $"Set {key} in {dbName}") + Stdlib.println (Colors.success $"Set {key} in {dbName}") state | Error err -> - Stdlib.printLine (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) + Stdlib.println (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) state @@ -251,10 +251,10 @@ let deleteDBValue match Builtin.cliEvaluateExpression state.accountID state.currentBranchId expr false with | Ok _ -> - Stdlib.printLine (Colors.success $"Deleted {key} from {dbName}") + Stdlib.println (Colors.success $"Deleted {key} from {dbName}") state | Error err -> - Stdlib.printLine (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) + Stdlib.println (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) state @@ -267,28 +267,28 @@ let queryDB match Builtin.cliEvaluateExpression state.accountID state.currentBranchId expr false with | Ok (Some result) -> - Stdlib.printLine result + Stdlib.println result state | Ok None -> state | Error err -> - Stdlib.printLine (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) + Stdlib.println (Colors.error (Cli.ExecutionError.toString state.currentBranchId err)) state let dropDB (state: Cli.AppState) (dbName: String) : Cli.AppState = match Builtin.dbDrop dbName with | Ok () -> - Stdlib.printLine (Colors.success $"Dropped database: {dbName}") + Stdlib.println (Colors.success $"Dropped database: {dbName}") state | Error msg -> - Stdlib.printLine (Colors.error $"Error: {msg}") + Stdlib.println (Colors.error $"Error: {msg}") state let createDB (state: Cli.AppState) (dbName: String) (typeName: String) : Cli.AppState = match Location.parseRelativeTo state.packageData.currentLocation typeName with | Error msg -> - Stdlib.printLine (Colors.error $"Error: Invalid type name: {msg}") + Stdlib.println (Colors.error $"Error: Invalid type name: {msg}") state | Ok typeLocation -> @@ -297,9 +297,9 @@ let createDB (state: Cli.AppState) (dbName: String) (typeName: String) : Cli.App let fullTypeName = PrettyPrinter.ProgramTypes.PackageLocation.packageLocation typeLocation - Stdlib.printLine (Colors.error $"Error: Type not found: {fullTypeName}") - Stdlib.printLine "" - Stdlib.printLine "Make sure the type exists before creating a database for it." + Stdlib.println (Colors.error $"Error: Type not found: {fullTypeName}") + Stdlib.println "" + Stdlib.println "Make sure the type exists before creating a database for it." state | Some typeHash -> @@ -308,15 +308,15 @@ let createDB (state: Cli.AppState) (dbName: String) (typeName: String) : Cli.App let fullTypeName = PrettyPrinter.ProgramTypes.PackageLocation.packageLocation typeLocation - Stdlib.printLine "" - Stdlib.printLine + Stdlib.println "" + Stdlib.println (Colors.success $"Created database: {dbName} (type: {fullTypeName})") - Stdlib.printLine "" + Stdlib.println "" state | Error msg -> - Stdlib.printLine (Colors.error $"Error: Failed to create database: {msg}") + Stdlib.println (Colors.error $"Error: Failed to create database: {msg}") state diff --git a/packages/darklang/cli/packages/delete.dark b/packages/darklang/cli/packages/delete.dark index 8569014351..200080f683 100644 --- a/packages/darklang/cli/packages/delete.dark +++ b/packages/darklang/cli/packages/delete.dark @@ -80,42 +80,42 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match (parsed.itemType, parsed.target) with | _ when Stdlib.Bool.not (Stdlib.List.isEmpty parsed.parseErrors) -> - Stdlib.printLine (Colors.error (FlagParser.parseErrorsMessage parsed.parseErrors)) + Stdlib.println (Colors.error (FlagParser.parseErrorsMessage parsed.parseErrors)) help state | (None, _) -> help state | (Some itemTypeStr, targetOpt) -> match Propagate.itemKindFromString itemTypeStr with | None -> - Stdlib.printLine + Stdlib.println (Colors.error $"Error: '{itemTypeStr}' is not a valid item kind. Expected fn, type, or value.") - Stdlib.printLine "Usage: delete [options]" + Stdlib.println "Usage: delete [options]" state | Some itemKind -> match targetOpt with | None -> - Stdlib.printLine (Colors.error "Error: Missing target (location or hash)") + Stdlib.println (Colors.error "Error: Missing target (location or hash)") state | Some target -> let currentLoc = state.packageData.currentLocation match Location.parseRelativeTo currentLoc target with | Error errMsg -> - Stdlib.printLine (Colors.error $"Could not resolve {itemTypeStr} {target}: {errMsg}") + Stdlib.println (Colors.error $"Could not resolve {itemTypeStr} {target}: {errMsg}") state | Ok targetLoc -> match Deprecate.resolveTarget branchId currentLoc itemKind target with | None -> - Stdlib.printLine (Colors.error $"Could not resolve {itemTypeStr} {target}") + Stdlib.println (Colors.error $"Could not resolve {itemTypeStr} {target}") state | Some _targetRef -> // Only gate `delete` adds over `deprecate --kind obsolete`: refuse // when LIVE dependents exist. Deprecated-only callers don't block. let depCount = countLiveDependents branchId targetLoc itemKind if depCount > 0 && Stdlib.Bool.not parsed.ignoreDependents then - Stdlib.printLine + Stdlib.println (Colors.error $"{Cli.Text.plural depCount "live dependent"} still reference this item.") - Stdlib.printLine + Stdlib.println "Use `deprecate` to give them time to migrate, or pass `--ignore-dependents` / `--force`." state else diff --git a/packages/darklang/cli/packages/deprecate.dark b/packages/darklang/cli/packages/deprecate.dark index 508f675bc2..7a69affbb4 100644 --- a/packages/darklang/cli/packages/deprecate.dark +++ b/packages/darklang/cli/packages/deprecate.dark @@ -129,18 +129,18 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match (parsed.itemType, parsed.target, parsed.kind) with | _ when Stdlib.Bool.not (Stdlib.List.isEmpty parsed.parseErrors) -> - Stdlib.printLine (Colors.error (FlagParser.parseErrorsMessage parsed.parseErrors)) + Stdlib.println (Colors.error (FlagParser.parseErrorsMessage parsed.parseErrors)) help state | (None, _, _) -> help state | (Some _, None, _) -> - Stdlib.printLine (Colors.error "Error: Missing target (location or hash)") + Stdlib.println (Colors.error "Error: Missing target (location or hash)") state | (_, _, None) -> - Stdlib.printLine + Stdlib.println (Colors.error "Error: Missing --kind. Use: superseded-by, harmful, or obsolete") state @@ -148,14 +148,14 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | (Some itemTypeStr, Some target, Some kindStr) -> match Propagate.itemKindFromString itemTypeStr with | None -> - Stdlib.printLine (Colors.error $"Error: Invalid item type '{itemTypeStr}'") - Stdlib.printLine "Must be one of: fn, type, value" + Stdlib.println (Colors.error $"Error: Invalid item type '{itemTypeStr}'") + Stdlib.println "Must be one of: fn, type, value" state | Some itemKind -> let currentLoc = state.packageData.currentLocation match resolveTarget branchId currentLoc itemKind target with | None -> - Stdlib.printLine (Colors.error $"Could not resolve {itemTypeStr} {target}") + Stdlib.println (Colors.error $"Could not resolve {itemTypeStr} {target}") state | Some targetRef -> // --replacement has three outcomes we care about: @@ -176,13 +176,13 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match replacementResult with | Error msg -> - Stdlib.printLine (Colors.error msg) + Stdlib.println (Colors.error msg) state | Ok replacementRef -> match parseKind itemKind kindStr replacementRef with | Error msg -> - Stdlib.printLine (Colors.error msg) + Stdlib.println (Colors.error msg) state | Ok deprecationKind -> let fullPath = @@ -190,14 +190,14 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | Ok l -> PrettyPrinter.ProgramTypes.PackageLocation.packageLocation l | Error _ -> target - Stdlib.printLine $" Will deprecate {itemTypeStr} {fullPath}" - Stdlib.printLine $" kind: {kindStr}" + Stdlib.println $" Will deprecate {itemTypeStr} {fullPath}" + Stdlib.println $" kind: {kindStr}" if parsed.message != "" then - Stdlib.printLine $" message: {parsed.message}" + Stdlib.println $" message: {parsed.message}" match replacementRef with | Some _ -> match parsed.replacement with - | Some r -> Stdlib.printLine $" replacement: {r}" + | Some r -> Stdlib.println $" replacement: {r}" | None -> () | None -> () @@ -214,7 +214,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | [] -> () | [ _ ] -> () | _ -> - Stdlib.printLine + Stdlib.println (Colors.warning $" Heads up: this {itemTypeStr}'s content is bound to multiple names — deprecation keys on content, so ALL of these are deprecated together:") affected @@ -222,14 +222,14 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = |> Stdlib.printLines if parsed.dryRun then - Stdlib.printLine "(dry-run, no op emitted)" + Stdlib.println "(dry-run, no op emitted)" state else let proceed = if parsed.autoConfirm then true else - Stdlib.printLine "" + Stdlib.println "" Stdlib.print (Colors.warning "Proceed? (y/n): ") let response = Builtin.stdinReadLine () (response == "y") || (response == "Y") @@ -240,14 +240,14 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = (targetRef, deprecationKind, parsed.message) match SCM.PackageOps.add branchId [ op ] with | Ok _ -> - Stdlib.printLine + Stdlib.println (Colors.success $"Deprecated {itemTypeStr} {fullPath}") state | Error e -> - Stdlib.printLine (Colors.error $"Deprecation failed: {e}") + Stdlib.println (Colors.error $"Deprecation failed: {e}") state else - Stdlib.printLine "Deprecation cancelled." + Stdlib.println "Deprecation cancelled." state diff --git a/packages/darklang/cli/packages/errors.dark b/packages/darklang/cli/packages/errors.dark index 2d7bd4425f..9c07a34caa 100644 --- a/packages/darklang/cli/packages/errors.dark +++ b/packages/darklang/cli/packages/errors.dark @@ -1,6 +1,68 @@ module Darklang.Cli.Packages.Errors +/// `PACKAGE.` appears in internal runtime/debug representations but is never a +/// valid source namespace. Treat it as a hard error rather than unresolved WIP: +/// adding dependencies can never make it resolve. +let isInternalPackageName (originalName: List) : Bool = + match originalName with + | "PACKAGE" :: _ -> true + | _ -> false + + +let sourceNameWithoutInternalPrefix (originalName: List) : String = + let sourceParts = + match originalName with + | "PACKAGE" :: rest -> + match rest with + | "Darklang" :: darklangRest -> + match darklangRest with + | "Stdlib" :: stdlibRest -> Stdlib.List.append ["Stdlib"] stdlibRest + | _ -> rest + | _ -> rest + | _ -> originalName + Stdlib.String.join sourceParts "." + + +let splitLast + (parts: List) + : Stdlib.Option.Option<(List * String)> = + match parts with + | [] -> Stdlib.Option.Option.None + | [ last ] -> Stdlib.Option.Option.Some(([], last)) + | first :: rest -> + match splitLast rest with + | Some((modules, name)) -> + Stdlib.Option.Option.Some((Stdlib.List.push modules first, name)) + | None -> Stdlib.Option.Option.None + + +/// If `Bool.not` fails from an application module, check whether +/// `Stdlib.Bool.not` really exists before offering it as a correction. +let stdlibSuggestion + (branchId: Uuid) + (originalName: List) + : Stdlib.Option.Option = + match originalName with + | "Stdlib" :: _ + | "Darklang" :: _ + | "PACKAGE" :: _ -> Stdlib.Option.Option.None + | _ -> + match splitLast originalName with + | Some((modules, name)) when Stdlib.Bool.not (Stdlib.List.isEmpty modules) -> + let location = + LanguageTools.ProgramTypes.PackageLocation + { owner = "Darklang" + modules = Stdlib.List.push modules "Stdlib" + name = name } + match LanguageTools.PackageManager.findAny branchId location with + | Some _ -> + Stdlib.Option.Option.Some + ("Stdlib." ++ (Stdlib.String.join originalName ".")) + | None -> Stdlib.Option.Option.None + | _ -> Stdlib.Option.Option.None + + /// Prints the first parse diagnostic. Returns true when parsing failed. let reportParseErrors (diagnostics: List<(LanguageTools.Parser.Range * String)>) @@ -12,7 +74,7 @@ let reportParseErrors // 1-based column, matching renderDiagnostic and the line number above let colNum = (range.start.column + 1) |> Stdlib.Int.toString - Stdlib.printLine + Stdlib.println (Colors.error $"Parse error at line {lineNum}, column {colNum}: {message}") true @@ -23,6 +85,7 @@ let reportParseErrors /// Unresolved names are allowed in WIP — they'll be resolved by WipRefresh /// when the missing items are added, and rejected at commit time if still unresolved. let reportUnresolvedNames + (branchId: Uuid) (unresolvedNames: List @@ -30,21 +93,21 @@ let reportUnresolvedNames : Bool = let invalidNames = unresolvedNames - |> Stdlib.List.filter (fun (_, _, err) -> + |> Stdlib.List.filter (fun (_, originalName, err) -> match err with | InvalidName -> true - | NotFound -> false) + | NotFound -> isInternalPackageName originalName) let notFoundNames = unresolvedNames - |> Stdlib.List.filter (fun (_, _, err) -> + |> Stdlib.List.filter (fun (_, originalName, err) -> match err with - | NotFound -> true + | NotFound -> Stdlib.Bool.not (isInternalPackageName originalName) | InvalidName -> false) // Print warnings for unresolved names (will be fixed by WipRefresh) if (Stdlib.List.isEmpty notFoundNames) |> Stdlib.Bool.not then - Stdlib.printLine (Colors.warning "Warning: Unresolved references (will resolve when dependencies are added)") + Stdlib.println (Colors.warning "Warning: Unresolved references (will resolve when dependencies are added)") notFoundNames |> Stdlib.List.iter (fun (range, originalName, _err) -> @@ -52,14 +115,20 @@ let reportUnresolvedNames let colNum = range.start.column |> Stdlib.Int.toString let nameStr = Stdlib.String.join originalName "." - Stdlib.printLine - (Colors.warning $" - Not found: {nameStr} (line {lineNum}, column {colNum})")) + let suggestion = + match stdlibSuggestion branchId originalName with + | Some name -> $" Did you mean `{name}`?" + | None -> "" + + Stdlib.println + (Colors.warning + $" - Not found: {nameStr} (line {lineNum}, column {colNum}).{suggestion}")) - Stdlib.printLine "" + Stdlib.println "" // Print errors for invalid names (these block creation) if (Stdlib.List.isEmpty invalidNames) |> Stdlib.Bool.not then - Stdlib.printLine (Colors.error "Error: Invalid names") + Stdlib.println (Colors.error "Error: Invalid names") invalidNames |> Stdlib.List.iter (fun (range, originalName, _err) -> @@ -67,10 +136,16 @@ let reportUnresolvedNames let colNum = range.start.column |> Stdlib.Int.toString let nameStr = Stdlib.String.join originalName "." - Stdlib.printLine - (Colors.error $" - Invalid name: {nameStr} (line {lineNum}, column {colNum})")) + if isInternalPackageName originalName then + let suggested = sourceNameWithoutInternalPrefix originalName + Stdlib.println + (Colors.error + $" - Invalid name: {nameStr} (line {lineNum}, column {colNum}). `PACKAGE.` is internal notation; write `{suggested}`.") + else + Stdlib.println + (Colors.error $" - Invalid name: {nameStr} (line {lineNum}, column {colNum})")) - Stdlib.printLine "" + Stdlib.println "" true else false diff --git a/packages/darklang/cli/packages/findValues.dark b/packages/darklang/cli/packages/findValues.dark index fb0880a4b3..bbd64b3dd6 100644 --- a/packages/darklang/cli/packages/findValues.dark +++ b/packages/darklang/cli/packages/findValues.dark @@ -31,43 +31,43 @@ let resolveTypeHash let execute (state: Cli.AppState) (args: List) : Cli.AppState = match args with | [] -> - Stdlib.printLine "Usage: find-values [namespace]" + Stdlib.println "Usage: find-values [namespace]" state | [ typePath ] -> match resolveTypeHash state.currentBranchId typePath with | Error msg -> - Stdlib.printLine (Colors.error msg) + Stdlib.println (Colors.error msg) state | Ok typeHash -> let values = Stdlib.ValueSearch.findByType state.currentBranchId "" typeHash if Stdlib.List.isEmpty values then - Stdlib.printLine (Colors.dimText $"No values of type {typePath}") + Stdlib.println (Colors.dimText $"No values of type {typePath}") else values - |> Stdlib.List.iter (fun discovered -> Stdlib.printLine discovered.path) + |> Stdlib.List.iter (fun discovered -> Stdlib.println discovered.path) state | [ typePath; namespace_ ] -> match resolveTypeHash state.currentBranchId typePath with | Error msg -> - Stdlib.printLine (Colors.error msg) + Stdlib.println (Colors.error msg) state | Ok typeHash -> let values = Stdlib.ValueSearch.findByType state.currentBranchId namespace_ typeHash if Stdlib.List.isEmpty values then - Stdlib.printLine (Colors.dimText $"No values of type {typePath} in {namespace_}") + Stdlib.println (Colors.dimText $"No values of type {typePath} in {namespace_}") else values - |> Stdlib.List.iter (fun discovered -> Stdlib.printLine discovered.path) + |> Stdlib.List.iter (fun discovered -> Stdlib.println discovered.path) state | _ -> - Stdlib.printLine "Usage: find-values [namespace]" + Stdlib.println "Usage: find-values [namespace]" state diff --git a/packages/darklang/cli/packages/fn.dark b/packages/darklang/cli/packages/fn.dark index ed9a736160..d532ebdfe8 100644 --- a/packages/darklang/cli/packages/fn.dark +++ b/packages/darklang/cli/packages/fn.dark @@ -36,13 +36,27 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = "" " # Multi-line body via stdin (run from shell, not the REPL):" " ./scripts/run-cli fn fib - <<'EOF'" - " (n: Int): Int =" + " let fib (n: Int): Int =" " if n < 2 then n" " else fib (n - 1) + fib (n - 2)" " EOF" "" ] |> Stdlib.printLines + state + else if + Stdlib.String.startsWith + (Stdlib.String.trim inlineDefinition) + "fn " + then + [ (Colors.error "Dark function declarations start with `let`, not `fn`.") + "" + "Pass either a complete declaration:" + " let greet (name: String) : String = \"Hello, \" ++ name" + "" + "Or pass only the parameters, return type, and body:" + " (name: String) : String = \"Hello, \" ++ name" ] + |> Stdlib.printLines state else // Parse and create function immediately @@ -62,8 +76,18 @@ let createFnInline // Parse location relative to current position match Location.parseRelativeTo currentLocation locationStr with | Ok location -> - // Build full source code for parsing - let fullSource = $"let {location.name} {definition}" + // Inline definitions keep the concise historical form. Stdin/file callers + // may instead provide ordinary Dark source beginning with `let`; parsing + // the real declaration avoids a surprising synthetic `let name let name`. + let trimmedDefinition = Stdlib.String.trim definition + let fullSource = + if + Stdlib.String.startsWith trimmedDefinition "let " + || Stdlib.String.startsWith trimmedDefinition "///" + then + definition + else + $"let {location.name} {definition}" // Parse the function definition match Builtin.parserParseToWrittenTypes fullSource with @@ -81,6 +105,16 @@ let createFnInline | _ -> false) match found with + | Some(Function writtenFn) when writtenFn.name.name != location.name -> + let actual = writtenFn.name.name + Stdlib.println + (Colors.error + $"Function name `{actual}` does not match target `{location.name}`") + Stdlib.println + (Colors.hint + $"Rename the declaration to `let {location.name} ...` or change the fn target.") + state + | Some(Function writtenFn) -> // Convert to ProgramTypes.PackageFn let pm = LanguageTools.PackageManager.pm () @@ -99,7 +133,7 @@ let createFnInline writtenFn // Check for unresolved names - if Errors.reportUnresolvedNames unresolvedNames then + if Errors.reportUnresolvedNames state.currentBranchId unresolvedNames then state // no unresolved names - keep going else @@ -119,14 +153,14 @@ let createFnInline let fullPath = PrettyPrinter.ProgramTypes.PackageLocation.packageLocation location let action = if isUpdate then "Updated" else "Created" - Stdlib.printLine "" - Stdlib.printLine (Colors.success $"✓ {action} function: {fullPath}") + Stdlib.println "" + Stdlib.println (Colors.success $"✓ {action} function: {fullPath}") // Auto-propagate if this is an update if isUpdate then Propagate.autoPropagateAfterUpdate state.currentBranchId location packageFn.hash LanguageTools.ProgramTypes.ItemKind.Fn - Stdlib.printLine "" + Stdlib.println "" state | Error msg -> @@ -163,7 +197,7 @@ let createFnInline ] |> Stdlib.printLines state | Error msg -> - Stdlib.printLine (Colors.error $"Invalid location: {msg}") + Stdlib.println (Colors.error $"Invalid location: {msg}") state @@ -191,10 +225,12 @@ let help (state: Cli.AppState) : Cli.AppState = "" " # Multi-line body via stdin (run from shell, not the REPL — '-' reads stdin):" " ./scripts/run-cli fn fib - <<'EOF'" - " (n: Int): Int =" + " let fib (n: Int): Int =" " if n < 2 then n" " else fib (n - 1) + fib (n - 2)" " EOF" + " # Stdin accepts a complete `let` declaration; the shorter fragment" + " # `(n: Int): Int = ...` form remains valid too." "" "Location can be relative or absolute:" " fn helper (x: Int): Int = ... # Relative to current location" diff --git a/packages/darklang/cli/packages/hash.dark b/packages/darklang/cli/packages/hash.dark index d0951f2007..d42f088332 100644 --- a/packages/darklang/cli/packages/hash.dark +++ b/packages/darklang/cli/packages/hash.dark @@ -13,12 +13,12 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match filteredArgs with | [] -> - Stdlib.printLine "Usage: hash " + Stdlib.println "Usage: hash " state | locationStr :: _ -> match Location.parseRelativeTo state.packageData.currentLocation locationStr with | Error e -> - Stdlib.printLine (Colors.error e) + Stdlib.println (Colors.error e) state | Ok location -> let branchId = state.currentBranchId @@ -31,10 +31,10 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | Fn -> "fn" | Value -> "value" - Stdlib.printLine $"{kindStr} {formatHash hash long}" + Stdlib.println $"{kindStr} {formatHash hash long}" state | None -> - Stdlib.printLine (Colors.error "Not found at this location") + Stdlib.println (Colors.error "Not found at this location") state let help (state: Cli.AppState) : Cli.AppState = diff --git a/packages/darklang/cli/packages/listing.dark b/packages/darklang/cli/packages/listing.dark index fb8822f771..89a8005090 100644 --- a/packages/darklang/cli/packages/listing.dark +++ b/packages/darklang/cli/packages/listing.dark @@ -25,24 +25,24 @@ let listModule // Display module path let locationStr = Packages.formatLocation location - Stdlib.printLine $"Contents of {locationStr}:" - Stdlib.printLine "" + Stdlib.println $"Contents of {locationStr}:" + Stdlib.println "" // Display in order: modules, types, values, functions if Stdlib.Bool.not (Stdlib.List.isEmpty allSubmodules) then - Stdlib.printLine (Display.getSectionHeader "module") + Stdlib.println (Display.getSectionHeader "module") allSubmodules - |> Stdlib.List.iter (fun name -> Stdlib.printLine $" {name}/") - Stdlib.printLine "" + |> Stdlib.List.iter (fun name -> Stdlib.println $" {name}/") + Stdlib.println "" let printSection (header: String) (items: List<(String * LanguageTools.ProgramTypes.Hash)>) : Unit = if Stdlib.Bool.not (Stdlib.List.isEmpty items) then - Stdlib.printLine (Display.getSectionHeader header) + Stdlib.println (Display.getSectionHeader header) items |> Stdlib.List.iter (fun (name, hash) -> let mark = Query.deprecationMarker depSets hash - Stdlib.printLine $" {name}{mark}") - Stdlib.printLine "" + Stdlib.println $" {name}{mark}") + Stdlib.println "" printSection "type" types printSection "value" values @@ -52,7 +52,7 @@ let listModule let execute (state: AppState) (args: List) : AppState = let branchId = state.currentBranchId let includeDeprecated = - Stdlib.List.member args "--include-deprecated" + Stdlib.List.contains args "--include-deprecated" let positional = args |> Stdlib.List.filter (fun a -> a != "--include-deprecated") match positional with @@ -64,7 +64,7 @@ let execute (state: AppState) (args: List) : AppState = // Navigate to the path and list match Traversal.traverse branchId state.packageData.currentLocation pathArg with | Error errorMsg -> - Stdlib.printLine (Colors.error $"Cannot list: {errorMsg}") + Stdlib.println (Colors.error $"Cannot list: {errorMsg}") state | Ok newLocation -> match newLocation with @@ -73,8 +73,8 @@ let execute (state: AppState) (args: List) : AppState = state | Type _ | Value _ | Function _ -> let locationStr = Packages.formatLocation newLocation - Stdlib.printLine $"'{locationStr}' is not a module." - Stdlib.printLine "Use 'view' to see details of types, values, or functions." + Stdlib.println $"'{locationStr}' is not a module." + Stdlib.println "Use 'view' to see details of types, values, or functions." state | _ -> help state diff --git a/packages/darklang/cli/packages/module.dark b/packages/darklang/cli/packages/module.dark new file mode 100644 index 0000000000..2f724315cb --- /dev/null +++ b/packages/darklang/cli/packages/module.dark @@ -0,0 +1,392 @@ +module Darklang.Cli.Packages.ModuleCommand + + +type UpdatedItem = + { location: LanguageTools.ProgramTypes.PackageLocation + hash: LanguageTools.ProgramTypes.Hash + kind: LanguageTools.ProgramTypes.ItemKind } + + +let sfDeclToModuleDecl + (decl: LanguageTools.WrittenTypes.SourceFile.SourceFileDeclaration) + : LanguageTools.WrittenTypes.ModuleDeclaration.Declaration = + match decl with + | Type typ -> LanguageTools.WrittenTypes.ModuleDeclaration.Declaration.Type typ + | Function fn -> + LanguageTools.WrittenTypes.ModuleDeclaration.Declaration.Function fn + | Value value -> + LanguageTools.WrittenTypes.ModuleDeclaration.Declaration.Value value + | Module modul -> + LanguageTools.WrittenTypes.ModuleDeclaration.Declaration.SubModule modul + + +let collectDecls + (modules: List) + (decls: List) + : List + * LanguageTools.WrittenTypes.ModuleDeclaration.Declaration> = + decls + |> Stdlib.List.map (fun decl -> + match decl with + | SubModule modul -> + let nestedModules = + Stdlib.List.append modules [ Stdlib.Tuple2.second modul.name ] + collectDecls nestedModules modul.declarations + | _ -> [ (modules, decl) ]) + |> Stdlib.List.flatten + + +let execute (state: Cli.AppState) (args: List) : Cli.AppState = + let input = + match args with + | [ modulePath; "-" ] -> + Stdlib.Result.Result.Ok((modulePath, Builtin.stdinReadAll ())) + | [ modulePath; sourcePath ] -> + match Stdlib.Cli.File.readText sourcePath with + | Ok source -> Stdlib.Result.Result.Ok((modulePath, source)) + | Error err -> + Stdlib.Result.Result.Error + $"Could not read '{sourcePath}': {err.message}" + | _ -> + Stdlib.Result.Result.Error + "Usage: module " + + match input with + | Error message -> + Stdlib.println (Colors.error message) + state + | Ok((modulePath, source)) -> + // Package input is declaration scope at its root. In particular, `val x` + // declares a package value while `let x` is diagnosed directly instead of + // being consumed as a script binding and failing later at EOF. + let diagnostics = Builtin.parserParsePackageDiagnostics source + if Errors.reportParseErrors diagnostics then + state + else + match Builtin.parserParsePackageToWrittenTypes source with + | None -> + Stdlib.println (Colors.error "Failed to parse package source") + state + | Some(SourceFile sourceFile) -> + let declarations = + let moduleDecls = + sourceFile.declarations + |> Stdlib.List.map sfDeclToModuleDecl + collectDecls [] moduleDecls + + if Stdlib.List.isEmpty declarations then + Stdlib.println (Colors.error "Package source contains no declarations") + state + else + // Parse a synthetic item below the requested module. This uses the + // same absolute/relative location rules as `fn`, `type`, and `val`. + let locationInput = modulePath ++ ".__module__" + match + Location.parseRelativeTo + state.packageData.currentLocation + locationInput + with + | Error message -> + Stdlib.println (Colors.error $"Invalid module location: {message}") + state + | Ok baseLocation -> + let basePm = LanguageTools.PackageManager.pm () + let branchId = state.currentBranchId + + // First pass: build provisional entities with unresolved references. + // Add all of them to an in-memory PM so the final pass can + // resolve forward references and cycles without writing anything. + let extraTypes = + declarations + |> Stdlib.List.filterMap (fun (extraModules, decl) -> + match decl with + | Type typeDecl -> + let modules = + Stdlib.List.append baseLocation.modules extraModules + let location = + LanguageTools.ProgramTypes.PackageLocation + { owner = baseLocation.owner + modules = modules + name = typeDecl.name.name } + let ctx = + LanguageTools.NameResolver.NRContext + { onMissing = LanguageTools.NameResolver.OnMissing.Allow + pm = basePm + branchId = branchId + owner = baseLocation.owner + currentModule = modules } + let (entity, _) = + LanguageTools.WrittenTypesToProgramTypes.TypeDeclaration.toPackageTypePT + ctx + typeDecl + Stdlib.Option.Option.Some + (LanguageTools.ProgramTypes.LocatedItem + { entity = entity; location = location }) + | _ -> Stdlib.Option.Option.None) + + let extraFns = + declarations + |> Stdlib.List.filterMap (fun (extraModules, decl) -> + match decl with + | Function fnDecl -> + let modules = + Stdlib.List.append baseLocation.modules extraModules + let location = + LanguageTools.ProgramTypes.PackageLocation + { owner = baseLocation.owner + modules = modules + name = fnDecl.name.name } + let ctx = + LanguageTools.NameResolver.NRContext + { onMissing = LanguageTools.NameResolver.OnMissing.Allow + pm = basePm + branchId = branchId + owner = baseLocation.owner + currentModule = modules } + let (entity, _) = + LanguageTools.WrittenTypesToProgramTypes.FunctionDeclaration.toPackageFnPT + ctx + fnDecl + Stdlib.Option.Option.Some + (LanguageTools.ProgramTypes.LocatedItem + { entity = entity; location = location }) + | _ -> Stdlib.Option.Option.None) + + let extraValues = + declarations + |> Stdlib.List.filterMap (fun (extraModules, decl) -> + match decl with + | Value valueDecl -> + let modules = + Stdlib.List.append baseLocation.modules extraModules + let location = + LanguageTools.ProgramTypes.PackageLocation + { owner = baseLocation.owner + modules = modules + name = valueDecl.name.name } + let ctx = + LanguageTools.NameResolver.NRContext + { onMissing = LanguageTools.NameResolver.OnMissing.Allow + pm = basePm + branchId = branchId + owner = baseLocation.owner + currentModule = modules } + let (entity, _) = + LanguageTools.WrittenTypesToProgramTypes.ValueDeclaration.toPackageValuePT + ctx + valueDecl + Stdlib.Option.Option.Some + (LanguageTools.ProgramTypes.LocatedItem + { entity = entity; location = location }) + | _ -> Stdlib.Option.Option.None) + + let pm = + LanguageTools.ProgramTypes.PackageManager.withExtras + basePm + extraTypes + extraValues + extraFns + + let preparedTypes = + declarations + |> Stdlib.List.filterMap (fun (extraModules, decl) -> + match decl with + | Type typeDecl -> + let modules = + Stdlib.List.append baseLocation.modules extraModules + let location = + LanguageTools.ProgramTypes.PackageLocation + { owner = baseLocation.owner + modules = modules + name = typeDecl.name.name } + let ctx = + LanguageTools.NameResolver.NRContext + { onMissing = LanguageTools.NameResolver.OnMissing.Allow + pm = pm + branchId = branchId + owner = baseLocation.owner + currentModule = modules } + let (packageType, unresolved) = + LanguageTools.WrittenTypesToProgramTypes.TypeDeclaration.toPackageTypePT + ctx + typeDecl + let previous = + Propagate.getAllPreviousHashes + branchId + location + LanguageTools.ProgramTypes.ItemKind.Type + let update = + if Stdlib.List.isEmpty previous then + Stdlib.Option.Option.None + else + Stdlib.Option.Option.Some + (UpdatedItem + { location = location + hash = packageType.hash + kind = LanguageTools.ProgramTypes.ItemKind.Type }) + Stdlib.Option.Option.Some( + ( [ LanguageTools.ProgramTypes.PackageOp.AddType packageType + LanguageTools.ProgramTypes.PackageOp.SetName + ( location, + LanguageTools.ProgramTypes.Reference.PackageType + packageType.hash ) ], + unresolved, + update ) ) + | _ -> Stdlib.Option.Option.None) + + let preparedFns = + declarations + |> Stdlib.List.filterMap (fun (extraModules, decl) -> + match decl with + | Function fnDecl -> + let modules = + Stdlib.List.append baseLocation.modules extraModules + let location = + LanguageTools.ProgramTypes.PackageLocation + { owner = baseLocation.owner + modules = modules + name = fnDecl.name.name } + let ctx = + LanguageTools.NameResolver.NRContext + { onMissing = LanguageTools.NameResolver.OnMissing.Allow + pm = pm + branchId = branchId + owner = baseLocation.owner + currentModule = modules } + let (packageFn, unresolved) = + LanguageTools.WrittenTypesToProgramTypes.FunctionDeclaration.toPackageFnPT + ctx + fnDecl + let previous = + Propagate.getAllPreviousHashes + branchId + location + LanguageTools.ProgramTypes.ItemKind.Fn + let update = + if Stdlib.List.isEmpty previous then + Stdlib.Option.Option.None + else + Stdlib.Option.Option.Some + (UpdatedItem + { location = location + hash = packageFn.hash + kind = LanguageTools.ProgramTypes.ItemKind.Fn }) + Stdlib.Option.Option.Some( + ( [ LanguageTools.ProgramTypes.PackageOp.AddFn packageFn + LanguageTools.ProgramTypes.PackageOp.SetName + ( location, + LanguageTools.ProgramTypes.Reference.PackageFn + packageFn.hash ) ], + unresolved, + update ) ) + | _ -> Stdlib.Option.Option.None) + + let preparedValues = + declarations + |> Stdlib.List.filterMap (fun (extraModules, decl) -> + match decl with + | Value valueDecl -> + let modules = + Stdlib.List.append baseLocation.modules extraModules + let location = + LanguageTools.ProgramTypes.PackageLocation + { owner = baseLocation.owner + modules = modules + name = valueDecl.name.name } + let ctx = + LanguageTools.NameResolver.NRContext + { onMissing = LanguageTools.NameResolver.OnMissing.Allow + pm = pm + branchId = branchId + owner = baseLocation.owner + currentModule = modules } + let (packageValue, unresolved) = + LanguageTools.WrittenTypesToProgramTypes.ValueDeclaration.toPackageValuePT + ctx + valueDecl + let previous = + Propagate.getAllPreviousHashes + branchId + location + LanguageTools.ProgramTypes.ItemKind.Value + let update = + if Stdlib.List.isEmpty previous then + Stdlib.Option.Option.None + else + Stdlib.Option.Option.Some + (UpdatedItem + { location = location + hash = packageValue.hash + kind = LanguageTools.ProgramTypes.ItemKind.Value }) + Stdlib.Option.Option.Some( + ( [ LanguageTools.ProgramTypes.PackageOp.AddValue packageValue + LanguageTools.ProgramTypes.PackageOp.SetName + ( location, + LanguageTools.ProgramTypes.Reference.PackageValue + packageValue.hash ) ], + unresolved, + update ) ) + | _ -> Stdlib.Option.Option.None) + + let prepared = + Stdlib.List.append + preparedTypes + (Stdlib.List.append preparedFns preparedValues) + let ops = + prepared + |> Stdlib.List.map (fun (itemOps, _, _) -> itemOps) + |> Stdlib.List.flatten + let unresolvedNames = + prepared + |> Stdlib.List.map (fun (_, unresolved, _) -> unresolved) + |> Stdlib.List.flatten + let updates = + prepared + |> Stdlib.List.filterMap (fun (_, _, update) -> update) + + if Errors.reportUnresolvedNames branchId unresolvedNames then + state + else + match SCM.PackageOps.add branchId ops with + | Error message -> + Stdlib.println + (Colors.error $"Failed to define module: {message}") + state + | Ok _ -> + updates + |> Stdlib.List.iter (fun update -> + Propagate.autoPropagateAfterUpdate + branchId + update.location + update.hash + update.kind) + let count = Stdlib.List.length prepared + Stdlib.println + (Colors.success + $"✓ Defined {Stdlib.Int.toString count} declarations in {modulePath}") + state + + +let help (state: Cli.AppState) : Cli.AppState = + [ "Define a module from multiple package declarations atomically" + "" + "Usage: module " + "" + "The source may contain functions, types, values, and nested modules." + "All declarations are validated before a single package operation is added." + "" + "Example:" + " ./scripts/run-cli module /Darklang.Example - <<'EOF'" + " val greeting = \"hello\"" + " let double (n: Int) : Int = n * 2" + " let quadruple (n: Int) : Int = double (double n)" + " EOF" + ] |> Stdlib.printLines + state + + +let complete + (_state: Cli.AppState) + (_args: List) + : List = + [] diff --git a/packages/darklang/cli/packages/nav.dark b/packages/darklang/cli/packages/nav.dark index ca7c7caf30..abfde984e4 100644 --- a/packages/darklang/cli/packages/nav.dark +++ b/packages/darklang/cli/packages/nav.dark @@ -31,7 +31,7 @@ let execute (state: AppState) (args: List) : AppState = currentPage = Page.InteractiveNav session prompt = Prompt.Editing.clear state.prompt } | Error message -> - Stdlib.printLine message + Stdlib.println message state | [pathArg] -> @@ -40,12 +40,12 @@ let execute (state: AppState) (args: List) : AppState = match Traversal.traverse branchId state.packageData.currentLocation pathArg with | Error errorMsg -> // Navigation failed - show error and don't change location - Stdlib.printLine (Colors.error $"Navigation failed: {errorMsg}") + Stdlib.println (Colors.error $"Navigation failed: {errorMsg}") state | Ok newLocation -> // Navigation succeeded - update state using navTo let locationStr = Packages.formatLocation newLocation - Stdlib.printLine (Colors.success $"Changed to: {locationStr}") + Stdlib.println (Colors.success $"Changed to: {locationStr}") navTo state newLocation | _ -> help state diff --git a/packages/darklang/cli/packages/navInteractive.dark b/packages/darklang/cli/packages/navInteractive.dark index b380b9e5f5..ae17749c13 100644 --- a/packages/darklang/cli/packages/navInteractive.dark +++ b/packages/darklang/cli/packages/navInteractive.dark @@ -696,7 +696,7 @@ let selectAndExit Darklang.Cli.Tui.TerminalSession.stop session.terminal let newState = Nav.navTo state selectedItem.location let locationStr = Packages.formatLocation selectedItem.location - Stdlib.printLine (Colors.success $"Selected: {locationStr}") + Stdlib.println (Colors.success $"Selected: {locationStr}") { newState with currentPage = Page.MainPrompt prompt = Prompt.Editing.clear newState.prompt } diff --git a/packages/darklang/cli/packages/propagate.dark b/packages/darklang/cli/packages/propagate.dark index f27f787a43..b437ec2235 100644 --- a/packages/darklang/cli/packages/propagate.dark +++ b/packages/darklang/cli/packages/propagate.dark @@ -190,9 +190,9 @@ let warnIfSignatureChanged if Stdlib.Bool.not (fnSignatureEqual branchId oldF newF) then let oldSig = PrettyPrinter.ProgramTypes.PackageFn.signature ctx oldF let newSig = PrettyPrinter.ProgramTypes.PackageFn.signature ctx newF - Stdlib.printLine (Colors.warning " Warning: function signature changed") - Stdlib.printLine (Colors.warning $" was: {oldSig}") - Stdlib.printLine (Colors.warning $" now: {newSig}") + Stdlib.println (Colors.warning " Warning: function signature changed") + Stdlib.println (Colors.warning $" was: {oldSig}") + Stdlib.println (Colors.warning $" now: {newSig}") true else false @@ -207,9 +207,9 @@ let warnIfSignatureChanged if Stdlib.Bool.not (typeDeclarationEqual branchId oldT.declaration newT.declaration) then let oldDef = PrettyPrinter.ProgramTypes.customType ctx oldT.declaration let newDef = PrettyPrinter.ProgramTypes.customType ctx newT.declaration - Stdlib.printLine (Colors.warning " Warning: type definition changed") - Stdlib.printLine (Colors.warning $" was: {oldDef}") - Stdlib.printLine (Colors.warning $" now: {newDef}") + Stdlib.println (Colors.warning " Warning: type definition changed") + Stdlib.println (Colors.warning $" was: {oldDef}") + Stdlib.println (Colors.warning $" now: {newDef}") true else false @@ -244,7 +244,7 @@ let propagateFromHashes | [] -> () | _ -> let count = Stdlib.List.length repoints - Stdlib.printLine $" Propagated to {Stdlib.Int.toString count} dependents:" + Stdlib.println $" Propagated to {Stdlib.Int.toString count} dependents:" let ids = repoints |> Stdlib.List.map (fun r -> LanguageTools.ProgramTypes.Reference.hash r.toRef) |> Stdlib.List.unique let namesDict = Deps.resolveNames branchId ids @@ -253,21 +253,21 @@ let propagateFromHashes |> Stdlib.List.iter (fun repoint -> let name = Deps.getName namesDict (LanguageTools.ProgramTypes.Reference.hash repoint.toRef) let kindStr = itemKindToString (LanguageTools.ProgramTypes.Reference.kind repoint.toRef) - Stdlib.printLine $" [{kindStr}] {name}") + Stdlib.println $" [{kindStr}] {name}") if signatureChanged then - Stdlib.printLine "" - Stdlib.printLine + Stdlib.println "" + Stdlib.println (Colors.warning " These callers may need manual updates. To review:") repoints |> Stdlib.List.iter (fun repoint -> let name = Deps.getName namesDict (LanguageTools.ProgramTypes.Reference.hash repoint.toRef) let kindStr = itemKindToString (LanguageTools.ProgramTypes.Reference.kind repoint.toRef) - Stdlib.printLine (Colors.hint $" view {kindStr} {name}")) + Stdlib.println (Colors.hint $" view {kindStr} {name}")) | Error errMsg -> - Stdlib.printLine (Colors.error $" Auto-propagation failed: {errMsg}") + Stdlib.println (Colors.error $" Auto-propagation failed: {errMsg}") /// Auto-propagate after creating/updating a definition. diff --git a/packages/darklang/cli/packages/search.dark b/packages/darklang/cli/packages/search.dark index 0b133b23e6..f041a2d566 100644 --- a/packages/darklang/cli/packages/search.dark +++ b/packages/darklang/cli/packages/search.dark @@ -3,6 +3,10 @@ module Darklang.Cli.Packages.Search // Max results rendered per entity type. val displayCap = 20 +// Batch search is meant for planning several API choices in one round-trip. +// Keep each answer small enough that ten related queries are still readable. +val batchDisplayCap = 3 + // Operator symbols aren't function names, so a literal search for one finds // nothing useful. Map each to the keyword that surfaces its named operation — @@ -39,6 +43,10 @@ let matchRank (query: String) (name: String) : Int = let q = Stdlib.String.toLowercase query let n = Stdlib.String.toLowercase name if n == q then 0 + // Concept searches are often ` `, for example + // `string toList` and `json parse`. Once the DB has matched the module token, + // put the item whose name is the final token ahead of incidental doc hits. + else if Stdlib.String.endsWith q (" " ++ n) then 1 else if Stdlib.String.startsWith q n then 1 // query elaborates name: parseInt ⊃ parse else if Stdlib.String.startsWith n q then 2 // name elaborates query: parseIntLiteral ⊃ parseInt else if Stdlib.String.contains n q then 3 @@ -50,17 +58,86 @@ let isStdlib (owner: String) (modules: List) : Bool = && (Stdlib.List.head modules == Stdlib.Option.Option.Some "Stdlib") -// Sort key: match quality first, then Stdlib-first, then name. Both ranks are a -// single digit, so plain string ordering under `sortBy` gives the right result. +// Give ` ` intent queries their strongest rank only when +// both components match directly. This puts `Json.parse` ahead of +// `AltJson.parse` for `json parse`, while both can remain in the result set. +let itemMatchRank + (query: String) + (modules: List) + (name: String) + : Int = + let moduleName = Stdlib.Option.withDefault (Stdlib.List.last modules) "" + let direct = + Stdlib.String.toLowercase (moduleName ++ " " ++ name) + + let q = Stdlib.String.toLowercase query + let n = Stdlib.String.toLowercase name + let compactQuery = Stdlib.String.replaceAll q " " "" + let queryTokens = + q + |> Stdlib.String.replaceAll "." " " + |> Stdlib.String.split " " + |> Stdlib.List.filter (fun token -> + Stdlib.Bool.not (Stdlib.String.isEmpty token)) + let finalQueryToken = + Stdlib.Option.withDefault (Stdlib.List.last queryTokens) "" + let qualifiedPath = + [ modules; [name] ] + |> Stdlib.List.flatten + |> Stdlib.String.join "." + |> Stdlib.String.toLowercase + let nestedModuleOperation = + (Stdlib.List.length queryTokens > 1) + && (Stdlib.String.startsWith n finalQueryToken) + && (Stdlib.List.all queryTokens (fun token -> + Stdlib.String.contains qualifiedPath token)) + + let preferredTextFileFn = + (q == "file read" && n == "readtext") + || (q == "file write" && n == "writetext") + + if preferredTextFileFn || direct == q || compactQuery == n then + 0 + else if nestedModuleOperation then + 1 + else + matchRank query name + + +// Generic numeric searches should lead with the default arbitrary-precision +// `Int`, not a fixed-width variant which merely happened to sort first. Keep +// non-numeric modules between the default and fixed-width numeric modules so +// useful peers such as `String.random` are not buried with the variants. +let numericModuleRank (modules: List) : String = + let moduleName = Stdlib.Option.withDefault (Stdlib.List.last modules) "" + if moduleName == "Int" then + "0" + else if + Stdlib.String.startsWith moduleName "Int" + || Stdlib.String.startsWith moduleName "UInt" + then + "2" + else + "1" + + +// Sort key: match quality first, then Stdlib-first, default-numeric preference, +// and finally the full path for deterministic ordering. All ranks are a single +// digit, so plain string ordering under `sortBy` gives the intended result. let sortKey (query: String) (owner: String) (modules: List) (name: String) : String = - let mr = Stdlib.Int.toString (matchRank query name) + let mr = Stdlib.Int.toString (itemMatchRank query modules name) let st = if isStdlib owner modules then "0" else "1" - $"{mr}{st}:{name}" + let nt = numericModuleRank modules + let path = + [ [owner]; modules; [name] ] + |> Stdlib.List.flatten + |> Stdlib.String.join "." + $"{mr}{st}{nt}:{path}" // Used to rank result sections; empty sections sort last. @@ -70,7 +147,8 @@ let bestItemRank : Int = items |> Stdlib.List.fold 9 (fun acc item -> - let r = matchRank query item.location.name + let r = + itemMatchRank query item.location.modules item.location.name if r < acc then r else acc) @@ -97,16 +175,34 @@ let withDoc (headline: String) (desc: String) : String = if Stdlib.String.isEmpty doc then headline else headline ++ "\n" ++ doc -// A rendered result line, with its doc comment appended only under --with-docs. -// The trailing blank line separates multi-line doc entries. -let withOptionalDoc (withDocs: Bool) (headline: String) (desc: String) : String = - if withDocs then (withDoc headline desc) ++ "\n" else headline +let docSummary (desc: String) : String = + desc + |> Stdlib.String.splitOnNewline + |> Stdlib.List.head + |> Stdlib.Option.withDefault "" + + +// `--with-docs` still renders complete comments. Without it, an exact or +// otherwise strongest-ranked match gets one summary line: enough to choose an +// API without making every weak fuzzy hit verbose. +let withOptionalDoc + (withDocs: Bool) + (showSummary: Bool) + (headline: String) + (desc: String) + : String = + if withDocs then + (withDoc headline desc) ++ "\n" + else if showSummary then + (withDoc headline (docSummary desc)) ++ "\n" + else + headline let overflowNote (total: Int) (shown: Int) : Unit = let extra = total - shown if extra > 0 then - Stdlib.printLine ( + Stdlib.println ( Colors.hint $" ... and {Stdlib.Int.toString extra} more (narrow with --exact or `nav` into a module)") else @@ -138,26 +234,28 @@ let contextFor let renderSection (header: String) (query: String) + (cap: Int) (items: List>) (renderItem: LanguageTools.ProgramTypes.LocatedItem<'a> -> String) : Unit = if Stdlib.List.isEmpty items then () else - Stdlib.printLine (Display.getSectionHeader header) + Stdlib.println (Display.getSectionHeader header) let sorted = items |> Stdlib.List.sortBy (fun i -> sortKey query i.location.owner i.location.modules i.location.name) - let shown = Stdlib.List.take sorted displayCap - shown |> Stdlib.List.iter (fun item -> Stdlib.printLine (renderItem item)) + let shown = Stdlib.List.take sorted cap + shown |> Stdlib.List.iter (fun item -> Stdlib.println (renderItem item)) overflowNote (Stdlib.List.length sorted) (Stdlib.List.length shown) - Stdlib.printLine "" + Stdlib.println "" type ParsedArgs = { queryParts: List entityTypes: List + batch: Bool exactMatch: Bool withDocs: Bool shallow: Bool @@ -173,6 +271,7 @@ val argSpec = [ ("--type", []) ("--fn", [ "--function" ]) ("--val", [ "--value" ]) + ("--batch", []) ("--exact", []) ("--shallow", []) ("--with-docs", []) @@ -202,6 +301,7 @@ let parseArgs (args: List) : ParsedArgs = ParsedArgs { queryParts = p.positionals entityTypes = entityTypes + batch = FlagParser.hasFlag p "--batch" exactMatch = FlagParser.hasFlag p "--exact" withDocs = FlagParser.hasFlag p "--with-docs" shallow = FlagParser.hasFlag p "--shallow" @@ -210,10 +310,12 @@ let parseArgs (args: List) : ParsedArgs = val usage = - "Usage: search [--type] [--fn] [--val] [--exact] [--shallow] [--with-docs] [--include-deprecated]" + "Usage: search [--batch] [--type] [--fn] [--val] [--exact] [--shallow] [--with-docs] [--include-deprecated]" -let execute (state: Cli.AppState) (args: List) : Cli.AppState = +// Execute one query. `execute` below fans batch arguments out through this +// function so ranking, filtering, and rendering stay identical in both modes. +let executeQuery (state: Cli.AppState) (args: List) : Cli.AppState = match args with | [] -> // Enter interactive search mode (nav with search enabled) @@ -225,27 +327,27 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = currentPage = Page.InteractiveNav session prompt = Prompt.Editing.clear state.prompt } | Error message -> - Stdlib.printLine message + Stdlib.println message state | _ -> let parsed = parseArgs args if Stdlib.Bool.not (Stdlib.List.isEmpty parsed.parseErrors) then - Stdlib.printLine (Colors.error (FlagParser.parseErrorsMessage parsed.parseErrors)) - Stdlib.printLine "" - Stdlib.printLine usage + Stdlib.println (Colors.error (FlagParser.parseErrorsMessage parsed.parseErrors)) + Stdlib.println "" + Stdlib.println usage state else let rawQueryText = Stdlib.String.join parsed.queryParts " " - let aliasKeyword = operatorAlias rawQueryText - let queryText = Stdlib.Option.withDefault aliasKeyword rawQueryText + let operatorKeyword = operatorAlias rawQueryText + let queryText = Stdlib.Option.withDefault operatorKeyword rawQueryText if Stdlib.String.isEmpty queryText then - Stdlib.printLine (Colors.error "Error: Search query cannot be empty") - Stdlib.printLine "" - Stdlib.printLine usage + Stdlib.println (Colors.error "Error: Search query cannot be empty") + Stdlib.println "" + Stdlib.println usage state else let searchDepth = @@ -272,7 +374,10 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = // Only count submodules when no entity type filter is specified let submodulesCount = - if Stdlib.List.isEmpty parsed.entityTypes then + if + (Stdlib.Bool.not parsed.batch) + && (Stdlib.List.isEmpty parsed.entityTypes) + then Stdlib.List.length results.submodules else 0 @@ -284,20 +389,25 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = + submodulesCount if totalResults == 0 then - Stdlib.printLine (Colors.dimText $"No results found for: {queryText}") - Stdlib.printLine "" + Stdlib.println (Colors.dimText $"No results found for: {queryText}") + Stdlib.println "" else - Stdlib.printLine (Colors.boldText $"Search results for: {queryText}") - match aliasKeyword with + Stdlib.println (Colors.boldText $"Search results for: {queryText}") + match operatorKeyword with | Some kw -> - Stdlib.printLine ( + Stdlib.println ( Colors.hint $" (operator {rawQueryText} maps to the \"{kw}\" operation)") | None -> () - Stdlib.printLine "" + Stdlib.println "" // Key format: match rank followed by the fixed section order. + let resultCap = if parsed.batch then batchDisplayCap else displayCap + let moduleRank = - if Stdlib.List.isEmpty parsed.entityTypes then + if + (Stdlib.Bool.not parsed.batch) + && (Stdlib.List.isEmpty parsed.entityTypes) + then bestModuleRank queryText results.submodules else 9 @@ -320,25 +430,47 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = |> Stdlib.List.iter (fun tag -> match tag with | "function" -> - renderSection "function" queryText results.fns (fun item -> + renderSection "function" queryText resultCap results.fns (fun item -> let mark = Query.deprecationMarker depSets item.entity.hash let ctx = contextFor state.currentBranchId item let sigBody = Signatures.formatFn ctx item.entity let line = $"{fullPathOf item} {Colors.dimText sigBody}{mark}" - withOptionalDoc parsed.withDocs line item.entity.description) + let showSummary = + itemMatchRank queryText item.location.modules item.location.name == 0 + withOptionalDoc + parsed.withDocs + showSummary + line + item.entity.description) | "value" -> - renderSection "value" queryText results.values (fun item -> + renderSection "value" queryText resultCap results.values (fun item -> let mark = Query.deprecationMarker depSets item.entity.hash - withOptionalDoc parsed.withDocs $"{fullPathOf item}{mark}" item.entity.description) + let showSummary = + itemMatchRank queryText item.location.modules item.location.name == 0 + withOptionalDoc + parsed.withDocs + showSummary + $"{fullPathOf item}{mark}" + item.entity.description) | "type" -> - renderSection "type" queryText results.types (fun item -> + renderSection "type" queryText resultCap results.types (fun item -> let mark = Query.deprecationMarker depSets item.entity.hash let ctx = contextFor state.currentBranchId item let defn = Signatures.formatType ctx (fullPathOf item) item.entity - withOptionalDoc parsed.withDocs $"{defn}{mark}" item.entity.description) + let showSummary = + itemMatchRank queryText item.location.modules item.location.name == 0 + withOptionalDoc + parsed.withDocs + showSummary + $"{defn}{mark}" + item.entity.description) | "module" -> - if (Stdlib.List.isEmpty parsed.entityTypes) && (Stdlib.Bool.not (Stdlib.List.isEmpty results.submodules)) then - Stdlib.printLine (Display.getSectionHeader "module") + if + (Stdlib.Bool.not parsed.batch) + && (Stdlib.List.isEmpty parsed.entityTypes) + && (Stdlib.Bool.not (Stdlib.List.isEmpty results.submodules)) + then + Stdlib.println (Display.getSectionHeader "module") let sortedMods = results.submodules |> Stdlib.List.sortBy (fun modulePath -> @@ -346,26 +478,63 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = let owner = Stdlib.Option.withDefault (Stdlib.List.head modulePath) "" let mods = Stdlib.Option.withDefault (Stdlib.List.tail modulePath) [] sortKey queryText owner mods modName) - let shownMods = Stdlib.List.take sortedMods displayCap + let shownMods = Stdlib.List.take sortedMods resultCap shownMods |> Stdlib.List.iter (fun modulePath -> let moduleStr = Stdlib.String.join modulePath "." - Stdlib.printLine moduleStr) + Stdlib.println moduleStr) overflowNote (Stdlib.List.length sortedMods) (Stdlib.List.length shownMods) - Stdlib.printLine "" + Stdlib.println "" else () | _ -> ()) // show hint about --with-docs if results have descriptions but weren't shown - if Stdlib.Bool.not parsed.withDocs then - Stdlib.printLine (Colors.hint " (add --with-docs to show doc comments)") + if (Stdlib.Bool.not parsed.withDocs) && (Stdlib.Bool.not parsed.batch) then + Stdlib.println (Colors.hint " (add --with-docs to show doc comments)") else () state +let execute (state: Cli.AppState) (args: List) : Cli.AppState = + match args with + | [] -> executeQuery state args + | _ -> + let parsed = parseArgs args + if + (Stdlib.Bool.not parsed.batch) + || (Stdlib.Bool.not (Stdlib.List.isEmpty parsed.parseErrors)) + then + executeQuery state args + else if Stdlib.List.isEmpty parsed.queryParts then + Stdlib.println (Colors.error "Error: Batch search requires at least one query") + Stdlib.println "" + Stdlib.println usage + state + else + // FlagParser has no value-taking flags in this command, so retaining the + // original flag arguments is sufficient to give every query the same + // filters. Each positional remains one query even when it contains spaces. + let flags = + args + |> Stdlib.List.filter (fun arg -> Stdlib.String.startsWith arg "--") + + parsed.queryParts + |> Stdlib.List.iter (fun query -> + let queryArgs = Stdlib.List.push flags query + let _ = executeQuery state queryArgs + ()) + + if Stdlib.Bool.not parsed.withDocs then + Stdlib.println (Colors.hint " (add --with-docs to show doc comments)") + else + () + + state + + let help (state: Cli.AppState) : Cli.AppState = [ "Search for packages, types, functions, and values" @@ -376,6 +545,7 @@ let help (state: Cli.AppState) : Cli.AppState = "With query: Execute search and display results" "" "Options:" + " --batch Treat each quoted query as an independent search (3 results each)" " --type Search only types" " --fn Search only functions" " --val Search only values" @@ -390,6 +560,8 @@ let help (state: Cli.AppState) : Cli.AppState = " search List --shallow # Find all entities matching 'List' (direct descendants only)" " search map --fn # Find functions matching 'map' (with param names)" " search map --fn --with-docs # ...and doc comments too" + " search --batch \"file read\" \"file write\" \"environment variable\" --fn" + " # Search several API concepts in one command" " search String --type # Find types matching 'String'" " search parseJson --exact # Find exact match for 'parseJson'" " search Http --fn --val # Find functions and values matching 'Http'" @@ -408,7 +580,7 @@ let complete (_state: Cli.AppState) (args: List) : List if Stdlib.String.startsWith lastArg "--" then - ["--type"; "--fn"; "--val"; "--exact"; "--shallow"; "--with-docs"; "--include-deprecated"] + ["--batch"; "--type"; "--fn"; "--val"; "--exact"; "--shallow"; "--with-docs"; "--include-deprecated"] |> Stdlib.List.filter (fun flag -> Stdlib.String.startsWith flag lastArg) |> Stdlib.List.map Cli.Completion.simple else diff --git a/packages/darklang/cli/packages/signatures.dark b/packages/darklang/cli/packages/signatures.dark index 906b2eacb6..557318f2c0 100644 --- a/packages/darklang/cli/packages/signatures.dark +++ b/packages/darklang/cli/packages/signatures.dark @@ -257,20 +257,20 @@ let executeFiltered | None -> $"# {rootName}" | Some t -> $"# {rootName} (filter: {t})" - Stdlib.printLine title - Stdlib.printLine "" - Stdlib.printLine (Stdlib.String.join sections "\n\n") - Stdlib.printLine "" + Stdlib.println title + Stdlib.println "" + Stdlib.println (Stdlib.String.join sections "\n\n") + Stdlib.println "" let typesNote = if includeTypes then $", {Stdlib.Int.toString totalTypes} types" else "" let summary = $"{Stdlib.Int.toString totalFns} fns{typesNote} across {Stdlib.Int.toString (Stdlib.List.length allKeys)} modules" - Stdlib.printLine (Colors.hint summary) + Stdlib.println (Colors.hint summary) if Stdlib.Bool.not includeTypes then - Stdlib.printLine (Colors.hint "Use --types to include type definitions. `view ` shows source/docs.") + Stdlib.println (Colors.hint "Use --types to include type definitions. `view ` shows source/docs.") state @@ -291,7 +291,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = && (Stdlib.Bool.not parsed.all) if Stdlib.Bool.not (Stdlib.List.isEmpty parsed.parseErrors) then - Stdlib.printLine (Colors.error (FlagParser.parseErrorsMessage parsed.parseErrors)) + Stdlib.println (Colors.error (FlagParser.parseErrorsMessage parsed.parseErrors)) help state else if isUnscoped then Darklang.Cli.Docs.StdlibOverview.execute state args diff --git a/packages/darklang/cli/packages/tree.dark b/packages/darklang/cli/packages/tree.dark index 45693ea3d3..ef09f21d94 100644 --- a/packages/darklang/cli/packages/tree.dark +++ b/packages/darklang/cli/packages/tree.dark @@ -67,10 +67,10 @@ val tocEntries = // Display the table of contents let displayTableOfContents (): Unit = - Stdlib.printLine "" - Stdlib.printLine (Colors.colorize Colors.bold "DARKLANG PACKAGE TABLE OF CONTENTS") - Stdlib.printLine (Colors.colorize Colors.bold "===================================") - Stdlib.printLine "" + Stdlib.println "" + Stdlib.println (Colors.colorize Colors.bold "DARKLANG PACKAGE TABLE OF CONTENTS") + Stdlib.println (Colors.colorize Colors.bold "===================================") + Stdlib.println "" // Group entries by category let grouped = @@ -98,27 +98,27 @@ let displayTableOfContents (): Unit = |> Stdlib.List.iter (fun category -> match Stdlib.Dict.get grouped category with | Some entries -> - Stdlib.printLine (Colors.colorize Colors.bold category) + Stdlib.println (Colors.colorize Colors.bold category) entries |> Stdlib.List.iter (fun entry -> let pathDisplay = Colors.colorize Colors.cyan entry.path let descDisplay = Colors.colorize Colors.dim entry.description - Stdlib.printLine $" {pathDisplay}" - Stdlib.printLine $" {descDisplay}") + Stdlib.println $" {pathDisplay}" + Stdlib.println $" {descDisplay}") - Stdlib.printLine "" + Stdlib.println "" | None -> ()) - Stdlib.printLine (Colors.hint "Use 'tree ' to explore a specific package") - Stdlib.printLine (Colors.hint "Use 'tree --depth=N' for deeper views (max 10)") + Stdlib.println (Colors.hint "Use 'tree ' to explore a specific package") + Stdlib.println (Colors.hint "Use 'tree --depth=N' for deeper views (max 10)") let execute (state: AppState) (args: List): AppState = // Check for --toc flag let showToc = Stdlib.List.any args (fun arg -> arg == "--toc") let includeDeprecated = - Stdlib.List.member args "--include-deprecated" + Stdlib.List.contains args "--include-deprecated" if showToc then displayTableOfContents () @@ -155,8 +155,8 @@ let execute (state: AppState) (args: List): AppState = // Display tree let locationStr = Packages.formatLocation startLocation - Stdlib.printLine $"Package tree from {locationStr}:" - Stdlib.printLine "" + Stdlib.println $"Package tree from {locationStr}:" + Stdlib.println "" displayTree branchId startLocation maxDepth 0 "" depSets includeDeprecated state @@ -234,7 +234,7 @@ let displayTree if entityType == EntityType.Module then "" else Query.deprecationMarker depSets hash - Stdlib.printLine $"{fullPrefix}{icon}{name}{mark}" + Stdlib.println $"{fullPrefix}{icon}{name}{mark}" // Recursively display submodules if entityType == EntityType.Module && remainingDepth > 1 then diff --git a/packages/darklang/cli/packages/type.dark b/packages/darklang/cli/packages/type.dark index bada0d5399..ed54a7d703 100644 --- a/packages/darklang/cli/packages/type.dark +++ b/packages/darklang/cli/packages/type.dark @@ -101,7 +101,7 @@ let createTypeInline writtenType // Check for unresolved names - if Errors.reportUnresolvedNames unresolvedNames then + if Errors.reportUnresolvedNames state.currentBranchId unresolvedNames then state // no unresolved names - keep going else @@ -121,14 +121,14 @@ let createTypeInline let fullPath = PrettyPrinter.ProgramTypes.PackageLocation.packageLocation location let action = if isUpdate then "Updated" else "Created" - Stdlib.printLine "" - Stdlib.printLine (Colors.success $"✓ {action} type: {fullPath}") + Stdlib.println "" + Stdlib.println (Colors.success $"✓ {action} type: {fullPath}") // Auto-propagate if this is an update if isUpdate then Propagate.autoPropagateAfterUpdate state.currentBranchId location packageType.hash LanguageTools.ProgramTypes.ItemKind.Type - Stdlib.printLine "" + Stdlib.println "" state | Error msg -> @@ -165,7 +165,7 @@ let createTypeInline ] |> Stdlib.printLines state | Error msg -> - Stdlib.printLine (Colors.error $"Invalid location: {msg}") + Stdlib.println (Colors.error $"Invalid location: {msg}") state diff --git a/packages/darklang/cli/packages/undo.dark b/packages/darklang/cli/packages/undo.dark index 904f8f37d9..4386de5c0c 100644 --- a/packages/darklang/cli/packages/undo.dark +++ b/packages/darklang/cli/packages/undo.dark @@ -25,7 +25,7 @@ let buildVersionStack if srcLoc == location && LanguageTools.ProgramTypes.Reference.kind restoredRef == itemKind then // Pop back to restoredHash: keep entries up to and including it let restoredHash = LanguageTools.ProgramTypes.Reference.hash restoredRef - if Stdlib.List.member stack restoredHash then + if Stdlib.List.contains stack restoredHash then let before = Stdlib.List.takeWhile stack (fun id -> id != restoredHash) Stdlib.List.append before [restoredHash] else @@ -39,7 +39,7 @@ let buildVersionStack let execute (state: Cli.AppState) (args: List) : Cli.AppState = let branchId = state.currentBranchId let autoConfirm = - (Stdlib.List.member args "--yes") || (Stdlib.List.member args "-y") + (Stdlib.List.contains args "--yes") || (Stdlib.List.contains args "-y") let args = args |> Stdlib.List.filter (fun a -> a != "--yes" && a != "-y") @@ -49,9 +49,9 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = help state | [itemType] -> - Stdlib.printLine (Colors.error "Error: Missing name argument") - Stdlib.printLine "" - Stdlib.printLine "Usage: undo [--yes|-y]" + Stdlib.println (Colors.error "Error: Missing name argument") + Stdlib.println "" + Stdlib.println "Usage: undo [--yes|-y]" state | itemTypeStr :: name :: _ -> @@ -70,7 +70,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match Stdlib.List.reverse versionStack with | [] -> - Stdlib.printLine "Nothing to undo — current version is already committed" + Stdlib.println "Nothing to undo — current version is already committed" state | currentHash :: rest -> @@ -120,7 +120,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match op with | PropagateUpdate (propId, srcLoc, _, _, repoints) -> if srcLoc != location && - Stdlib.Bool.not (Stdlib.List.member revertedPropagationIds propId) then + Stdlib.Bool.not (Stdlib.List.contains revertedPropagationIds propId) then Stdlib.Option.Option.Some (repoints |> Stdlib.List.map (fun r -> r.location)) else @@ -132,12 +132,12 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = myRepoints |> Stdlib.List.filter (fun r -> Stdlib.Bool.not - (Stdlib.List.member otherRepointLocations r.location)) + (Stdlib.List.contains otherRepointLocations r.location)) let overlapping = myRepoints |> Stdlib.List.filter (fun r -> - Stdlib.List.member otherRepointLocations r.location) + Stdlib.List.contains otherRepointLocations r.location) // Resolve names before undo (locations get unlisted by undo) let repointNamesDict = @@ -157,19 +157,19 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | _ -> let count = Stdlib.List.length myRepoints - Stdlib.printLine + Stdlib.println $" This will revert {Stdlib.Int.toString count} dependent(s):" myRepoints |> Stdlib.List.iter (fun r -> let name = Deps.getName repointNamesDict (LanguageTools.ProgramTypes.Reference.hash r.toRef) let kindStr = Propagate.itemKindToString (LanguageTools.ProgramTypes.Reference.kind r.toRef) - Stdlib.printLine $" [{kindStr}] {name}") + Stdlib.println $" [{kindStr}] {name}") if autoConfirm then true else - Stdlib.printLine "" + Stdlib.println "" Stdlib.print (Colors.warning "Proceed? (y/n): ") let response = Builtin.stdinReadLine () (response == "y") || (response == "Y") @@ -179,7 +179,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = Builtin.pmAtomicUndo branchId revertable location itemKind myPropagationIds targetHash with | Error errMsg -> - Stdlib.printLine (Colors.error errMsg) + Stdlib.println (Colors.error errMsg) state | Ok result -> let (_revertId, restoredHash) = result @@ -188,15 +188,15 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = |> Stdlib.List.iter (fun r -> let name = Deps.getName repointNamesDict (LanguageTools.ProgramTypes.Reference.hash r.toRef) let kindStr = Propagate.itemKindToString (LanguageTools.ProgramTypes.Reference.kind r.toRef) - Stdlib.printLine $" Reverted [{kindStr}] {name}") + Stdlib.println $" Reverted [{kindStr}] {name}") let itemTypeStr = Propagate.itemKindToString itemKind if isToCommitted then - Stdlib.printLine + Stdlib.println (Colors.success $"Restored committed version of {itemTypeStr} {fullPath}") else - Stdlib.printLine + Stdlib.println (Colors.success $"Restored previous version of {itemTypeStr} {fullPath}") // Re-propagate overlapping dependents @@ -205,17 +205,17 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | _ -> Propagate.autoPropagateAfterUpdate branchId location restoredHash itemKind - Stdlib.printLine "" + Stdlib.println "" state else - Stdlib.printLine "Undo cancelled." + Stdlib.println "Undo cancelled." state | Error msg -> - Stdlib.printLine (Colors.error $"Invalid location: {msg}") + Stdlib.println (Colors.error $"Invalid location: {msg}") state | None -> - Stdlib.printLine (Colors.error $"Error: Invalid item type '{itemTypeStr}'") - Stdlib.printLine "Must be one of: fn, type, value" + Stdlib.println (Colors.error $"Error: Invalid item type '{itemTypeStr}'") + Stdlib.println "Must be one of: fn, type, value" state let help (state: Cli.AppState) : Cli.AppState = diff --git a/packages/darklang/cli/packages/value.dark b/packages/darklang/cli/packages/value.dark index 27ff039c32..0b07010b92 100644 --- a/packages/darklang/cli/packages/value.dark +++ b/packages/darklang/cli/packages/value.dark @@ -102,7 +102,7 @@ let createValueInline writtenValue // Check for unresolved names - if Errors.reportUnresolvedNames unresolvedNames then + if Errors.reportUnresolvedNames state.currentBranchId unresolvedNames then state // no unresolved names - keep going else @@ -122,14 +122,14 @@ let createValueInline let fullPath = PrettyPrinter.ProgramTypes.PackageLocation.packageLocation location let action = if isUpdate then "Updated" else "Created" - Stdlib.printLine "" - Stdlib.printLine (Colors.success $"✓ {action} value: {fullPath}") + Stdlib.println "" + Stdlib.println (Colors.success $"✓ {action} value: {fullPath}") // Auto-propagate if this is an update if isUpdate then Propagate.autoPropagateAfterUpdate state.currentBranchId location packageValue.hash LanguageTools.ProgramTypes.ItemKind.Value - Stdlib.printLine "" + Stdlib.println "" state | Error msg -> @@ -166,7 +166,7 @@ let createValueInline ] |> Stdlib.printLines state | Error msg -> - Stdlib.printLine (Colors.error $"Invalid location: {msg}") + Stdlib.println (Colors.error $"Invalid location: {msg}") state diff --git a/packages/darklang/cli/packages/view.dark b/packages/darklang/cli/packages/view.dark index d27e74d716..be57cd5d2a 100644 --- a/packages/darklang/cli/packages/view.dark +++ b/packages/darklang/cli/packages/view.dark @@ -363,11 +363,11 @@ let viewCompiled (mode: ViewMode) : Unit = match fnAt branchId location with - | Error e -> Stdlib.printLine (Colors.error $"Cannot {modeName mode}: {e}") + | Error e -> Stdlib.println (Colors.error $"Cannot {modeName mode}: {e}") | Ok fn -> let title = Colors.boldText (Packages.formatLocation location) - Stdlib.printLine title - Stdlib.printLine "" + Stdlib.println title + Stdlib.println "" match mode with | Ast -> @@ -380,7 +380,7 @@ let viewCompiled match Builtin.pmFnInstructions fn.hash with | Some instrs -> instructionRows branchId instrs |> Stdlib.printLines | None -> - Stdlib.printLine + Stdlib.println (Colors.error "No compiled instructions stored for this function.") | Source -> viewEntity branchId location @@ -391,7 +391,7 @@ let execute (state: AppState) (args: List) : AppState = match parseArgs args with | Error e -> - Stdlib.printLine (Colors.error $"Cannot view: {e}") + Stdlib.println (Colors.error $"Cannot view: {e}") help state state | Ok parsed -> @@ -404,7 +404,7 @@ let execute (state: AppState) (args: List) : AppState = match locationResult with | Error errorMsg -> - Stdlib.printLine (Colors.error $"Cannot view: {errorMsg}") + Stdlib.println (Colors.error $"Cannot view: {errorMsg}") state | Ok location -> match mode with diff --git a/packages/darklang/cli/quit.dark b/packages/darklang/cli/quit.dark index a7e3df4de0..05665f9ba0 100644 --- a/packages/darklang/cli/quit.dark +++ b/packages/darklang/cli/quit.dark @@ -14,7 +14,7 @@ let execute (state: AppState) (_args: List) : AppState = | other -> other { cleared with currentPage = resumed; pageStack = rest } | [] -> - Stdlib.printLine "Goodbye!" + Stdlib.println "Goodbye!" { state with isExiting = true } diff --git a/packages/darklang/cli/registry.dark b/packages/darklang/cli/registry.dark index 52fb282df4..69f95c1d9e 100644 --- a/packages/darklang/cli/registry.dark +++ b/packages/darklang/cli/registry.dark @@ -36,6 +36,7 @@ module Registry = ("let", "Create a new value (alias for val)", [], Packages.Value.execute, Packages.Value.help, Packages.Value.complete) ("fn", "Create a new function", [ "function" ], Packages.Fn.execute, Packages.Fn.help, Packages.Fn.complete) ("type", "Create a new type", [], Packages.Type.execute, Packages.Type.help, Packages.Type.complete) + ("module", "Define multiple package declarations", [], Packages.ModuleCommand.execute, Packages.ModuleCommand.help, Packages.ModuleCommand.complete) ("hash", "Show hash of a package item", [], Packages.Hash.execute, Packages.Hash.help, Packages.Hash.complete) // SCM commands ("status", "Show uncommitted changes", [ "wip"; "changes" ], SCM.Status.execute, SCM.Status.help, SCM.Status.complete) @@ -114,11 +115,11 @@ module Registry = description = "Unknown command" aliases = [] execute = fun state args -> - Stdlib.printLine (View.formatError $"Unknown command: {name}") - Stdlib.printLine "Use 'help' to see available commands." + Stdlib.println (View.formatError $"Unknown command: {name}") + Stdlib.println "Use 'help' to see available commands." state help = fun state -> - Stdlib.printLine $"No help available for command: {name}" + Stdlib.println $"No help available for command: {name}" state complete = fun _state _args -> [] } @@ -126,7 +127,7 @@ module Registry = let handler = findCommandIn state.allCommandsCache name let hasHelpFlag = - (Stdlib.List.member args "--help") || (Stdlib.List.member args "-h") + (Stdlib.List.contains args "--help") || (Stdlib.List.contains args "-h") if hasHelpFlag then executeCommandHelp name state @@ -144,13 +145,13 @@ module Registry = | [] -> state | aliases -> let aliasText = Stdlib.String.join aliases ", " - Stdlib.printLine "" - Stdlib.printLine $"Aliases: {aliasText}" + Stdlib.println "" + Stdlib.println $"Aliases: {aliasText}" state // Command groups organized by category (shared between compact and detailed views) let commandGroups () : List<(String * List)> = - [ ("Packages", [ "nav"; "ls"; "view"; "tree"; "back"; "search"; "deps"; "val"; "let"; "fn"; "type"; "hash"; "db"; "deprecate"; "delete" ]) + [ ("Packages", [ "nav"; "ls"; "view"; "tree"; "back"; "search"; "deps"; "val"; "let"; "fn"; "type"; "module"; "hash"; "db"; "deprecate"; "delete" ]) ("SCM", [ "status"; "commits"; "commit"; "discard"; "show"; "ops"; "branch"; "rebase"; "merge"; "review" ]) ("Execution", [ "run"; "eval"; "scripts" ]) diff --git a/packages/darklang/cli/scm/branch.dark b/packages/darklang/cli/scm/branch.dark index 656d167dd5..16ef35ed13 100644 --- a/packages/darklang/cli/scm/branch.dark +++ b/packages/darklang/cli/scm/branch.dark @@ -76,7 +76,7 @@ let printBranchTree let isMain = child.id == SCM.Branch.mainBranchId let annotation = branchAnnotation child isMain - Stdlib.printLine $"{prefix}{connector}{marker}{child.name}{annotation}" + Stdlib.println $"{prefix}{connector}{marker}{child.name}{annotation}" let childPrefix = if isLastChild then @@ -93,7 +93,7 @@ let execute (state: AppState) (args: List) : AppState = | [] | [ "list" ] -> let branches = SCM.Branch.list () if Stdlib.List.isEmpty branches then - Stdlib.printLine "No branches." + Stdlib.println "No branches." else // Find the root: main branch (no parent) let mainId = SCM.Branch.mainBranchId @@ -111,7 +111,7 @@ let execute (state: AppState) (args: List) : AppState = " " let annotation = branchAnnotation main true - Stdlib.printLine $"{marker}{main.name}{annotation}" + Stdlib.println $"{marker}{main.name}{annotation}" printBranchTree branches mainId state.currentBranchId " " // Print any orphan branches (no parent or parent not in list) @@ -123,7 +123,7 @@ let execute (state: AppState) (args: List) : AppState = match b.parentBranchId with | None -> b.id != mainId | Some pid -> - Stdlib.Bool.not (Stdlib.List.member branchIds pid) + Stdlib.Bool.not (Stdlib.List.contains branchIds pid) if isOrphan then let orphanMarker = @@ -133,7 +133,7 @@ let execute (state: AppState) (args: List) : AppState = " " let orphanAnnotation = branchAnnotation b false - Stdlib.printLine $"{orphanMarker}{b.name}{orphanAnnotation}" + Stdlib.println $"{orphanMarker}{b.name}{orphanAnnotation}" printBranchTree branches b.id state.currentBranchId "" else ()) @@ -148,27 +148,27 @@ let execute (state: AppState) (args: List) : AppState = else " " - Stdlib.printLine $"{marker}{b.name}") + Stdlib.println $"{marker}{b.name}") state | [ "create"; name ] -> let newBranch = SCM.Branch.create name state.currentBranchId - Stdlib.printLine (Colors.success $"Created and switched to branch '{name}'") + Stdlib.println (Colors.success $"Created and switched to branch '{name}'") if state.nonInteractive then - Stdlib.printLine + Stdlib.println "(non-interactive: pass --branch or set DARK_BRANCH on the next call)" { state with currentBranchId = newBranch.id } | [ "switch"; name ] -> match SCM.Branch.getByName name with | Some b -> - Stdlib.printLine $"Switched to branch '{name}'" + Stdlib.println $"Switched to branch '{name}'" if state.nonInteractive then - Stdlib.printLine + Stdlib.println "(non-interactive: pass --branch or set DARK_BRANCH on the next call)" { state with currentBranchId = b.id } | None -> - Stdlib.printLine (Colors.error $"Branch '{name}' not found.") + Stdlib.println (Colors.error $"Branch '{name}' not found.") state // `switch` only lasts as long as the process, which is no use to a non-interactive run — hence its own @@ -176,8 +176,8 @@ let execute (state: AppState) (args: List) : AppState = // you change it. DARK_BRANCH still wins for one shell, and --branch still wins for one command. | [ "default" ] -> match Darklang.Cli.Config.get "branch.default" with - | Some name -> Stdlib.printLine $"Default branch: {name}" - | None -> Stdlib.printLine "Default branch: main (unset)" + | Some name -> Stdlib.println $"Default branch: {name}" + | None -> Stdlib.println "Default branch: main (unset)" state @@ -191,27 +191,27 @@ let execute (state: AppState) (args: List) : AppState = name let _ = Darklang.Cli.Config.writeConfig updated - Stdlib.printLine (Colors.success $"Default branch is now '{name}'.") - Stdlib.printLine (Colors.hint " Override once with --branch, or per-shell with DARK_BRANCH.") + Stdlib.println (Colors.success $"Default branch is now '{name}'.") + Stdlib.println (Colors.hint " Override once with --branch, or per-shell with DARK_BRANCH.") state | None -> - Stdlib.printLine (Colors.error $"Branch '{name}' not found.") + Stdlib.println (Colors.error $"Branch '{name}' not found.") state // Setting is its own verb (`set-default`), so `default` reads as show-only and doesn't silently write. | [ "default"; name ] -> - Stdlib.printLine (Colors.hint $"To set the default branch, use: branch set-default {name}") + Stdlib.println (Colors.hint $"To set the default branch, use: branch set-default {name}") state | [ "rename"; oldName; newName ] -> match SCM.Branch.getByName oldName with | Some b -> match SCM.Branch.rename b.id newName with - | Ok _ -> Stdlib.printLine (Colors.success $"Renamed '{oldName}' to '{newName}'") - | Error e -> Stdlib.printLine (Colors.error $"Rename failed: {e}") + | Ok _ -> Stdlib.println (Colors.success $"Renamed '{oldName}' to '{newName}'") + | Error e -> Stdlib.println (Colors.error $"Rename failed: {e}") state | None -> - Stdlib.printLine (Colors.error $"Branch '{oldName}' not found.") + Stdlib.println (Colors.error $"Branch '{oldName}' not found.") state | [ "delete"; name ] | [ "archive"; name ] -> @@ -219,22 +219,22 @@ let execute (state: AppState) (args: List) : AppState = | Some b -> match SCM.Branch.archive b.id with | Ok _ -> - Stdlib.printLine (Colors.success $"Archived branch '{name}'") + Stdlib.println (Colors.success $"Archived branch '{name}'") if b.id == state.currentBranchId then let mainId = SCM.Branch.mainBranchId - Stdlib.printLine "Switched to main." + Stdlib.println "Switched to main." { state with currentBranchId = mainId } else state | Error e -> - Stdlib.printLine (Colors.error $"Archive failed: {e}") + Stdlib.println (Colors.error $"Archive failed: {e}") state | None -> - Stdlib.printLine (Colors.error $"Branch '{name}' not found.") + Stdlib.println (Colors.error $"Branch '{name}' not found.") state | _ -> - Stdlib.printLine + Stdlib.println "Usage: branch [list|create|switch|rename|archive|default|set-default] [args]" state diff --git a/packages/darklang/cli/scm/commit.dark b/packages/darklang/cli/scm/commit.dark index 9c6265e64b..477629bade 100644 --- a/packages/darklang/cli/scm/commit.dark +++ b/packages/darklang/cli/scm/commit.dark @@ -33,7 +33,7 @@ let promptYesNo (question: String) (autoConfirm: Bool) (defaultYes: Bool) : Bool if autoConfirm then true else - Stdlib.printLine question + Stdlib.println question let response = Builtin.stdinReadLine () if Stdlib.String.isEmpty (Stdlib.String.trim response) then @@ -43,23 +43,23 @@ let promptYesNo (question: String) (autoConfirm: Bool) (defaultYes: Bool) : Bool let printUnresolved (items: List) : Unit = - Stdlib.printLine (Colors.error "Cannot commit: unresolved references found:") + Stdlib.println (Colors.error "Cannot commit: unresolved references found:") items |> Stdlib.List.iter (fun item -> - Stdlib.printLine (Colors.error $" {item.label}:") + Stdlib.println (Colors.error $" {item.label}:") item.names |> Stdlib.List.iter (fun name -> - Stdlib.printLine (Colors.error $" - {name}"))) + Stdlib.println (Colors.error $" - {name}"))) - Stdlib.printLine "" + Stdlib.println "" let execute (state: AppState) (args: List) : AppState = match state.accountID with | None -> - Stdlib.printLine + Stdlib.println (Colors.error "Not logged in. Run `login ` (see `login` for available accounts).") state @@ -68,7 +68,7 @@ let execute (state: AppState) (args: List) : AppState = let branchId = state.currentBranchId let autoConfirm = - (Stdlib.List.member args "--yes") || (Stdlib.List.member args "-y") + (Stdlib.List.contains args "--yes") || (Stdlib.List.contains args "-y") let includeNames = parseInclude args @@ -82,14 +82,14 @@ let execute (state: AppState) (args: List) : AppState = let message = Stdlib.String.join messageParts " " if Stdlib.String.isEmpty (Stdlib.String.trim message) then - Stdlib.printLine (Colors.error "Commit message required.") - Stdlib.printLine "Usage: commit \"Your commit message\"" + Stdlib.println (Colors.error "Commit message required.") + Stdlib.println "Usage: commit \"Your commit message\"" state else let wipOps = SCM.PackageOps.getWipWithIds branchId if Stdlib.List.isEmpty wipOps then - Stdlib.printLine "Nothing to commit." + Stdlib.println "Nothing to commit." state else // Pick op IDs to commit + display lines for the confirm prompt. @@ -113,13 +113,13 @@ let execute (state: AppState) (args: List) : AppState = if Stdlib.List.isEmpty depClosure then (selection, []) else - Stdlib.printLine + Stdlib.println (Colors.warning "Selected item(s) reference uncommitted item(s) not in the selection:") depClosure |> Stdlib.List.iter (fun item -> - Stdlib.printLine + Stdlib.println $" {SCM.PartialCommit.itemKindLabel item.kind} {item.fqn}") if @@ -149,23 +149,23 @@ let execute (state: AppState) (args: List) : AppState = match plan with | Error msg -> - Stdlib.printLine (Colors.error msg) + Stdlib.println (Colors.error msg) state | Ok((opIds, summaryLines)) -> if Stdlib.List.isEmpty opIds then - Stdlib.printLine "Nothing to commit." + Stdlib.println "Nothing to commit." state else - Stdlib.printLine "Will commit:" - summaryLines |> Stdlib.List.iter Stdlib.printLine - Stdlib.printLine "" + Stdlib.println "Will commit:" + summaryLines |> Stdlib.List.iter Stdlib.println + Stdlib.println "" if promptYesNo "Proceed? (y/n): " autoConfirm false then // Only selected ops should block this commit on unresolved names. let selectedOps = wipOps - |> Stdlib.List.filter (fun e -> Stdlib.List.member opIds e.id) + |> Stdlib.List.filter (fun e -> Stdlib.List.contains opIds e.id) |> Stdlib.List.map (fun e -> e.op) let unresolved = SCM.UnresolvedCheck.findUnresolvedInOps selectedOps @@ -178,21 +178,21 @@ let execute (state: AppState) (args: List) : AppState = with | Ok commitHash -> let shortId = Stdlib.String.first commitHash 8 - Stdlib.printLine (Colors.success $"Created commit {shortId}") + Stdlib.println (Colors.success $"Created commit {shortId}") // Make the outbound side of sync visible right where you'd wonder "where did this go?": sync // is pull-based, so your commit sits on your instance until a peer pulls it — there's no push. // Only worth saying when you actually have peers. if Stdlib.List.isEmpty (Darklang.Sync.peers ()) |> Stdlib.Bool.not then - Stdlib.printLine (Colors.hint " Peers pull your commits when they sync.") + Stdlib.println (Colors.hint " Peers pull your commits when they sync.") else () state | Error e -> - Stdlib.printLine (Colors.error $"Commit failed: {e}") + Stdlib.println (Colors.error $"Commit failed: {e}") state else - Stdlib.printLine "Commit cancelled." + Stdlib.println "Commit cancelled." state diff --git a/packages/darklang/cli/scm/discard.dark b/packages/darklang/cli/scm/discard.dark index ce456695e0..2dff7a7d7e 100644 --- a/packages/darklang/cli/scm/discard.dark +++ b/packages/darklang/cli/scm/discard.dark @@ -5,46 +5,46 @@ let execute (state: AppState) (args: List) : AppState = let branchId = state.currentBranchId let autoConfirm = - (Stdlib.List.member args "--yes") || (Stdlib.List.member args "-y") + (Stdlib.List.contains args "--yes") || (Stdlib.List.contains args "-y") let summary = SCM.PackageOps.getWipSummary branchId if summary.total == 0 then - Stdlib.printLine "Nothing to discard." + Stdlib.println "Nothing to discard." state else let header = if autoConfirm then "Discarded:" else "Will discard:" - Stdlib.printLine header + Stdlib.println header if summary.types > 0 then - Stdlib.printLine $" {Cli.Text.plural summary.types "type"}" + Stdlib.println $" {Cli.Text.plural summary.types "type"}" if summary.values > 0 then - Stdlib.printLine $" {Cli.Text.plural summary.values "value"}" + Stdlib.println $" {Cli.Text.plural summary.values "value"}" if summary.fns > 0 then - Stdlib.printLine $" {Cli.Text.plural summary.fns "function"}" + Stdlib.println $" {Cli.Text.plural summary.fns "function"}" - Stdlib.printLine "" + Stdlib.println "" let proceed = if autoConfirm then true else - Stdlib.printLine (Colors.warning "This cannot be undone. Proceed? (y/n): ") + Stdlib.println (Colors.warning "This cannot be undone. Proceed? (y/n): ") let response = Builtin.stdinReadLine () (response == "y") || (response == "Y") if proceed then match SCM.PackageOps.discard branchId with | Ok count -> - Stdlib.printLine (Colors.success $"Discarded {Stdlib.Int.toString count} ops.") + Stdlib.println (Colors.success $"Discarded {Stdlib.Int.toString count} ops.") state | Error e -> - Stdlib.printLine (Colors.error $"Discard failed: {e}") + Stdlib.println (Colors.error $"Discard failed: {e}") state else - Stdlib.printLine "Discard cancelled." + Stdlib.println "Discard cancelled." state diff --git a/packages/darklang/cli/scm/log.dark b/packages/darklang/cli/scm/log.dark index 0c2e6fe698..d6278ef006 100644 --- a/packages/darklang/cli/scm/log.dark +++ b/packages/darklang/cli/scm/log.dark @@ -27,22 +27,22 @@ let renderDetailed (c: SCM.PackageOps.Commit) (isAncestor: Bool) : Unit = let opCount = Stdlib.Int.toString c.opCount if isAncestor then - Stdlib.printLine + Stdlib.println $"{Cli.Colors.dim}{id} {createdAt} {c.committerName} ({opCount} ops) [{c.branchName}]{Cli.Colors.reset}" - Stdlib.printLine $"{Cli.Colors.dim} {c.message}{Cli.Colors.reset}" + Stdlib.println $"{Cli.Colors.dim} {c.message}{Cli.Colors.reset}" else - Stdlib.printLine + Stdlib.println $"{Cli.Colors.cyan}{id}{Cli.Colors.reset} {createdAt} {Cli.Colors.dim}{c.committerName}{Cli.Colors.reset} ({opCount} ops)" - Stdlib.printLine $" {c.message}" + Stdlib.println $" {c.message}" - Stdlib.printLine "" + Stdlib.println "" let execute (state: AppState) (args: List) : AppState = let branchId = state.currentBranchId let detailed = - (Stdlib.List.member args "--detailed") || (Stdlib.List.member args "--full") + (Stdlib.List.contains args "--detailed") || (Stdlib.List.contains args "--full") let limit = match Stdlib.List.filter args (fun a -> Stdlib.String.startsWith a "--" |> Stdlib.Bool.not) with @@ -56,7 +56,7 @@ let execute (state: AppState) (args: List) : AppState = let currentBranchIdStr = Stdlib.Uuid.toString branchId if Stdlib.List.isEmpty commits then - Stdlib.printLine "No commits yet." + Stdlib.println "No commits yet." else let branchName = match SCM.Branch.get branchId with @@ -65,7 +65,7 @@ let execute (state: AppState) (args: List) : AppState = // Say what you're looking at. The bare list left you to infer both the branch and that commits from // parent branches are in here too (the dimmed ones). - Stdlib.printLine ( + Stdlib.println ( Cli.Colors.success $"Commits on {branchName} ({Stdlib.Int.toString (Stdlib.List.length commits)}, newest first):") @@ -77,7 +77,7 @@ let execute (state: AppState) (args: List) : AppState = if detailed then renderDetailed c isAncestor else - Stdlib.printLine (renderOneLine c isAncestor)) + Stdlib.println (renderOneLine c isAncestor)) state diff --git a/packages/darklang/cli/scm/merge.dark b/packages/darklang/cli/scm/merge.dark index a6e86059a5..6789a9fbe5 100644 --- a/packages/darklang/cli/scm/merge.dark +++ b/packages/darklang/cli/scm/merge.dark @@ -3,40 +3,40 @@ module Darklang.Cli.SCM.Merge let execute (state: AppState) (args: List) : AppState = let dryRun = - (Stdlib.List.member args "--dry-run") || (Stdlib.List.member args "-n") + (Stdlib.List.contains args "--dry-run") || (Stdlib.List.contains args "-n") if dryRun then match SCM.Merge.canMerge state.currentBranchId with | Ok _ -> - Stdlib.printLine (Colors.success "Branch is ready to merge.") + Stdlib.println (Colors.success "Branch is ready to merge.") state | Error err -> let msg = SCM.Merge.mergeErrorToString err - Stdlib.printLine (Colors.error $"Cannot merge: {msg}") + Stdlib.println (Colors.error $"Cannot merge: {msg}") state else match SCM.Merge.merge state.currentBranchId with | Ok _ -> - Stdlib.printLine (Colors.success "Branch merged successfully.") + Stdlib.println (Colors.success "Branch merged successfully.") // Switch to parent branch match SCM.Branch.get state.currentBranchId with | Some b -> match b.parentBranchId with | Some parentId -> - Stdlib.printLine "Switched to parent branch." + Stdlib.println "Switched to parent branch." { state with currentBranchId = parentId } | None -> let mainId = SCM.Branch.mainBranchId - Stdlib.printLine "Switched to main." + Stdlib.println "Switched to main." { state with currentBranchId = mainId } | None -> // Branch was merged and info might not be available, switch to main let mainId = SCM.Branch.mainBranchId - Stdlib.printLine "Switched to main." + Stdlib.println "Switched to main." { state with currentBranchId = mainId } | Error err -> let msg = SCM.Merge.mergeErrorToString err - Stdlib.printLine (Colors.error $"Merge failed: {msg}") + Stdlib.println (Colors.error $"Merge failed: {msg}") state diff --git a/packages/darklang/cli/scm/rebase.dark b/packages/darklang/cli/scm/rebase.dark index ce6d9b2520..23aaab500e 100644 --- a/packages/darklang/cli/scm/rebase.dark +++ b/packages/darklang/cli/scm/rebase.dark @@ -3,7 +3,7 @@ module Darklang.Cli.SCM.Rebase let execute (state: AppState) (args: List) : AppState = let showStatus = - (Stdlib.List.member args "--status") || (Stdlib.List.member args "-s") + (Stdlib.List.contains args "--status") || (Stdlib.List.contains args "-s") if showStatus then let conflicts = SCM.Rebase.getConflicts state.currentBranchId @@ -13,13 +13,13 @@ let execute (state: AppState) (args: List) : AppState = // together: genuinely up to date vs. behind-but-clean (parent moved, nothing overlaps). if Stdlib.List.isEmpty conflicts then if SCM.Rebase.needed state.currentBranchId then - Stdlib.printLine (Colors.warning "Rebase needed — your branch is behind its parent.") - Stdlib.printLine (Colors.hint " No conflicts. Run `rebase` to catch up.") + Stdlib.println (Colors.warning "Rebase needed — your branch is behind its parent.") + Stdlib.println (Colors.hint " No conflicts. Run `rebase` to catch up.") else - Stdlib.printLine (Colors.success "Up to date with parent — no rebase needed.") + Stdlib.println (Colors.success "Up to date with parent — no rebase needed.") else - Stdlib.printLine (Colors.warning "Rebase needed, but these locations conflict:") - conflicts |> Stdlib.List.iter (fun c -> Stdlib.printLine $" {c}") + Stdlib.println (Colors.warning "Rebase needed, but these locations conflict:") + conflicts |> Stdlib.List.iter (fun c -> Stdlib.println $" {c}") [ "" "Reconcile each on your branch so it matches the parent's version, then run `rebase`." "(Or `dark branch archive ` to abandon this branch.)" ] @@ -29,13 +29,13 @@ let execute (state: AppState) (args: List) : AppState = else match SCM.Rebase.rebase state.currentBranchId with | Ok msg -> - Stdlib.printLine (Colors.success msg) + Stdlib.println (Colors.success msg) state | Error conflicts -> - Stdlib.printLine ( + Stdlib.println ( Colors.error "Rebase blocked — these locations changed on both your branch and its parent:") - conflicts |> Stdlib.List.iter (fun c -> Stdlib.printLine $" {c}") + conflicts |> Stdlib.List.iter (fun c -> Stdlib.println $" {c}") [ "" "Reconcile each on your branch so it matches the parent's version, then run `rebase` again." "(Or `dark branch archive ` to abandon this branch.)" ] diff --git a/packages/darklang/cli/scm/review/app.dark b/packages/darklang/cli/scm/review/app.dark index bb4bbe7cfe..cb666c8895 100644 --- a/packages/darklang/cli/scm/review/app.dark +++ b/packages/darklang/cli/scm/review/app.dark @@ -678,72 +678,72 @@ let buildDetailForBranch (branchId: Uuid) : Screen = let printDiffLine (diffLine: Stdlib.Diff.DiffLine) : Unit = match diffLine with - | Added text -> Stdlib.printLine (Darklang.Cli.Colors.colorize Darklang.Cli.Colors.green $"+ {text}") - | Removed text -> Stdlib.printLine (Darklang.Cli.Colors.colorize Darklang.Cli.Colors.red $"- {text}") - | Same text -> Stdlib.printLine $" {text}" + | Added text -> Stdlib.println (Darklang.Cli.Colors.colorize Darklang.Cli.Colors.green $"+ {text}") + | Removed text -> Stdlib.println (Darklang.Cli.Colors.colorize Darklang.Cli.Colors.red $"- {text}") + | Same text -> Stdlib.println $" {text}" let printWipItems (branchId: Uuid) : Unit = let wipItems = loadWipItems branchId if Stdlib.List.isEmpty wipItems then - Stdlib.printLine (Darklang.Cli.Colors.dimText "No uncommitted changes.") + Stdlib.println (Darklang.Cli.Colors.dimText "No uncommitted changes.") else - Stdlib.printLine "" - Stdlib.printLine (Darklang.Cli.Colors.boldText "Uncommitted changes:") + Stdlib.println "" + Stdlib.println (Darklang.Cli.Colors.boldText "Uncommitted changes:") let groups = groupByModule wipItems let ops = SCM.PackageOps.getWip branchId let parentId = getParentBranchId branchId Stdlib.List.iter groups (fun group -> let (modPath, items) = group - Stdlib.printLine "" - Stdlib.printLine $" {Darklang.Cli.Colors.bold}{Darklang.Cli.Colors.magenta}{modPath}{Darklang.Cli.Colors.reset}" + Stdlib.println "" + Stdlib.println $" {Darklang.Cli.Colors.bold}{Darklang.Cli.Colors.magenta}{modPath}{Darklang.Cli.Colors.reset}" Stdlib.List.iter items (fun pkg -> let propText = if pkg.propagatedCount > 0 then Darklang.Cli.Colors.dimText $" ({Stdlib.Int.toString pkg.propagatedCount} propagated)" else "" - Stdlib.printLine $" {kindLabel pkg.kind} {pkg.name}{propText}") + Stdlib.println $" {kindLabel pkg.kind} {pkg.name}{propText}") let diffLines = diffForItems ops branchId parentId items if diffLines != [] then - Stdlib.printLine "" + Stdlib.println "" Stdlib.List.iter diffLines (fun line -> printDiffLine line)) let printRecentCommits (branchId: Uuid) : Unit = let commits = SCM.PackageOps.getCommits branchId 10 if commits != [] then - Stdlib.printLine "" - Stdlib.printLine (Darklang.Cli.Colors.boldText "Recent commits:") + Stdlib.println "" + Stdlib.println (Darklang.Cli.Colors.boldText "Recent commits:") Stdlib.List.iter commits (fun c -> let shortHash = LanguageTools.ProgramTypes.hashToShort c.hash let date = Stdlib.DateTime.toString c.createdAt let hashText = Darklang.Cli.Colors.colorize Darklang.Cli.Colors.yellow shortHash let opsText = Darklang.Cli.Colors.dimText $"({Stdlib.Int.toString c.opCount} ops)" let dateText = Darklang.Cli.Colors.dimText date - Stdlib.printLine $" {hashText} {c.message} {opsText} {dateText}") + Stdlib.println $" {hashText} {c.message} {opsText} {dateText}") let printBranchDetail (branchId: Uuid) : Unit = let branchName = match SCM.Branch.get branchId with | Some b -> b.name | None -> "unknown" - Stdlib.printLine $"Branch: {Darklang.Cli.Colors.cyan}{branchName}{Darklang.Cli.Colors.reset}" + Stdlib.println $"Branch: {Darklang.Cli.Colors.cyan}{branchName}{Darklang.Cli.Colors.reset}" printWipItems branchId printRecentCommits branchId let printAllBranches () : Unit = let items = loadBranchList () if Stdlib.List.isEmpty items then - Stdlib.printLine (Darklang.Cli.Colors.dimText "No branches with changes to review.") + Stdlib.println (Darklang.Cli.Colors.dimText "No branches with changes to review.") else - Stdlib.printLine (Darklang.Cli.Colors.boldText "Branches with changes:") + Stdlib.println (Darklang.Cli.Colors.boldText "Branches with changes:") Stdlib.List.iter items (fun item -> let counts = formatBranchCounts item.wipCount item.committedCount "" let nameText = Darklang.Cli.Colors.colorize (Darklang.Cli.Colors.bold ++ Darklang.Cli.Colors.cyan) item.branchName - Stdlib.printLine $" {nameText} {counts}") + Stdlib.println $" {nameText} {counts}") let execute (cliState: Darklang.Cli.AppState) (args: List) : Darklang.Cli.AppState = let branchId = cliState.currentBranchId - let showAll = Stdlib.List.member args "--all" + let showAll = Stdlib.List.contains args "--all" if cliState.nonInteractive then if showAll then printAllBranches () @@ -768,7 +768,7 @@ let execute (cliState: Darklang.Cli.AppState) (args: List) : Darklang.Cl { cliState with currentPage = Darklang.Cli.Page.SubApp (makeSubApp session) } | Error message -> - Stdlib.printLine message + Stdlib.println message cliState let help (_state: Darklang.Cli.AppState) : Darklang.Cli.AppState = diff --git a/packages/darklang/cli/scm/showCommit.dark b/packages/darklang/cli/scm/showCommit.dark index 52526b7825..72b1a8dc06 100644 --- a/packages/darklang/cli/scm/showCommit.dark +++ b/packages/darklang/cli/scm/showCommit.dark @@ -7,7 +7,7 @@ let execute (state: AppState) (args: List) : AppState = match args with | [] -> - Stdlib.printLine "Usage: show " + Stdlib.println "Usage: show " state | [ commitHashStr ] -> // Allow partial commit IDs (prefix matching) @@ -24,10 +24,10 @@ let execute (state: AppState) (args: List) : AppState = match matching with | [ c ] -> LanguageTools.ProgramTypes.hashToString c.hash | [] -> - Stdlib.printLine (Cli.Colors.error "No matching commit found.") + Stdlib.println (Cli.Colors.error "No matching commit found.") commitHashStr | _ -> - Stdlib.printLine (Cli.Colors.error "Multiple commits match that prefix. Be more specific.") + Stdlib.println (Cli.Colors.error "Multiple commits match that prefix. Be more specific.") commitHashStr else commitHashStr @@ -35,7 +35,7 @@ let execute (state: AppState) (args: List) : AppState = let ops = SCM.PackageOps.getCommitOps fullIdStr if Stdlib.List.isEmpty ops then - Stdlib.printLine "Commit not found or has no ops." + Stdlib.println "Commit not found or has no ops." else let shortId = Stdlib.String.first fullIdStr 8 @@ -49,22 +49,22 @@ let execute (state: AppState) (args: List) : AppState = // No blank line under the header: it separated the title from the only thing the title was introducing. match meta with | Some c -> - Stdlib.printLine + Stdlib.println $"Commit {Cli.Colors.cyan}{shortId}{Cli.Colors.reset} — {c.committerName}, {Stdlib.DateTime.toString c.createdAt} — {Stdlib.Int.toString (Stdlib.List.length ops)} ops" - Stdlib.printLine $" {c.message}" + Stdlib.println $" {c.message}" | None -> - Stdlib.printLine + Stdlib.println $"Commit {Cli.Colors.cyan}{shortId}{Cli.Colors.reset} — {Stdlib.Int.toString (Stdlib.List.length ops)} ops:" ops |> Stdlib.List.iter (fun op -> let opStr = PrettyPrinter.ProgramTypes.PackageOp.packageOp branchId op - Stdlib.printLine $" {opStr}") + Stdlib.println $" {opStr}") state | _ -> - Stdlib.printLine "Usage: show " + Stdlib.println "Usage: show " state diff --git a/packages/darklang/cli/scm/status.dark b/packages/darklang/cli/scm/status.dark index 797c85ff50..1f005698cc 100644 --- a/packages/darklang/cli/scm/status.dark +++ b/packages/darklang/cli/scm/status.dark @@ -10,26 +10,26 @@ let execute (state: AppState) (args: List) : AppState = | Some b -> b.name | None -> "unknown" - Stdlib.printLine $"On branch {Cli.Colors.cyan}{branchName}{Cli.Colors.reset}" + Stdlib.println $"On branch {Cli.Colors.cyan}{branchName}{Cli.Colors.reset}" let summary = SCM.PackageOps.getWipSummary branchId if summary.total == 0 then - Stdlib.printLine "No uncommitted changes." + Stdlib.println "No uncommitted changes." else - Stdlib.printLine "Uncommitted changes:" + Stdlib.println "Uncommitted changes:" if summary.types > 0 then - Stdlib.printLine $" {Cli.Text.plural summary.types "type"}" + Stdlib.println $" {Cli.Text.plural summary.types "type"}" if summary.values > 0 then - Stdlib.printLine $" {Cli.Text.plural summary.values "value"}" + Stdlib.println $" {Cli.Text.plural summary.values "value"}" if summary.fns > 0 then - Stdlib.printLine $" {Cli.Text.plural summary.fns "function"}" + Stdlib.println $" {Cli.Text.plural summary.fns "function"}" if summary.deprecations > 0 then - Stdlib.printLine $" {Cli.Text.plural summary.deprecations "deprecation"}" + Stdlib.println $" {Cli.Text.plural summary.deprecations "deprecation"}" [ "" "Use 'commit \"message\"' to commit these changes." diff --git a/packages/darklang/cli/scripts.dark b/packages/darklang/cli/scripts.dark index 97c6478508..9dd224e74d 100644 --- a/packages/darklang/cli/scripts.dark +++ b/packages/darklang/cli/scripts.dark @@ -27,32 +27,32 @@ let execute (state: AppState) (args: List) : AppState = | ["list"] -> let scripts = Builtin.pmScriptsList () if Stdlib.List.isEmpty scripts then - Stdlib.printLine "No scripts found" + Stdlib.println "No scripts found" state else let output = scripts |> Stdlib.List.map (fun s -> $" {s.name}") |> Stdlib.String.join "\n" - Stdlib.printLine $"Scripts:\n{output}" + Stdlib.println $"Scripts:\n{output}" state | ["view"; name] -> match Builtin.pmScriptsGet name with | Some script -> let output = $"Script: {script.name}\n\n{script.text}" - Stdlib.printLine output + Stdlib.println output state | None -> let error = $"Script '{name}' not found" - Stdlib.printLine $"Error: {error}" + Stdlib.println $"Error: {error}" state | ["add"] -> [ "Error: Missing arguments for scripts add" "Usage: scripts add " - "Example: scripts add hello 'Stdlib.printLine \"Hello World\"'" + "Example: scripts add hello 'Stdlib.println \"Hello World\"'" ] |> Stdlib.printLines state @@ -60,7 +60,7 @@ let execute (state: AppState) (args: List) : AppState = [ "Error: Missing script text" "Usage: scripts add " - "Example: scripts add hello 'Stdlib.printLine \"Hello World\"'" + "Example: scripts add hello 'Stdlib.println \"Hello World\"'" ] |> Stdlib.printLines state @@ -69,10 +69,10 @@ let execute (state: AppState) (args: List) : AppState = match Builtin.pmScriptsAdd name text with | Ok script -> let message = $"Script '{script.name}' added successfully" - Stdlib.printLine message + Stdlib.println message state | Error err -> - Stdlib.printLine $"Error: {err}" + Stdlib.println $"Error: {err}" state | ["edit"; name] -> @@ -86,7 +86,7 @@ let execute (state: AppState) (args: List) : AppState = state | None -> let error = $"Script '{name}' not found" - Stdlib.printLine $"Error: {error}" + Stdlib.println $"Error: {error}" state | "edit" :: name :: textParts when Stdlib.Bool.not (Stdlib.List.isEmpty textParts) -> @@ -94,20 +94,20 @@ let execute (state: AppState) (args: List) : AppState = match Builtin.pmScriptsUpdate name text with | Ok () -> let message = $"Script '{name}' updated successfully" - Stdlib.printLine message + Stdlib.println message state | Error err -> - Stdlib.printLine $"Error: {err}" + Stdlib.println $"Error: {err}" state | ["delete"; name] -> match Builtin.pmScriptsDelete name with | Ok () -> let message = $"Script '{name}' deleted successfully" - Stdlib.printLine message + Stdlib.println message state | Error err -> - Stdlib.printLine $"Error: {err}" + Stdlib.println $"Error: {err}" state | ["run"; name] -> @@ -117,18 +117,18 @@ let execute (state: AppState) (args: List) : AppState = match result with | Ok 0 -> state | Ok exitCode -> - Stdlib.printLine $"Script '{name}' exited with code {Stdlib.Int.toString exitCode}" + Stdlib.println $"Script '{name}' exited with code {Stdlib.Int.toString exitCode}" state | Error execErr -> let pretty = ExecutionError.toString state.currentBranchId execErr - Stdlib.printLine $"Script error: {pretty}" + Stdlib.println $"Script error: {pretty}" state | None -> - Stdlib.printLine $"Error: Script '{name}' not found" + Stdlib.println $"Error: Script '{name}' not found" state | _ -> - Stdlib.printLine "Error: Invalid scripts command. Use 'scripts' to see available commands" + Stdlib.println "Error: Invalid scripts command. Use 'scripts' to see available commands" state @@ -167,7 +167,7 @@ let help (state: AppState) : Unit = " scripts run - Run a script" "" "Examples:" - " scripts add hello 'Stdlib.printLine \"Hello World\"'" + " scripts add hello 'Stdlib.println \"Hello World\"'" " scripts run hello" - " scripts edit hello 'Stdlib.printLine \"Updated Hello\"'" + " scripts edit hello 'Stdlib.println \"Updated Hello\"'" ] |> Stdlib.printLines \ No newline at end of file diff --git a/packages/darklang/cli/sync.dark b/packages/darklang/cli/sync.dark index e62cd56777..26d0944104 100644 --- a/packages/darklang/cli/sync.dark +++ b/packages/darklang/cli/sync.dark @@ -43,12 +43,12 @@ let changeLines (changes: List) : List = // few, grouped counts for many. `changeLines` already deduped and chose the form. let printPull (who: String) (changes: List) : Unit = match changeLines changes with - | [ one ] -> Stdlib.printLine (Colors.success $"Pulled from {who}: {one}") + | [ one ] -> Stdlib.println (Colors.success $"Pulled from {who}: {one}") | many -> - Stdlib.printLine (Colors.success $"Pulled from {who}:") + Stdlib.println (Colors.success $"Pulled from {who}:") many - |> Stdlib.List.iter (fun l -> Stdlib.printLine (Colors.success $" {l}")) + |> Stdlib.List.iter (fun l -> Stdlib.println (Colors.success $" {l}")) // After a pull, mention any name where an incoming change overwrote a different local one — so last-writer-wins // is never SILENT, without crying "conflict". Most of the time this is just a peer that was ahead of you (a @@ -62,7 +62,7 @@ let conflictNudge () : Unit = |> Stdlib.List.length if unreviewed > 0 then - Stdlib.printLine ( + Stdlib.println ( Colors.dimText $" {Text.plural unreviewed "name"} updated by a peer (last-writer-wins) — `dark conflicts` to review") else @@ -89,7 +89,7 @@ let serveOn (port: Int) (state: AppState) : AppState = announce with | Ok _ -> () - | Error msg -> Stdlib.printLine (Colors.error msg) + | Error msg -> Stdlib.println (Colors.error msg) state @@ -139,17 +139,17 @@ let setConfig (key: String) (value: String) : Unit = // The first-run dialog: explain the defaults, offer to adjust them inline. In a non-interactive context // (piped/no TTY) the "Adjust?" prompt reads EOF → No, so it falls through to defaults without blocking. let firstRunDialog () : Unit = - Stdlib.printLine "Setting up automatic sync. Defaults:" + Stdlib.println "Setting up automatic sync. Defaults:" - Stdlib.printLine ( + Stdlib.println ( Colors.hint $" - pull from peers + serve to them, every {Stdlib.Int.toString (intervalSec ())}s") - Stdlib.printLine ( + Stdlib.println ( Colors.hint $" - serve on port {Stdlib.Int.toString (servePort ())} — all branches, open to anyone who can reach you") - Stdlib.printLine (Colors.hint " - start automatically on boot") + Stdlib.println (Colors.hint " - start automatically on boot") let _ = if Stdlib.Cli.UI.Prompt.confirm "Adjust these settings?" then @@ -186,7 +186,7 @@ let firstRunDialog () : Unit = setConfig "sync.daemon.configured" "yes" // Separate the dialog from the "Sync started" lines the caller prints next — otherwise a non-interactive // run (prompt auto-answered "no") runs the "Adjust these settings?" line straight into "✓ Sync started". - Stdlib.printLine "" + Stdlib.println "" let daemonStart (state: AppState) : AppState = let firstRun = @@ -198,7 +198,7 @@ let daemonStart (state: AppState) : AppState = if firstRun then firstRunDialog () else - Stdlib.printLine ( + Stdlib.println ( Colors.success $"Automatic sync started — {settingsLine ()}.") let started = @@ -219,30 +219,30 @@ let daemonStop (state: AppState) : AppState = let _ = Stdlib.List.map [ "sync-pull"; "sync-serve" ] (fun slug -> match Stdlib.Cli.Daemon.stop slug with - | Ok _ -> Stdlib.printLine (Colors.success $"Stopped {slug}.") + | Ok _ -> Stdlib.println (Colors.success $"Stopped {slug}.") | Error _ -> ()) - Stdlib.printLine ( + Stdlib.println ( Colors.hint "Automatic sync off. One-off: dark sync (pull), or dark sync serve (be reachable).") state let daemonStatusCmd (state: AppState) : AppState = - Stdlib.printLine "Automatic sync:" - Stdlib.printLine $" {Stdlib.Cli.Daemon.statusLine "sync-pull"}" - Stdlib.printLine $" {Stdlib.Cli.Daemon.statusLine "sync-serve"}" - Stdlib.printLine (Colors.hint $" {settingsLine ()} (change: dark sync daemon config)") + Stdlib.println "Automatic sync:" + Stdlib.println $" {Stdlib.Cli.Daemon.statusLine "sync-pull"}" + Stdlib.println $" {Stdlib.Cli.Daemon.statusLine "sync-serve"}" + Stdlib.println (Colors.hint $" {settingsLine ()} (change: dark sync daemon config)") state let daemonConfigShow (state: AppState) : AppState = - Stdlib.printLine "Automatic sync settings:" - Stdlib.printLine $" mode {daemonMode ()} (pull+serve | pull-only | serve-only)" - Stdlib.printLine $" interval {Stdlib.Int.toString (intervalSec ())}s" - Stdlib.printLine $" port {Stdlib.Int.toString (servePort ())}" - Stdlib.printLine $" on-boot {if onBoot () then "yes" else "no"}" + Stdlib.println "Automatic sync settings:" + Stdlib.println $" mode {daemonMode ()} (pull+serve | pull-only | serve-only)" + Stdlib.println $" interval {Stdlib.Int.toString (intervalSec ())}s" + Stdlib.println $" port {Stdlib.Int.toString (servePort ())}" + Stdlib.println $" on-boot {if onBoot () then "yes" else "no"}" - Stdlib.printLine ( + Stdlib.println ( Colors.hint " change: dark sync daemon config ") @@ -258,7 +258,7 @@ let daemonConfigValue : Stdlib.Option.Option<(String * String)> = match key with | "mode" -> - if Stdlib.List.member_v0 [ "pull+serve"; "pull-only"; "serve-only" ] value then + if Stdlib.List.contains_v0 [ "pull+serve"; "pull-only"; "serve-only" ] value then Stdlib.Option.Option.Some(("sync.daemon.mode", value)) else Stdlib.Option.Option.None @@ -271,7 +271,7 @@ let daemonConfigValue Stdlib.Option.Option.None | Error _ -> Stdlib.Option.Option.None | "on-boot" -> - if Stdlib.List.member_v0 [ "yes"; "no" ] value then + if Stdlib.List.contains_v0 [ "yes"; "no" ] value then Stdlib.Option.Option.Some(("sync.daemon.onBoot", value)) else Stdlib.Option.Option.None @@ -306,11 +306,11 @@ let daemonConfigSet let _ = Darklang.Cli.Config.writeConfig updated - Stdlib.printLine ( + Stdlib.println ( Colors.success $"Set {key} = {value}. Apply: dark sync daemon stop, then dark sync daemon start.") | None -> - Stdlib.printLine ( + Stdlib.println ( Colors.error $"Can't set '{key}' to '{value}'. Keys: mode | interval (seconds) | port | on-boot (yes/no).") @@ -352,9 +352,9 @@ let execute (state: AppState) (args: List) : AppState = // bare `dark sync` = pull now from every connected instance (the daemon automates this). match Darklang.Sync.peers () with | [] -> - Stdlib.printLine "You're not syncing with anyone yet." + Stdlib.println "You're not syncing with anyone yet." - Stdlib.printLine ( + Stdlib.println ( Colors.hint " dark sync connect sync with one of your instances") state @@ -395,11 +395,11 @@ let execute (state: AppState) (args: List) : AppState = else if pulled > 0 then // Ops applied, but nothing a human would call a change — branch structure, or content whose // name-binding came in an earlier batch. Say that honestly rather than invent a count. - Stdlib.printLine ( + Stdlib.println ( Colors.success $"Synced with {reachedPhrase reached (Stdlib.List.length ps)} — no new names") else if reached > 0 then - Stdlib.printLine ( + Stdlib.println ( Colors.success $"Already up to date with {reachedPhrase reached (Stdlib.List.length ps)}") else @@ -411,9 +411,9 @@ let execute (state: AppState) (args: List) : AppState = | [ "status" ] -> match Darklang.Sync.status () with | [] -> - Stdlib.printLine "You're not syncing with anyone yet." + Stdlib.println "You're not syncing with anyone yet." - Stdlib.printLine ( + Stdlib.println ( Colors.hint " dark sync connect sync with one of your instances") state @@ -471,16 +471,16 @@ let execute (state: AppState) (args: List) : AppState = // silent `dark sync`. The peer is added either way — an offline instance will just sync once it's up. match Darklang.Sync.reachable peer with | Ok n -> - Stdlib.printLine ( + Stdlib.println ( Colors.success $"Now syncing with {peer} — reachable ({Stdlib.Int64.toString n} ops)") - Stdlib.printLine ( + Stdlib.println ( Colors.hint " dark sync — sync now. dark sync daemon start — keep current on boot.") | Error _ -> - Stdlib.printLine (Colors.success $"Added {peer}") - Stdlib.printLine ( + Stdlib.println (Colors.success $"Added {peer}") + Stdlib.println ( Colors.hint " can't reach it yet — check the URL and that the instance is running `dark sync serve`; it'll sync once it's up") - | Error e -> Stdlib.printLine (Colors.error e) + | Error e -> Stdlib.println (Colors.error e) state @@ -490,7 +490,7 @@ let execute (state: AppState) (args: List) : AppState = // rather than trying to pull from a non-URL. match Darklang.Sync.resolvePeer peerRef with | None -> - Stdlib.printLine ( + Stdlib.println ( Colors.error $"Not syncing with an instance named '{peerRef}'. Use its URL, or `dark sync status` to see the names you have.") @@ -503,10 +503,10 @@ let execute (state: AppState) (args: List) : AppState = if Stdlib.List.length result.changes > 0 then printPull who result.changes else if result.applied > 0 then - Stdlib.printLine (Colors.success $"Synced with {who} — no new names") + Stdlib.println (Colors.success $"Synced with {who} — no new names") else - Stdlib.printLine (Colors.success $"Already up to date with {who}") - | Error e -> Stdlib.printLine (Colors.error e) + Stdlib.println (Colors.success $"Already up to date with {who}") + | Error e -> Stdlib.println (Colors.error e) let _ = conflictNudge () state @@ -519,9 +519,9 @@ let execute (state: AppState) (args: List) : AppState = | None -> peerRef if Darklang.Sync.disconnect peer then - Stdlib.printLine (Colors.success $"Stopped syncing with {Darklang.Sync.instanceName peer}") + Stdlib.println (Colors.success $"Stopped syncing with {Darklang.Sync.instanceName peer}") else - Stdlib.printLine (Colors.hint $"You weren't syncing with {peerRef}") + Stdlib.println (Colors.hint $"You weren't syncing with {peerRef}") state @@ -537,13 +537,13 @@ let execute (state: AppState) (args: List) : AppState = match Stdlib.Int.parse portStr with | Ok p -> serveOn p state | Error _ -> - Stdlib.printLine (Colors.error $"Invalid --port: {portStr}") + Stdlib.println (Colors.error $"Invalid --port: {portStr}") state | [ "help" ] -> help state | _ -> - Stdlib.printLine (Colors.error $"Unknown: dark sync {Stdlib.String.join args " "}") - Stdlib.printLine "" + Stdlib.println (Colors.error $"Unknown: dark sync {Stdlib.String.join args " "}") + Stdlib.println "" help state diff --git a/packages/darklang/cli/tests/tests-runner.dark b/packages/darklang/cli/tests/tests-runner.dark index 131fbb9de3..638297e092 100644 --- a/packages/darklang/cli/tests/tests-runner.dark +++ b/packages/darklang/cli/tests/tests-runner.dark @@ -15,6 +15,9 @@ let allTests (): List = ("Eval String Length", fun () -> testEvalStringLength ()) ("Eval List Length", fun () -> testEvalListLength ()) + ("Sleep Seconds", fun () -> testSleepSeconds ()) + ("Module Command", fun () -> testModuleCommand ()) + ("Authoring Source Contracts", fun () -> testAuthoringSourceContracts ()) ("View AST", fun () -> testViewAst ()) ("View Instructions", fun () -> testViewInstructions ()) @@ -29,6 +32,26 @@ let allTests (): List = ("List Functions", fun () -> testListFunctions ()) ("View Function", fun () -> testViewFunction ()) + ( "Search Module and CamelCase Name", + fun () -> testSearchMatchesModuleAndCamelCaseName () ) + ( "Search Module and Operation Name", + fun () -> testSearchMatchesModuleAndOperationName () ) + ( "Search Prefers Text File Functions", + fun () -> testSearchPrefersTextFileFunctions () ) + ( "Search Shows Strong Match Summary", + fun () -> testSearchShowsSummaryForStrongMatches () ) + ( "Search Prefers Default Int", + fun () -> testSearchPrefersDefaultIntOverFixedWidths () ) + ( "Search Finds Nested Helpers", + fun () -> testSearchFindsNestedHelpersFromParentModule () ) + ( "Batch Search Keeps Queries Independent", + fun () -> testBatchSearchKeepsQueriesIndependent () ) + ( "For-AI Docs Stay Compact", + fun () -> testForAiDocsStayCompact () ) + ( "Internal PACKAGE Prefix Guidance", + fun () -> testInternalPackagePrefixGuidance () ) + ( "Bare Prelude Constructors", + fun () -> testBarePreludeConstructors () ) ("List Types", fun () -> testListTypes ()) ("Help for Run", fun () -> testHelpForRun ()) @@ -85,9 +108,9 @@ type TestSummary = let runAllTests (): Int = let tests = allTests () - Stdlib.printLine "" - Stdlib.printLine "Darklang CLI Tests" - Stdlib.printLine "==================" + Stdlib.println "" + Stdlib.println "Darklang CLI Tests" + Stdlib.println "==================" let initialSummary = TestSummary @@ -99,38 +122,38 @@ let runAllTests (): Int = let finalSummary = tests |> Stdlib.List.fold initialSummary (fun summary (name, testFn) -> - Stdlib.printLine "" - Stdlib.printLine $"Running: {name} ..." + Stdlib.println "" + Stdlib.println $"Running: {name} ..." let newSummary = { summary with totalTests = summary.totalTests + 1 } match testFn () with | Pass -> - Stdlib.printLine $"✓ PASS - {name}" + Stdlib.println $"✓ PASS - {name}" { newSummary with passedTests = newSummary.passedTests + 1 } | Fail message -> - Stdlib.printLine $"✗ FAIL - {name}" - Stdlib.printLine $" Reason: {message}" + Stdlib.println $"✗ FAIL - {name}" + Stdlib.println $" Reason: {message}" { newSummary with failedTests = newSummary.failedTests + 1 failedTestNames = Stdlib.List.push newSummary.failedTestNames name }) - Stdlib.printLine "" - Stdlib.printLine "📊 Test Results Summary" - Stdlib.printLine "======================" - Stdlib.printLine $"Total tests: {Stdlib.Int.toString finalSummary.totalTests}" - Stdlib.printLine $"Passed: {Stdlib.Int.toString finalSummary.passedTests}" - Stdlib.printLine $"Failed: {Stdlib.Int.toString finalSummary.failedTests}" + Stdlib.println "" + Stdlib.println "📊 Test Results Summary" + Stdlib.println "======================" + Stdlib.println $"Total tests: {Stdlib.Int.toString finalSummary.totalTests}" + Stdlib.println $"Passed: {Stdlib.Int.toString finalSummary.passedTests}" + Stdlib.println $"Failed: {Stdlib.Int.toString finalSummary.failedTests}" if finalSummary.failedTests == 0 then - Stdlib.printLine "🎉 All tests passed!" + Stdlib.println "🎉 All tests passed!" 0 else - Stdlib.printLine "🚨 Some tests failed!" + Stdlib.println "🚨 Some tests failed!" if Stdlib.Bool.not (Stdlib.List.isEmpty finalSummary.failedTestNames) then - Stdlib.printLine "Failed tests:" + Stdlib.println "Failed tests:" finalSummary.failedTestNames |> Stdlib.List.iter (fun testName -> - Stdlib.printLine $" ✗ {testName}") + Stdlib.println $" ✗ {testName}") 1 diff --git a/packages/darklang/cli/tests/tests-tui.dark b/packages/darklang/cli/tests/tests-tui.dark index 70d4498b63..0b88a3f1c8 100644 --- a/packages/darklang/cli/tests/tests-tui.dark +++ b/packages/darklang/cli/tests/tests-tui.dark @@ -427,7 +427,7 @@ let testWorkbenchCapturesStrayOutput (): TestResult = // letting the print through. let (value, out) = Darklang.Cli.Workbench.captureOutputAnd (fun () -> - Stdlib.printLine "this must not reach the terminal" + Stdlib.println "this must not reach the terminal" 41 + 1) if (value == 42) && (Stdlib.String.contains out "must not reach") then TestResult.Pass diff --git a/packages/darklang/cli/tests/tests.dark b/packages/darklang/cli/tests/tests.dark index 1cafd8fb38..b9e4fa2574 100644 --- a/packages/darklang/cli/tests/tests.dark +++ b/packages/darklang/cli/tests/tests.dark @@ -117,6 +117,208 @@ let testEvalListLength (): TestResult = TestResult.Fail $"Expected '3', got '{output}'" +let testSleepSeconds (): TestResult = + let output = + runWithCommand ["eval"; "Stdlib.Cli.Posix.sleepSeconds 0.0"] + + if output == "" then + TestResult.Pass + else + TestResult.Fail $"Expected sleepSeconds to return silent Unit, got '{output}'" + + +let testModuleCommand (): TestResult = + let suffix = Stdlib.Int.toString (Stdlib.Int.random 100000 999999) + let branchName = "cli-module-test-" ++ suffix + let moduleName = "Tests.CliModule" + let initialSource = + [ "type Counter = { value: Int }" + "" + "val initial = Counter { value = 0 }" + "" + "let double (n: Int) : Int = n * 2" + "" + "let quadruple (n: Int) : Int = double (double n)" + ] |> Stdlib.String.join "\n" + let updatedSource = + [ "type Counter = { value: Int }" + "" + "val initial = Counter { value = 1 }" + "" + "let double (n: Int) : Int = n * 3" + "" + "let quadruple (n: Int) : Int = double (double n)" + ] |> Stdlib.String.join "\n" + let invalidSource = + [ "type Wrapped = Wrap of (Int * String)" + "" + "let shouldNotExist (value: Wrapped) : Wrapped =" + " match value with" + " | Wrap((number, text)) -> Wrapped.Wrap(number, text)" + ] |> Stdlib.String.join "\n" + let initialPath = Stdlib.Cli.File.createTemp () |> Builtin.unwrap + let updatedPath = Stdlib.Cli.File.createTemp () |> Builtin.unwrap + let invalidPath = Stdlib.Cli.File.createTemp () |> Builtin.unwrap + let _ = + (Stdlib.Cli.File.writeAtomic initialPath initialSource) |> Builtin.unwrap + let _ = + (Stdlib.Cli.File.writeAtomic updatedPath updatedSource) |> Builtin.unwrap + let _ = + (Stdlib.Cli.File.writeAtomic invalidPath invalidSource) |> Builtin.unwrap + let _ = runWithCommand ["branch"; "create"; branchName] + let moduleOutput = + runWithCommand + [ "--branch" + branchName + "module" + "/" ++ moduleName + initialPath ] + let initialEval = + runWithCommand + [ "--branch"; branchName; "eval"; moduleName ++ ".quadruple 3" ] + let updateOutput = + runWithCommand + [ "--branch" + branchName + "module" + "/" ++ moduleName + updatedPath ] + let updatedEval = + runWithCommand + [ "--branch"; branchName; "eval"; moduleName ++ ".quadruple 3" ] + let invalidOutput = + runWithCommand + [ "--branch" + branchName + "module" + "/" ++ moduleName + invalidPath ] + let searchOutput = + runWithCommand + [ "--branch" + branchName + "search" + moduleName ++ ".shouldNotExist" + "--exact" + "--fn" ] + let afterInvalidEval = + runWithCommand + [ "--branch"; branchName; "eval"; moduleName ++ ".quadruple 3" ] + let _ = runWithCommand ["branch"; "archive"; branchName] + let _ = Stdlib.Cli.File.delete initialPath + let _ = Stdlib.Cli.File.delete updatedPath + let _ = Stdlib.Cli.File.delete invalidPath + + if + Stdlib.String.contains moduleOutput "Defined 4 declarations" + && initialEval == "12" + && Stdlib.String.contains updateOutput "Defined 4 declarations" + && updatedEval == "27" + && Stdlib.String.contains invalidOutput "expects 1 field(s)" + && Stdlib.String.contains searchOutput "No results found" + && afterInvalidEval == "27" + then + TestResult.Pass + else + let details = + [ $"create: {moduleOutput}" + $"initial eval: {initialEval}" + $"update: {updateOutput}" + $"updated eval: {updatedEval}" + $"invalid: {invalidOutput}" + $"invalid item search: {searchOutput}" + $"eval after invalid: {afterInvalidEval}" + ] |> Stdlib.String.join "; " + TestResult.Fail + $"Expected atomic create, update, and rejection; got {details}" + + +let testAuthoringSourceContracts (): TestResult = + let suffix = Stdlib.Int.toString (Stdlib.Int.random 100000 999999) + let branchName = "cli-authoring-test-" ++ suffix + let moduleName = "Tests.CliAuthoring" + let fnSource = + [ "let main (name: String) : String =" + " \"Hello, \" ++ name" + ] |> Stdlib.String.join "\n" + let mismatchedSource = + "let other (name: String) : String = name" + let invalidModuleSource = + [ "let greeting = \"hello\"" + "" + "let main (name: String) : String = greeting ++ name" + ] |> Stdlib.String.join "\n" + let invalidModulePath = Stdlib.Cli.File.createTemp () |> Builtin.unwrap + let _ = + Stdlib.Cli.File.writeAtomic invalidModulePath invalidModuleSource + |> Builtin.unwrap + let _ = runWithCommand [ "branch"; "create"; branchName ] + let fnOutput = + runWithCommand + [ "--branch" + branchName + "fn" + "/" ++ moduleName ++ ".main" + fnSource ] + let evalOutput = + runWithCommand + [ "--branch" + branchName + "eval" + moduleName ++ ".main \"Dark\"" ] + let fragmentOutput = + runWithCommand + [ "--branch" + branchName + "fn" + "/" ++ moduleName ++ ".double" + "(n: Int) : Int = n * 2" ] + let fragmentEval = + runWithCommand + [ "--branch"; branchName; "eval"; moduleName ++ ".double 4" ] + let unresolvedOutput = + runWithCommand + [ "--branch" + branchName + "fn" + "/" ++ moduleName ++ ".negate" + "(value: Bool) : Bool = Bool.not value" ] + let mismatchOutput = + runWithCommand + [ "--branch" + branchName + "fn" + "/" ++ moduleName ++ ".main" + mismatchedSource ] + let moduleOutput = + runWithCommand + [ "--branch" + branchName + "module" + "/Tests.InvalidModuleValue" + invalidModulePath ] + let _ = runWithCommand [ "branch"; "archive"; branchName ] + let _ = Stdlib.Cli.File.delete invalidModulePath + + if + Stdlib.String.contains fnOutput "Created function" + && evalOutput == "Hello, Dark" + && Stdlib.String.contains fragmentOutput "Created function" + && fragmentEval == "8" + && Stdlib.String.contains unresolvedOutput "Did you mean `Stdlib.Bool.not`?" + && Stdlib.String.contains mismatchOutput "does not match target" + && Stdlib.String.contains moduleOutput "must use 'val'" + && Stdlib.Bool.not (Stdlib.String.contains moduleOutput "end of file") + then + TestResult.Pass + else + TestResult.Fail + ($"Expected source-shaped fn input and a focused module-value error; " + ++ $"fn={fnOutput}; eval={evalOutput}; fragment={fragmentOutput}; " + ++ $"fragment eval={fragmentEval}; unresolved={unresolvedOutput}; " + ++ $"mismatch={mismatchOutput}; module={moduleOutput}") + + let testListFunctions (): TestResult = let output = runWithCommand ["ls"; "Stdlib.List"] if Stdlib.String.contains output "Functions" && @@ -137,6 +339,152 @@ let testViewFunction (): TestResult = TestResult.Fail "Expected function signature for List.head" +let testSearchMatchesModuleAndCamelCaseName (): TestResult = + let output = runWithCommand ["search"; "string toList"; "--fn"] + if Stdlib.String.contains output "Darklang.Stdlib.String.toList" then + TestResult.Pass + else + TestResult.Fail + $"Expected `string toList` to find Stdlib.String.toList, got: {output}" + + +let testSearchMatchesModuleAndOperationName (): TestResult = + let output = runWithCommand ["search"; "json parse"; "--fn"] + if Stdlib.String.contains output "Darklang.Stdlib.Json.parse" then + TestResult.Pass + else + TestResult.Fail + $"Expected `json parse` to find Stdlib.Json.parse, got: {output}" + + +let testSearchPrefersTextFileFunctions (): TestResult = + let readOutput = runWithCommand ["search"; "file read"; "--fn"] + let writeOutput = runWithCommand ["search"; "file write"; "--fn"] + + match + ( Stdlib.String.indexOf readOutput "Darklang.Stdlib.Cli.File.readText", + Stdlib.String.indexOf readOutput "Darklang.Stdlib.Cli.Posix.fdRead", + Stdlib.String.indexOf writeOutput "Darklang.Stdlib.Cli.File.writeText", + Stdlib.String.indexOf writeOutput "Darklang.Stdlib.Cli.Bash.overwriteBashrc" ) + with + | (Some readText, Some fdRead, Some writeText, Some bashWrite) -> + if readText < fdRead && writeText < bashWrite then + TestResult.Pass + else + TestResult.Fail "Expected text-file functions before lower-level matches" + | _ -> TestResult.Fail "Expected file searches to contain preferred and fallback functions" + + +let testSearchShowsSummaryForStrongMatches (): TestResult = + let output = runWithCommand ["search"; "random"; "--fn"] + if Stdlib.String.contains output "Charset is" then + TestResult.Pass + else + TestResult.Fail + $"Expected default search to summarize the exact String.random match, got: {output}" + + +let testSearchPrefersDefaultIntOverFixedWidths (): TestResult = + let output = runWithCommand ["search"; "random"; "--fn"] + + match + ( Stdlib.String.indexOf output "Darklang.Stdlib.Int.random", + Stdlib.String.indexOf output "Darklang.Stdlib.Int64.random" ) + with + | (Some defaultInt, Some fixedWidthInt) -> + if defaultInt < fixedWidthInt then + TestResult.Pass + else + TestResult.Fail + $"Expected Int.random before fixed-width variants, got: {output}" + | _ -> + TestResult.Fail + $"Expected random search to contain Int.random and Int64.random, got: {output}" + + +let testSearchFindsNestedHelpersFromParentModule (): TestResult = + let output = + runWithCommand ["search"; "--batch"; "AltJson.get"; "--fn"] + + if Stdlib.String.contains output "Darklang.Stdlib.AltJson.Helpers.getInt" then + TestResult.Pass + else + TestResult.Fail + $"Expected `AltJson.get` to find nested AltJson helpers, got: {output}" + + +let testBatchSearchKeepsQueriesIndependent (): TestResult = + let output = + runWithCommand + [ "search" + "--batch" + "string toList" + "json parse" + "file write" + "--fn" ] + + if + Stdlib.String.contains output "Search results for: string toList" + && Stdlib.String.contains output "Darklang.Stdlib.String.toList" + && Stdlib.String.contains output "Search results for: json parse" + && Stdlib.String.contains output "Darklang.Stdlib.Json.parse" + && Stdlib.String.contains output "Search results for: file write" + && Stdlib.String.contains output "Darklang.Stdlib.Cli.File.writeText" + then + TestResult.Pass + else + TestResult.Fail + $"Expected three independent batch search results, got: {output}" + + +let testForAiDocsStayCompact (): TestResult = + let content = Cli.Docs.ForAI.content () + let length = Stdlib.String.length content + if + length <= 2600 + && Stdlib.String.contains content "docs syntax | types | operators" + && Stdlib.String.contains content "Batch search returns two ranked signatures" + then + TestResult.Pass + else + TestResult.Fail + $"Expected concise routed AI docs, got {Stdlib.Int.toString length} characters" + + +let testInternalPackagePrefixGuidance (): TestResult = + let internalName = + ["PACKAGE"; "Darklang"; "Stdlib"; "List"; "map"] + if + Cli.Packages.Errors.isInternalPackageName internalName + && (Cli.Packages.Errors.sourceNameWithoutInternalPrefix internalName + == "Stdlib.List.map") + then + TestResult.Pass + else + TestResult.Fail "Expected PACKAGE.Darklang.Stdlib.* to suggest Stdlib.*" + + +let testBarePreludeConstructors (): TestResult = + let some = + runWithCommand + [ "eval"; "(Some 5) == Stdlib.Option.Option.Some 5" ] + let none = + runWithCommand + [ "eval"; "None == Stdlib.Option.Option.None" ] + let ok = + runWithCommand + [ "eval"; "(Ok 5) == Stdlib.Result.Result.Ok 5" ] + let error = + runWithCommand + [ "eval"; "(Error \"no\") == Stdlib.Result.Result.Error \"no\"" ] + + if some == "true" && none == "true" && ok == "true" && error == "true" then + TestResult.Pass + else + TestResult.Fail + $"Expected bare prelude constructors to resolve; got Some={some}, None={none}, Ok={ok}, Error={error}" + + let testListTypes (): TestResult = let output = runWithCommand ["ls"; "Stdlib.Option"] if Stdlib.String.contains output "Types" && diff --git a/packages/darklang/cli/tracing.dark b/packages/darklang/cli/tracing.dark index f914984603..66f5daf4e5 100644 --- a/packages/darklang/cli/tracing.dark +++ b/packages/darklang/cli/tracing.dark @@ -25,7 +25,7 @@ let emptyStateHint () : String = /// Print the empty-result explanation, dimmed. let printEmptyStateHint () : Unit = - Stdlib.printLine (Colors.dimText (emptyStateHint ())) + Stdlib.println (Colors.dimText (emptyStateHint ())) /// Filters applied when rendering a trace tree. Each filter hides matching @@ -88,19 +88,19 @@ let parsePositionalErr (input: String) (label: String) : String = /// "Trace not found: --fake-arg". let resolveTraceID (input: String) : Stdlib.Option.Option = if Stdlib.String.startsWith input "-" then - Stdlib.printLine (Colors.error $"Unknown flag: {input}") + Stdlib.println (Colors.error $"Unknown flag: {input}") Stdlib.Option.Option.None else // Trim accidental whitespace from copy-paste; never part of a UUID. let trimmed = Stdlib.String.trim input if trimmed == "" then - Stdlib.printLine (Colors.error "Trace ID must not be empty") + Stdlib.println (Colors.error "Trace ID must not be empty") Stdlib.Option.Option.None else match Builtin.tracesResolveID trimmed with | Ok fullID -> Stdlib.Option.Option.Some fullID | Error msg -> - Stdlib.printLine (Colors.error msg) + Stdlib.println (Colors.error msg) Stdlib.Option.Option.None @@ -187,7 +187,7 @@ let confirmDestructive (autoConfirm: Bool) (action: String) : Bool = else if Stdlib.Bool.not (Builtin.stdinIsInteractive ()) then true else - Stdlib.printLine $"{action} (y/n): " + Stdlib.println $"{action} (y/n): " let response = Builtin.stdinReadLine () (response == "y") || (response == "Y") @@ -197,9 +197,9 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = // don't have to enumerate their position. `--yes`/`-y` for destructive // confirmations; `--json` for output format; `--view` is find-only. let autoConfirm = - (Stdlib.List.member args "--yes") || (Stdlib.List.member args "-y") - let wantsJson = Stdlib.List.member args "--json" - let wantsFindView = Stdlib.List.member args "--view" + (Stdlib.List.contains args "--yes") || (Stdlib.List.contains args "-y") + let wantsJson = Stdlib.List.contains args "--json" + let wantsFindView = Stdlib.List.contains args "--view" let args = args |> Stdlib.List.filter (fun a -> @@ -212,11 +212,11 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | [ "list" ] -> if wantsJson then executeListJson state 20 else executeList state 20 | [ "list"; "--fn" ] -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces list --fn [limit] [--json]") state | [ "list"; "--route" ] -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces list --route [limit] [--json]") state | [ "list"; limitStr ] -> @@ -224,7 +224,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | Ok limit -> if wantsJson then executeListJson state limit else executeList state limit | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr limitStr "limit")) + Stdlib.println (Colors.error (parsePositionalErr limitStr "limit")) state | [ "list"; "--fn"; fnName ] -> executeListByFn state fnName 20 wantsJson @@ -232,7 +232,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match Stdlib.Int.parse limitStr with | Ok limit -> executeListByFn state fnName limit wantsJson | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr limitStr "limit")) + Stdlib.println (Colors.error (parsePositionalErr limitStr "limit")) state | [ "list"; "--route"; route ] -> executeListByRoute state route 20 wantsJson @@ -240,7 +240,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match Stdlib.Int.parse limitStr with | Ok limit -> executeListByRoute state route limit wantsJson | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr limitStr "limit")) + Stdlib.println (Colors.error (parsePositionalErr limitStr "limit")) state | [ "follow" ] -> @@ -255,21 +255,21 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | Ok limit -> if wantsJson then executeStatsJson state limit else executeStats state limit | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr limitStr "limit")) + Stdlib.println (Colors.error (parsePositionalErr limitStr "limit")) state | [ "tail" ] -> executeTail state Stdlib.Option.Option.None 1 // Flag-without-arg case must precede the `[ "tail"; nStr ]` arm // (which would otherwise treat "--route" as a malformed N). | [ "tail"; "--route" ] -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces tail [N] --route ") state | [ "tail"; nStr ] -> match Stdlib.Int.parse nStr with | Ok n -> executeTail state Stdlib.Option.Option.None n | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr nStr "trace number")) + Stdlib.println (Colors.error (parsePositionalErr nStr "trace number")) state | [ "tail"; "--route"; route ] -> executeTail state (Stdlib.Option.Option.Some route) 1 @@ -278,7 +278,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = match Stdlib.Int.parse nStr with | Ok n -> executeTail state (Stdlib.Option.Option.Some route) n | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr nStr "trace number")) + Stdlib.println (Colors.error (parsePositionalErr nStr "trace number")) state | [ "find"; pattern ] -> @@ -294,7 +294,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = if wantsJson then executeFindJson state pattern limit else executeFind state pattern limit | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr limitStr "limit")) + Stdlib.println (Colors.error (parsePositionalErr limitStr "limit")) state | [ "hotspots" ] -> @@ -306,7 +306,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = if wantsJson then executeHotspotsJson state limit else executeHotspots state limit | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr limitStr "limit")) + Stdlib.println (Colors.error (parsePositionalErr limitStr "limit")) state | "view" :: traceID :: flagArgs -> @@ -316,7 +316,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = | Some full -> executeView state full opts | None -> state | Error msg -> - Stdlib.printLine (Colors.error msg) + Stdlib.println (Colors.error msg) state | [ "replay"; traceID ] -> @@ -331,7 +331,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = if confirmDestructive autoConfirm "Delete all traces?" then executeClear state else - Stdlib.printLine "Cancelled." + Stdlib.println "Cancelled." state | [ "delete"; "--before"; durationStr ] -> @@ -340,7 +340,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = $"Delete traces older than {durationStr}?" then executeClearBefore state durationStr else - Stdlib.printLine "Cancelled." + Stdlib.println "Cancelled." state | [ "delete"; "--keep"; nStr ] -> @@ -351,10 +351,10 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = $"Keep the {Stdlib.Int.toString n} most-recent traces and drop the rest?" then executePruneKeep state n else - Stdlib.printLine "Cancelled." + Stdlib.println "Cancelled." state | Error _ -> - Stdlib.printLine (Colors.error (parsePositionalErr nStr "--keep N")) + Stdlib.println (Colors.error (parsePositionalErr nStr "--keep N")) state | [ "delete"; traceID ] -> @@ -363,7 +363,7 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = if confirmDestructive autoConfirm $"Delete trace {full}?" then executeDelete state full else - Stdlib.printLine "Cancelled." + Stdlib.println "Cancelled." state | None -> state @@ -371,14 +371,14 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = // that don't conflict with positional-catching arms higher up // (the list/tail variants are handled inline above). | [ "follow"; "--route" ] -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces follow --route [--json]") state | [ "delete"; "--keep" ] -> - Stdlib.printLine (Colors.error "Usage: traces delete --keep ") + Stdlib.println (Colors.error "Usage: traces delete --keep ") state | [ "delete"; "--before" ] -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces delete --before (e.g. 30s, 5m, 1h, 2d)") state @@ -387,18 +387,18 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = // argument. Better than dumping the full multi-page help — the user // ran a known command, just forgot the id/path. | [ "view" ] -> - Stdlib.printLine (Colors.error "Usage: traces view [flags]") + Stdlib.println (Colors.error "Usage: traces view [flags]") state | [ "replay" ] -> - Stdlib.printLine (Colors.error "Usage: traces replay ") + Stdlib.println (Colors.error "Usage: traces replay ") state | [ "delete" ] -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces delete | --all | --keep | --before ") state | [ "find" ] -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces find [limit] [--view] [--json]") state @@ -406,40 +406,40 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = // Catch any other malformed follow invocation — anything not matched // by the earlier specific arms (e.g. `traces follow --fake-arg`). | "follow" :: _ -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces follow [--route ] [--json]") state // Catch malformed arity-1 invocations (e.g. `traces replay --fake-arg`) // that the strict-length arms above missed. | "replay" :: _ -> - Stdlib.printLine (Colors.error "Usage: traces replay ") + Stdlib.println (Colors.error "Usage: traces replay ") state | "delete" :: _ -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces delete | --all | --keep | --before ") state | "tail" :: _ -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces tail [N] [--route ]") state | "list" :: _ -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces list [limit] | list --fn [limit] [--json] | list --route [limit] [--json]") state | "find" :: _ -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces find [limit] [--view] [--json]") state | "hotspots" :: _ -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces hotspots [trace-limit] [--json]") state | "stats" :: _ -> - Stdlib.printLine + Stdlib.println (Colors.error "Usage: traces stats [limit] [--json]") state @@ -458,8 +458,8 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = if Stdlib.Bool.not isKnownHead then match args with | head :: _ -> - Stdlib.printLine (Colors.error $"Unknown subcommand: {head}") - Stdlib.printLine "" + Stdlib.println (Colors.error $"Unknown subcommand: {head}") + Stdlib.println "" help state state | [] -> help state; state @@ -470,22 +470,22 @@ let execute (state: Cli.AppState) (args: List) : Cli.AppState = let executeList (state: Cli.AppState) (limit: Int) : Cli.AppState = if limit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesList limit match rows with | [] -> - Stdlib.printLine "No traces found." + Stdlib.println "No traces found." printEmptyStateHint () state | _ -> - Stdlib.printLine $"Recent traces (last {Stdlib.Int.toString limit}):" - Stdlib.printLine "" + Stdlib.println $"Recent traces (last {Stdlib.Int.toString limit}):" + Stdlib.println "" printTraceRows rows - Stdlib.printLine "" - Stdlib.printLine "Use 'traces view ' to see details." + Stdlib.println "" + Stdlib.println "Use 'traces view ' to see details." state @@ -494,11 +494,11 @@ let executeList (state: Cli.AppState) (limit: Int) : Cli.AppState = /// footers. Designed for piping into jq / scripts. Empty store prints `[]`. let executeListJson (state: Cli.AppState) (limit: Int) : Cli.AppState = if limit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesList limit - Stdlib.printLine (Stdlib.Json.serialize> rows) + Stdlib.println (Stdlib.Json.serialize> rows) state @@ -509,28 +509,28 @@ let executeListByFn (jsonMode: Bool) : Cli.AppState = if Stdlib.String.trim fnName == "" then - Stdlib.printLine (Colors.error "--fn pattern must not be empty") + Stdlib.println (Colors.error "--fn pattern must not be empty") state else if limit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesListByFn fnName limit if jsonMode then - Stdlib.printLine (Stdlib.Json.serialize> rows) + Stdlib.println (Stdlib.Json.serialize> rows) state else match rows with | [] -> - Stdlib.printLine $"No traces found calling '{fnName}'." + Stdlib.println $"No traces found calling '{fnName}'." printEmptyStateHint () state | _ -> - Stdlib.printLine $"Traces calling '{fnName}' (last {Stdlib.Int.toString limit}):" - Stdlib.printLine "" + Stdlib.println $"Traces calling '{fnName}' (last {Stdlib.Int.toString limit}):" + Stdlib.println "" printTraceRows rows - Stdlib.printLine "" - Stdlib.printLine "Use 'traces view ' to see details." + Stdlib.println "" + Stdlib.println "Use 'traces view ' to see details." state @@ -542,29 +542,29 @@ let executeHotspots (traceLimit: Int) : Cli.AppState = if traceLimit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesHotspots traceLimit match rows with | [] -> - Stdlib.printLine $"No fn-call data in the last {Stdlib.Int.toString traceLimit} traces." + Stdlib.println $"No fn-call data in the last {Stdlib.Int.toString traceLimit} traces." state | _ -> let fnCount = Stdlib.List.length rows let fnWord = if fnCount == 1 then "fn" else "fns" - Stdlib.printLine + Stdlib.println $"Hotspots (last {Stdlib.Int.toString traceLimit} traces, top {Stdlib.Int.toString fnCount} {fnWord} by total ms):" - Stdlib.printLine "" - Stdlib.printLine " total ms │ max ms │ count │ fn" - Stdlib.printLine " ─────────┼──────────┼───────┼─────────────────────────" + Stdlib.println "" + Stdlib.println " total ms │ max ms │ count │ fn" + Stdlib.println " ─────────┼──────────┼───────┼─────────────────────────" Stdlib.List.iter rows (fun row -> let (name, count, total, max) = row let totalStr = padNum total 9 let maxStr = padNum max 8 let countStr = padNum count 5 - Stdlib.printLine $" {totalStr} │ {maxStr} │ {countStr} │ {name}") + Stdlib.println $" {totalStr} │ {maxStr} │ {countStr} │ {name}") state @@ -576,7 +576,7 @@ let executeHotspotsJson (traceLimit: Int) : Cli.AppState = if traceLimit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesHotspots traceLimit @@ -589,7 +589,7 @@ let executeHotspotsJson ++ $",\"totalMs\":{Stdlib.Int.toString total}" ++ $",\"maxMs\":{Stdlib.Int.toString max}}}") |> Stdlib.String.join "," - Stdlib.printLine $"[{body}]" + Stdlib.println $"[{body}]" state @@ -613,26 +613,26 @@ let executeFind (limit: Int) : Cli.AppState = if pattern == "" then - Stdlib.printLine (Colors.error "find pattern must not be empty") + Stdlib.println (Colors.error "find pattern must not be empty") state else if limit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesFind pattern limit match rows with | [] -> - Stdlib.printLine $"No traces match '{pattern}'." + Stdlib.println $"No traces match '{pattern}'." printEmptyStateHint () state | _ -> - Stdlib.printLine + Stdlib.println $"Traces matching '{pattern}' (last {Stdlib.Int.toString limit}):" - Stdlib.printLine "" + Stdlib.println "" printTraceRows rows - Stdlib.printLine "" - Stdlib.printLine "Use 'traces view ' to see details." + Stdlib.println "" + Stdlib.println "Use 'traces view ' to see details." state @@ -645,14 +645,14 @@ let executeFindJson (limit: Int) : Cli.AppState = if pattern == "" then - Stdlib.printLine (Colors.error "find pattern must not be empty") + Stdlib.println (Colors.error "find pattern must not be empty") state else if limit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesFind pattern limit - Stdlib.printLine (Stdlib.Json.serialize> rows) + Stdlib.println (Stdlib.Json.serialize> rows) state @@ -661,18 +661,18 @@ let executeFindJson /// the others are noted but the user gets the freshest one. let executeFindView (state: Cli.AppState) (pattern: String) : Cli.AppState = if pattern == "" then - Stdlib.printLine (Colors.error "find pattern must not be empty") + Stdlib.println (Colors.error "find pattern must not be empty") state else let rows = Builtin.tracesFind pattern 1 match rows with | [] -> - Stdlib.printLine $"No traces match '{pattern}'." + Stdlib.println $"No traces match '{pattern}'." printEmptyStateHint () state | row :: _ -> - Stdlib.printLine $"Latest trace matching '{pattern}': {row.traceId}" - Stdlib.printLine "" + Stdlib.println $"Latest trace matching '{pattern}': {row.traceId}" + Stdlib.println "" executeView state row.traceId defaultViewOptions @@ -692,10 +692,10 @@ let executeTail | Some r -> Stdlib.String.trim r == "" | None -> false if n < 1 then - Stdlib.printLine (Colors.error "tail N must be ≥ 1") + Stdlib.println (Colors.error "tail N must be ≥ 1") state else if routeIsEmpty then - Stdlib.printLine (Colors.error "--route pattern must not be empty") + Stdlib.println (Colors.error "--route pattern must not be empty") state else // Over-fetch so route-filtered Nth candidate is reachable; the @@ -730,7 +730,7 @@ let executeTail else let nStr = Stdlib.Int.toString n $"No {nStr}{ordinalSuffix n}-most-recent trace{suffix}." - Stdlib.printLine msg + Stdlib.println msg state | row :: _ -> executeView state row.traceId defaultViewOptions @@ -741,29 +741,29 @@ let executeTail /// for "which routes are heavy?" vs "which fns are heavy?". let executeStats (state: Cli.AppState) (limit: Int) : Cli.AppState = if limit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesStatsByHandler limit match rows with | [] -> - Stdlib.printLine "No traces found." + Stdlib.println "No traces found." printEmptyStateHint () state | _ -> - Stdlib.printLine + Stdlib.println $"Per-handler stats (last {Stdlib.Int.toString limit} traces):" - Stdlib.printLine "" - Stdlib.printLine " total ms │ max ms │ count │ handler" - Stdlib.printLine " ─────────┼──────────┼───────┼─────────────────────────" + Stdlib.println "" + Stdlib.println " total ms │ max ms │ count │ handler" + Stdlib.println " ─────────┼──────────┼───────┼─────────────────────────" Stdlib.List.iter rows (fun row -> let (handler, count, total, max) = row let totalStr = padNum total 9 let maxStr = padNum max 8 let countStr = padNum count 5 - Stdlib.printLine $" {totalStr} │ {maxStr} │ {countStr} │ {handler}") - Stdlib.printLine "" + Stdlib.println $" {totalStr} │ {maxStr} │ {countStr} │ {handler}") + Stdlib.println "" // The route-drill hint only makes sense if there are HTTP // handlers in the result; eval/run-only stores get no help // from `--route`. HTTP handler descriptions look like @@ -774,7 +774,7 @@ let executeStats (state: Cli.AppState) (limit: Int) : Cli.AppState = let (handler, _, _, _) = row Stdlib.String.contains handler "/") if hasHttp then - Stdlib.printLine "Use 'traces list --route ' to drill into a route." + Stdlib.println "Use 'traces list --route ' to drill into a route." state @@ -783,7 +783,7 @@ let executeStats (state: Cli.AppState) (limit: Int) : Cli.AppState = /// for piping. Empty store prints `[]`. let executeStatsJson (state: Cli.AppState) (limit: Int) : Cli.AppState = if limit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else let rows = Builtin.tracesStatsByHandler limit @@ -796,7 +796,7 @@ let executeStatsJson (state: Cli.AppState) (limit: Int) : Cli.AppState = ++ $",\"totalMs\":{Stdlib.Int.toString total}" ++ $",\"maxMs\":{Stdlib.Int.toString max}}}") |> Stdlib.String.join "," - Stdlib.printLine $"[{body}]" + Stdlib.println $"[{body}]" state @@ -828,9 +828,9 @@ let followLoop Stdlib.List.iter chronological (fun row -> if jsonMode then - Stdlib.printLine (Stdlib.Json.serialize row) + Stdlib.println (Stdlib.Json.serialize row) else - Stdlib.printLine $" {row.timestamp} {row.traceId} {row.handler}") + Stdlib.println $" {row.timestamp} {row.traceId} {row.handler}") let newLastSeen = match rows with @@ -858,7 +858,7 @@ let executeFollow | Some r -> Stdlib.String.trim r == "" | None -> false if routeIsEmpty then - Stdlib.printLine (Colors.error "--route pattern must not be empty") + Stdlib.println (Colors.error "--route pattern must not be empty") state else if jsonMode == false then @@ -866,8 +866,8 @@ let executeFollow match routeFilter with | Some r -> $"route '{r}'" | None -> "all routes" - Stdlib.printLine $"Following traces for {label}. Ctrl+C to stop." - Stdlib.printLine "" + Stdlib.println $"Following traces for {label}. Ctrl+C to stop." + Stdlib.println "" // Anchor: ignore traces that already existed when we started. let initialRows = Builtin.tracesList 1 @@ -890,10 +890,10 @@ let executeListByRoute (jsonMode: Bool) : Cli.AppState = if Stdlib.String.trim route == "" then - Stdlib.printLine (Colors.error "--route pattern must not be empty") + Stdlib.println (Colors.error "--route pattern must not be empty") state else if limit < 1 then - Stdlib.printLine (Colors.error "Limit must be ≥ 1") + Stdlib.println (Colors.error "Limit must be ≥ 1") state else // Over-fetch then filter; tracesList returns the most-recent N rows @@ -910,20 +910,20 @@ let executeListByRoute |> Stdlib.List.take limit if jsonMode then - Stdlib.printLine (Stdlib.Json.serialize> matching) + Stdlib.println (Stdlib.Json.serialize> matching) state else match matching with | [] -> - Stdlib.printLine $"No traces found for route filter '{route}'." + Stdlib.println $"No traces found for route filter '{route}'." printEmptyStateHint () state | _ -> - Stdlib.printLine $"Traces matching route '{route}' (last {Stdlib.Int.toString limit}):" - Stdlib.printLine "" + Stdlib.println $"Traces matching route '{route}' (last {Stdlib.Int.toString limit}):" + Stdlib.println "" printTraceRows matching - Stdlib.printLine "" - Stdlib.printLine "Use 'traces view ' to see details." + Stdlib.println "" + Stdlib.println "Use 'traces view ' to see details." state @@ -931,7 +931,7 @@ let executeListByRoute let printTraceRows (rows: List) : Unit = Stdlib.List.iter rows (fun row -> - Stdlib.printLine $" {row.timestamp} {row.traceId} {row.handler}") + Stdlib.println $" {row.timestamp} {row.traceId} {row.handler}") let formatInputs (branchId: Uuid) (inputs: List) : String = @@ -1096,7 +1096,7 @@ let executeView match result with | None -> - Stdlib.printLine (Colors.error $"Trace not found: {traceID}") + Stdlib.println (Colors.error $"Trace not found: {traceID}") state | Some trace -> let input = formatInputs state.currentBranchId trace.inputs @@ -1121,27 +1121,27 @@ let executeView let executeReplay (state: Cli.AppState) (traceID: String) : Cli.AppState = match Builtin.tracesGetInput traceID with | None -> - Stdlib.printLine + Stdlib.println (Colors.error $"Trace not found, or trace input isn't a replayable string: {traceID}") state | Some code -> - Stdlib.printLine $"Replaying trace {traceID}..." - Stdlib.printLine "" + Stdlib.println $"Replaying trace {traceID}..." + Stdlib.println "" match Builtin.cliEvaluateExpression state.accountID state.currentBranchId code false with | Ok outputOpt -> match outputOpt with - | Some output -> Stdlib.printLine output + | Some output -> Stdlib.println output | None -> () - Stdlib.printLine "" - Stdlib.printLine "Replay complete. A new trace was created for this execution." + Stdlib.println "" + Stdlib.println "Replay complete. A new trace was created for this execution." state | Error err -> let pretty = Cli.ExecutionError.toString state.currentBranchId err - Stdlib.printLine (Colors.error $"Replay failed: {pretty}") + Stdlib.println (Colors.error $"Replay failed: {pretty}") state @@ -1149,11 +1149,11 @@ let executeClear (state: Cli.AppState) : Cli.AppState = let count = Builtin.tracesClear () match count with | 0 -> - Stdlib.printLine "No traces to clear." + Stdlib.println "No traces to clear." state | _ -> let traceWord = if count == 1 then "trace" else "traces" - Stdlib.printLine $"Cleared {Stdlib.Int.toString count} {traceWord}." + Stdlib.println $"Cleared {Stdlib.Int.toString count} {traceWord}." state @@ -1175,7 +1175,7 @@ let executeClearBefore : Cli.AppState = match parseDuration durationStr with | Error msg -> - Stdlib.printLine (Colors.error msg) + Stdlib.println (Colors.error msg) state | Ok seconds -> let now = Stdlib.DateTime.now () @@ -1186,11 +1186,11 @@ let executeClearBefore let count = Builtin.tracesClearBefore cutoff match count with | 0 -> - Stdlib.printLine $"No traces older than {durationStr}." + Stdlib.println $"No traces older than {durationStr}." state | _ -> let traceWord = if count == 1 then "trace" else "traces" - Stdlib.printLine + Stdlib.println $"Cleared {Stdlib.Int.toString count} {traceWord} older than {durationStr}." state @@ -1201,10 +1201,10 @@ let executeDelete (state: Cli.AppState) (traceID: String) : Cli.AppState = let deleted = Builtin.tracesDelete traceID match deleted with | 0 -> - Stdlib.printLine (Colors.error $"Trace not found: {traceID}") + Stdlib.println (Colors.error $"Trace not found: {traceID}") state | _ -> - Stdlib.printLine $"Deleted trace {traceID}." + Stdlib.println $"Deleted trace {traceID}." state @@ -1213,13 +1213,13 @@ let executeDelete (state: Cli.AppState) (traceID: String) : Cli.AppState = /// grows; pair with `traces delete --all` for a full wipe. let executePruneKeep (state: Cli.AppState) (keepN: Int) : Cli.AppState = if keepN < 0 then - Stdlib.printLine (Colors.error "--keep N must be ≥ 0") + Stdlib.println (Colors.error "--keep N must be ≥ 0") state else let deleted = Builtin.tracesPruneKeep keepN match deleted with | 0 -> - Stdlib.printLine + Stdlib.println $"Already at or below {Stdlib.Int.toString keepN} traces; nothing to prune." state | _ -> @@ -1228,7 +1228,7 @@ let executePruneKeep (state: Cli.AppState) (keepN: Int) : Cli.AppState = if keepN == 0 then "none kept" else if keepN == 1 then "kept the most-recent" else $"kept the {Stdlib.Int.toString keepN} most-recent" - Stdlib.printLine + Stdlib.println $"Pruned {Stdlib.Int.toString deleted} {traceWord}; {keptPhrase}." state diff --git a/packages/darklang/cli/ui/components/dropdown.dark b/packages/darklang/cli/ui/components/dropdown.dark index 433c1af2e1..b91292130e 100644 --- a/packages/darklang/cli/ui/components/dropdown.dark +++ b/packages/darklang/cli/ui/components/dropdown.dark @@ -166,7 +166,7 @@ let renderMultiSelect (component: Core.Types.Component) (conte if item.separator then " ├" ++ Stdlib.String.repeat "─" (component.bounds.dimensions.width - 4) ++ "┤" else - let isSelected = Stdlib.List.``member`` model.selectedIndices i + let isSelected = Stdlib.List.contains model.selectedIndices i let checkMark = if isSelected then "☑" else "☐" let itemColor = @@ -193,7 +193,7 @@ let toggleMultiSelectItem (component: Core.Types.Component) (i if item.disabled || item.separator then component else - let isSelected = Stdlib.List.``member`` model.selectedIndices index + let isSelected = Stdlib.List.contains model.selectedIndices index let newSelectedIndices = if isSelected then Stdlib.List.filter model.selectedIndices (fun i -> i != index) diff --git a/packages/darklang/cli/ui/components/panel.dark b/packages/darklang/cli/ui/components/panel.dark index f5cbe59d99..d724b9f39e 100644 --- a/packages/darklang/cli/ui/components/panel.dark +++ b/packages/darklang/cli/ui/components/panel.dark @@ -259,7 +259,7 @@ let renderFilterPanel (component: Core.Types.Component) (conte let filterLines = model.filters |> Stdlib.List.indexedMap (fun i filter -> - let isApplied = Stdlib.List.``member`` model.appliedFilters i + let isApplied = Stdlib.List.contains model.appliedFilters i let checkbox = if isApplied then "☑" else "☐" let filterColor = if isApplied then Core.Types.Color.Success else Core.Types.Color.Default let styledLabel = Core.Rendering.colorize filterColor (checkbox ++ " " ++ filter.label) @@ -273,7 +273,7 @@ let renderFilterPanel (component: Core.Types.Component) (conte let toggleFilter (component: Core.Types.Component) (index: Int) : Core.Types.Component = let model = component.model if index >= 0 && index < (Stdlib.List.length model.filters) then - let isApplied = Stdlib.List.``member`` model.appliedFilters index + let isApplied = Stdlib.List.contains model.appliedFilters index let newAppliedFilters = if isApplied then Stdlib.List.filter model.appliedFilters (fun i -> i != index) diff --git a/packages/darklang/cli/workbench/app.dark b/packages/darklang/cli/workbench/app.dark index 9b1f25edff..3d5d04b704 100644 --- a/packages/darklang/cli/workbench/app.dark +++ b/packages/darklang/cli/workbench/app.dark @@ -136,7 +136,7 @@ let execute (cliState: Darklang.Cli.AppState) (_args: List) : Darklang.C // Report and stop here rather than falling through to the prompt: falling through works for the no-args // path (the prompt says the same thing) but would leave an explicit `dark wb` silent, and it prints the // message twice when it does fire. - Stdlib.printLine + Stdlib.println (Darklang.Cli.Tui.TerminalSupport.unavailableMessage reason) { cliState with isExiting = true } | Available -> @@ -176,7 +176,7 @@ let execute (cliState: Darklang.Cli.AppState) (_args: List) : Darklang.C Darklang.Cli.Page.SubApp (makeSubApp (Session { state = state; terminal = terminal })) } | Error message -> - Stdlib.printLine message + Stdlib.println message { cliState with isExiting = true } let help (state: Darklang.Cli.AppState) : Darklang.Cli.AppState = diff --git a/packages/darklang/cli/workbench/detail.dark b/packages/darklang/cli/workbench/detail.dark index b55685d9f3..402225a853 100644 --- a/packages/darklang/cli/workbench/detail.dark +++ b/packages/darklang/cli/workbench/detail.dark @@ -46,12 +46,12 @@ let renderTreeList (state: State) (region: UI.Layout.Region) : List if isKnown then Darklang.Cli.Registry.executeCommand cmd appState args diff --git a/packages/darklang/cli/workbench/loaders-misc.dark b/packages/darklang/cli/workbench/loaders-misc.dark index b59f4e3c3f..7800fc0e2a 100644 --- a/packages/darklang/cli/workbench/loaders-misc.dark +++ b/packages/darklang/cli/workbench/loaders-misc.dark @@ -244,7 +244,7 @@ let loadAppItems (branchId: Uuid) : List = let slugs = Darklang.Cli.Apps.Registry.installedSlugs () Darklang.Cli.Apps.Registry.available () |> Stdlib.List.map (fun a -> - let mark = if Stdlib.List.member slugs a.slug then "● " else "○ " + let mark = if Stdlib.List.contains slugs a.slug then "● " else "○ " BodyItem { name = mark ++ a.name ++ " · " ++ a.description; kind = "app"; isModule = false }) /// The body items for a given view. Home/Matter list packages (Matter with a Values lens); SCM lists its diff --git a/packages/darklang/cli/workbench/nav.dark b/packages/darklang/cli/workbench/nav.dark index e1ebb9429f..ba2d4ae9bc 100644 --- a/packages/darklang/cli/workbench/nav.dark +++ b/packages/darklang/cli/workbench/nav.dark @@ -150,7 +150,7 @@ let toggleInstall (state: State) : Step = | Some app -> let slugs = Darklang.Cli.Apps.Registry.installedSlugs () let (newSlugs, msg) = - if Stdlib.List.member slugs app.slug then + if Stdlib.List.contains slugs app.slug then (slugs |> Stdlib.List.filter (fun s -> s != app.slug), "uninstalled " ++ app.slug) else (Stdlib.List.append slugs [ app.slug ], "installed " ++ app.slug) diff --git a/packages/darklang/cli/workbench/types.dark b/packages/darklang/cli/workbench/types.dark index e1c2082ab6..aa1999bdbf 100644 --- a/packages/darklang/cli/workbench/types.dark +++ b/packages/darklang/cli/workbench/types.dark @@ -186,7 +186,7 @@ val gatedViewAccounts = [ "stachu"; "feriel" ] /// was machinery ahead of a need: there is exactly one rule today, which is "is this one of the two people /// building it". A config key can come back when someone actually wants a third answer. let gateEnabled (accountName: String) : Bool = - Stdlib.List.member gatedViewAccounts (Stdlib.String.toLowercase accountName) + Stdlib.List.contains gatedViewAccounts (Stdlib.String.toLowercase accountName) /// Every view as (absolute index, tab label, gate key) - built-ins then composed extensions. The single place /// that unifies the base views and the registry; all navigation/gating/labels derive from this list, so the @@ -240,7 +240,7 @@ let visibleViews (state: State) : List = viewSpecs () |> Stdlib.List.filter (fun spec -> let (i, _n, g) = spec - (g == "") || (Stdlib.List.member state.enabledGatedViews i)) + (g == "") || (Stdlib.List.contains state.enabledGatedViews i)) |> Stdlib.List.map (fun spec -> let (i, _n, _g) = spec i) @@ -250,7 +250,7 @@ let visibleViews (state: State) : List = /// single place `Builtin.stdoutCapture*` is used, so the many "run a command, show its output inside the /// frame" sites go through one wrapper instead of each touching the builtins directly. /// -/// Capturing is not optional in here. The workbench owns the whole screen, so a stray `printLine` from +/// Capturing is not optional in here. The workbench owns the whole screen, so a stray `println` from /// inside a command scrolls the alternate screen, and every frame after it lands that many rows off - which /// looks like two overlapping copies of the UI, not like a missing line. `cliEvaluateExpression` prints a /// three-line call-stack header on any runtime error, so this is reachable by typing a typo at the REPL. diff --git a/packages/darklang/languageTools/writtenTypesToProgramTypes.dark b/packages/darklang/languageTools/writtenTypesToProgramTypes.dark index 4361aa39a1..982d832e4a 100644 --- a/packages/darklang/languageTools/writtenTypesToProgramTypes.dark +++ b/packages/darklang/languageTools/writtenTypesToProgramTypes.dark @@ -41,6 +41,19 @@ let locationPlaceholderHash ProgramTypes.Hash.Hash $"{owner}.{modulesPart}.{name}" +// Option and Result are language prelude types. Their cases resolve bare in +// expressions just as they already do in match patterns. +let preludeEnumTypeName + (caseName: String) + : Stdlib.Option.Option> = + match caseName with + | "Some" + | "None" -> Stdlib.Option.Option.Some [ "Stdlib"; "Option"; "Option" ] + | "Ok" + | "Error" -> Stdlib.Option.Option.Some [ "Stdlib"; "Result"; "Result" ] + | _ -> Stdlib.Option.Option.None + + module Identifiers = module Type = let toPT (typ: WrittenTypes.TypeIdentifier) : String = typ.name @@ -81,6 +94,51 @@ module Identifiers = | _ -> (typeName, typeArgs, typeArgsUnresolvedNames |> Stdlib.List.flatten) + + let toPTEnum + (ctx: NameResolver.NRContext) + (i: WrittenTypes.QualifiedTypeIdentifier) + (caseName: String) + : (ProgramTypes.NameResolution * + List * + List) + = + let typeNameElided = + (Stdlib.List.isEmpty i.modules) && (i.typ.name == "") + + if Stdlib.Bool.not typeNameElided then + toPT ctx i + else + let (typeArgs, typeArgsUnresolvedNames) = + i.typeArgs + |> Stdlib.List.map (fun t -> TypeReference.toPT ctx t) + |> Stdlib.List.unzip + + let typeArgsUnresolvedNames = + typeArgsUnresolvedNames |> Stdlib.List.flatten + + let typeName = + match preludeEnumTypeName caseName with + | Some name -> + NameResolver.TypeName.resolve + ctx + (WrittenTypes.Name.Unresolved(i.range, name)) + | None -> + ProgramTypes.NameResolution + { originalName = [ caseName ] + resolved = + Stdlib.Result.Result.Error + ProgramTypes.NameResolutionError.InvalidName } + + match typeName.resolved with + | Error e -> + (typeName, + typeArgs, + Stdlib.List.append + typeArgsUnresolvedNames + [ (i.range, typeName.originalName, e) ]) + | Ok _ -> (typeName, typeArgs, typeArgsUnresolvedNames) + module Value = let toPT (v: WrittenTypes.ValueIdentifier) : String = v.name @@ -469,7 +527,7 @@ module Expr = | Some _ -> true | None -> false - if isArgument || (Stdlib.List.``member`` context.localBindings name) then + if isArgument || (Stdlib.List.contains context.localBindings name) then (ProgramTypes.PipeExpr.EPipeVariable(gid (), name, []), []) else let resolvedFn = @@ -512,7 +570,7 @@ module Expr = | Some _ -> true | None -> false - if isArgument || (Stdlib.List.``member`` context.localBindings fn.fn.name) then + if isArgument || (Stdlib.List.contains context.localBindings fn.fn.name) then (ProgramTypes.PipeExpr.EPipeVariable(gid (), fn.fn.name, args), unresolvedNames) else @@ -684,11 +742,15 @@ module Expr = // variable (values/fns are lowercase, so never those). Lower it to an // EVariable and let name resolution disambiguate — this mirrors the F# WT2PT // so the two lowerings agree. - if typeNameElided && (Stdlib.List.isEmpty fields) then + if + typeNameElided + && (Stdlib.List.isEmpty fields) + && (preludeEnumTypeName caseName == Stdlib.Option.Option.None) + then toPT ctx context (WrittenTypes.Expr.EVariable(range, caseName)) else let (typeName, typeArgs, typeNameUnresolvedNames) = - Identifiers.QualifiedType.toPT ctx typeName + Identifiers.QualifiedType.toPTEnum ctx typeName caseName let (fields, fieldsUnresolvedNames) = fields @@ -696,12 +758,9 @@ module Expr = toPT ctx context expr) |> Stdlib.List.unzip - // A bare constructor WITH fields (`Ok 5`) has an elided type name that can't - // be constructed without a resolved type — the runtime rejects it. Constructors - // must be qualified (`Stdlib.Result.Result.Ok 5`); the corpus does this - // universally. So the elided-type-name resolution stays tracked, and CLI create - // reports it as an invalid name (fail fast) rather than saving a fn that fails - // at runtime. `typeNameElided` is bound above; kept for the nullary-case check. + // Prelude cases resolve above. A bare user-defined case still has no + // inferable type, so retain its invalid-name result and report the case + // name rather than the parser's empty type-name placeholder. let unresolvedNames = Stdlib.List.flatten [ typeNameUnresolvedNames @@ -758,7 +817,7 @@ module Expr = match Stdlib.Dict.get context.argMap var with | Some index -> (ProgramTypes.Expr.EArg(gid (), index), []) | None -> - if Stdlib.List.``member`` context.localBindings var then + if Stdlib.List.contains context.localBindings var then (ProgramTypes.Expr.EVariable (gid ()) var, []) else let asValue = @@ -824,7 +883,7 @@ module Expr = match context.currentFnName with | Some qn -> match Stdlib.List.last qn with - | Some lastSeg when Stdlib.List.``member`` bindings lastSeg -> + | Some lastSeg when Stdlib.List.contains bindings lastSeg -> Stdlib.Option.Option.None | _ -> context.currentFnName | None -> Stdlib.Option.Option.None @@ -939,7 +998,7 @@ module Expr = // binding (let, lambda param, match pattern), treat as variable. // Otherwise keep the failed NR so DeferredResolver can refresh // once the target fn is created. - if Stdlib.List.``member`` context.localBindings fnName.fn.name then + if Stdlib.List.contains context.localBindings fnName.fn.name then (ProgramTypes.Expr.EVariable(gid (), fnName.fn.name), []) else (ProgramTypes.Expr.EFnName(gid (), resolvedFnName), unresolvedNames) @@ -1001,7 +1060,7 @@ module Expr = | Some index -> Stdlib.Option.Option.Some (ProgramTypes.Expr.EArg(gid (), index)) | None -> - if Stdlib.List.``member`` context.localBindings name then + if Stdlib.List.contains context.localBindings name then Stdlib.Option.Option.Some (ProgramTypes.Expr.EVariable(gid (), name)) else Stdlib.Option.Option.None @@ -1261,7 +1320,7 @@ module FunctionDeclaration = : List = match tr with | TVariable name -> - if Stdlib.List.``member`` acc name then acc else Stdlib.List.append acc [ name ] + if Stdlib.List.contains acc name then acc else Stdlib.List.append acc [ name ] | TList inner -> collectTVarsInTypeRef acc inner | TStream inner -> collectTVarsInTypeRef acc inner | TDict inner -> collectTVarsInTypeRef acc inner @@ -1322,7 +1381,7 @@ module FunctionDeclaration = let withReturn = collectTVarsInTypeRef fromParams rtyp withReturn |> Stdlib.List.filter (fun n -> - Stdlib.Bool.not (Stdlib.List.``member`` explicitTypeParams n)) + Stdlib.Bool.not (Stdlib.List.contains explicitTypeParams n)) let typeParams = Stdlib.List.append explicitTypeParams implicitTypeParams let (body, bdUnresolvedNames) = diff --git a/packages/darklang/scm/partialCommit.dark b/packages/darklang/scm/partialCommit.dark index 2d72783927..a501702f53 100644 --- a/packages/darklang/scm/partialCommit.dark +++ b/packages/darklang/scm/partialCommit.dark @@ -184,7 +184,7 @@ let isSelected (loc: LanguageTools.ProgramTypes.PackageLocation) (kind: LanguageTools.ProgramTypes.ItemKind) : Bool = - Stdlib.List.member selection (loc, kind) + Stdlib.List.contains selection (loc, kind) /// Pick the propagation IDs of batches whose source is in the selection. @@ -219,7 +219,7 @@ let inPropBatch (propId: Stdlib.Option.Option) : Bool = match propId with - | Some p -> Stdlib.List.member includedPropIds p + | Some p -> Stdlib.List.contains includedPropIds p | None -> false @@ -258,7 +258,7 @@ let referencedHashContains : Bool = let h = LanguageTools.ProgramTypes.Reference.hash target let k = LanguageTools.ProgramTypes.Reference.kind target - Stdlib.List.member referencedHashes (h, k) + Stdlib.List.contains referencedHashes (h, k) /// Decide whether one WIP op should be in the partial commit. @@ -277,15 +277,15 @@ let shouldInclude else match entry.op with | AddType t -> - Stdlib.List.member + Stdlib.List.contains referencedHashes (t.hash, LanguageTools.ProgramTypes.ItemKind.Type) | AddValue v -> - Stdlib.List.member + Stdlib.List.contains referencedHashes (v.hash, LanguageTools.ProgramTypes.ItemKind.Value) | AddFn f -> - Stdlib.List.member + Stdlib.List.contains referencedHashes (f.hash, LanguageTools.ProgramTypes.ItemKind.Fn) | SetName(loc, target) -> @@ -455,7 +455,7 @@ let oneLevelDeps | Some loc -> if (selectionContains selection loc dKind) - || (Stdlib.List.member seenKeys (loc, dKind)) + || (Stdlib.List.contains seenKeys (loc, dKind)) then Stdlib.Option.Option.None else diff --git a/packages/darklang/stdlib/cli/file.dark b/packages/darklang/stdlib/cli/file.dark index 99f6e46e27..850ff82df1 100644 --- a/packages/darklang/stdlib/cli/file.dark +++ b/packages/darklang/stdlib/cli/file.dark @@ -78,6 +78,7 @@ let readBytes (path: String) : Result.Result = /// Read the entire contents of a file as a string. +/// Search terms: file read, read file, text file. let readText (path: String) : Result.Result = match Posix.openFile path (Posix.OpenFlags.rdonly ()) 0 with | Ok fd -> @@ -219,6 +220,7 @@ let readLastLines /// Write text to a file, creating it if it doesn't exist or overwriting if it does. +/// Search terms: file write, write file, text file. let writeText (path: String) (content: String) : Result.Result = let flags = Stdlib.Int.bitwiseOr diff --git a/packages/darklang/stdlib/cli/posix.dark b/packages/darklang/stdlib/cli/posix.dark index da48f11f02..fb976f05f3 100644 --- a/packages/darklang/stdlib/cli/posix.dark +++ b/packages/darklang/stdlib/cli/posix.dark @@ -197,6 +197,11 @@ let sleep (delayMs: Float) : Unit = Builtin.timeSleep delayMs +/// Sleep for the given number of seconds. +let sleepSeconds (delaySeconds: Float) : Unit = + sleep (delaySeconds * 1000.0) + + /// Check if a path is a directory via stat(). /// Returns false if the path doesn't exist or is not a directory. let isDirectory (path: String) : Bool = diff --git a/packages/darklang/stdlib/html.dark b/packages/darklang/stdlib/html.dark index 816c7b594e..f997b32a07 100644 --- a/packages/darklang/stdlib/html.dark +++ b/packages/darklang/stdlib/html.dark @@ -156,7 +156,7 @@ let nodeToString (node: Node) : String = | None -> key) |> String.join " " - if (Stdlib.List.``member`` voidElements tag.name) then + if (Stdlib.List.contains voidElements tag.name) then match attributesText with | "" -> $"<{tag.name}>" | text -> $"<{tag.name} {text}>" diff --git a/packages/darklang/stdlib/json.dark b/packages/darklang/stdlib/json.dark index f67eee7c36..db4be237bc 100644 --- a/packages/darklang/stdlib/json.dark +++ b/packages/darklang/stdlib/json.dark @@ -4,7 +4,8 @@ module Darklang.Stdlib.Json let serialize<'a> (value: 'a) : String = Builtin.jsonSerialize<'a> value -/// Parses a JSON string as a Dark value, matching the type +/// Parses a JSON string as a Dark value, matching the type . +/// Search terms: deserialize JSON, decode JSON. let parse<'a> (json: String) : Stdlib.Result.Result<'a, ParseError.ParseError> = @@ -66,7 +67,7 @@ module ParseError = ++ PrettyPrinter.RuntimeTypes.typeReference branchId typ ++ "` at path: `" ++ JsonPath.toString path - ++ "`" + ++ "`. If the JSON is an object with mixed-type values, parse it as `AltJson.Json` and read fields with `AltJson.Helpers` (e.g. getInt, getString, getArray)." // Can't parse JSON due to an extra argument `2.0` at path: `root.Enh[2]` | EnumExtraField(rawJson, path) -> diff --git a/packages/darklang/stdlib/list.dark b/packages/darklang/stdlib/list.dark index 4650c89cea..78100a810a 100644 --- a/packages/darklang/stdlib/list.dark +++ b/packages/darklang/stdlib/list.dark @@ -79,7 +79,7 @@ let findFirstIndex (list: List<'a>) (fn: 'a -> Bool) : Option.Option = /// Returns {{true}} if is in the list -let ``member`` (list: List<'a>) (value: 'a) : Bool = +let contains (list: List<'a>) (value: 'a) : Bool = Option.isSome (findFirst list (fun elem -> elem == value)) @@ -166,7 +166,7 @@ let uniqueBy (list: List<'a>) (fn: 'a -> 'b) : List<'a> = |> fold ([], []) (fun (unique, seen) value -> let uniqueValue = fn value - if member seen uniqueValue then + if contains seen uniqueValue then (unique, seen) else (push unique value, push seen uniqueValue)) @@ -603,4 +603,4 @@ let chunkBySize let splitLast (l: List<'a>) : Option.Option<(List<'a> * 'a)> = match reverse l with | [] -> Option.Option.None - | head :: tail -> Option.Option.Some((reverse tail, head)) \ No newline at end of file + | head :: tail -> Option.Option.Some((reverse tail, head)) diff --git a/packages/darklang/stdlib/print.dark b/packages/darklang/stdlib/print.dark index ea6f5ccd62..562461b846 100644 --- a/packages/darklang/stdlib/print.dark +++ b/packages/darklang/stdlib/print.dark @@ -5,10 +5,12 @@ let print (str: String): Unit = Builtin.print str -let printLine (str: String): Unit = +/// Prints one line to standard output. +/// Search terms: printLine, print line, printLn. +let println (str: String): Unit = Builtin.printLine str let printLines (lines: List): Unit = lines - |> List.iter(fun l -> printLine l) + |> List.iter(fun l -> println l) diff --git a/packages/darklang/stdlib/string.dark b/packages/darklang/stdlib/string.dark index 0cbc4a408f..9a63ed8a68 100644 --- a/packages/darklang/stdlib/string.dark +++ b/packages/darklang/stdlib/string.dark @@ -27,7 +27,7 @@ let articleFor (nextWord: String) : String = && (Bool.not (Char.isASCIILetter c)) then articleFor (String.dropFirst nextWord 1) - else if List.``member`` vowels c then + else if List.contains vowels c then "an" else "a" @@ -239,6 +239,7 @@ let contains (lookingIn: String) (searchingFor: String) : Bool = /// Returns the substring of between the and indices. /// Negative indices start counting from the end of . +/// Search terms: take string, substring. let slice (string: String) (from: Int) (``to``: Int) : String = let len = String.length string @@ -519,4 +520,4 @@ let head (str: String) : Option.Option = /// Returns {{Some c}} for the character at (0-based, EGC, not /// byte), or {{None}} if is negative or past the end. let charAt (str: String) (index: Int) : Option.Option = - str |> String.toList |> List.getAt index \ No newline at end of file + str |> String.toList |> List.getAt index diff --git a/packages/darklang/sync.dark b/packages/darklang/sync.dark index cf3f9db955..879f1500d8 100644 --- a/packages/darklang/sync.dark +++ b/packages/darklang/sync.dark @@ -142,7 +142,7 @@ let connect (url: String) : Stdlib.Result.Result = /// actually a peer, so the caller can tell you if you weren't syncing with it. Purely local; the peer is /// untouched (this just stops *you* pulling from it). let disconnect (url: String) : Bool = - let wasConnected = Stdlib.List.member_v0 (peers ()) url + let wasConnected = Stdlib.List.contains_v0 (peers ()) url let _ = Stdlib.Sqlite.execP (syncDb ()) "DELETE FROM sync_peers_v0 WHERE url = @p0" [ url ] @@ -397,9 +397,9 @@ let syncLoop (intervalMs: Int) (ceilingMs: Int) (remaining: Int) : Int = let daemon (name: String) : Int = let _ = match Stdlib.Cli.Daemon.claimPidfile name with - | Error e -> Stdlib.printLine $"{name}: could not write pidfile: {e}" + | Error e -> Stdlib.println $"{name}: could not write pidfile: {e}" | Ok _ -> - Stdlib.printLine + Stdlib.println $"{name}: keeping your peers in sync in the background (pid {Stdlib.Int.toString (Stdlib.Cli.Sys.currentPid ())})" // The configured interval is the idle CEILING (set by `dark sync daemon config interval N`); the daemon diff --git a/packages/darklang/wip/ai/anthropic/integration-tests.dark b/packages/darklang/wip/ai/anthropic/integration-tests.dark index b6f6f63e9a..73e58d067e 100644 --- a/packages/darklang/wip/ai/anthropic/integration-tests.dark +++ b/packages/darklang/wip/ai/anthropic/integration-tests.dark @@ -1094,13 +1094,13 @@ let runTestGroup (groupName: String) (tests: TestList) : TestSummary = match result with | Pass -> - Stdlib.printLine $" [PASS] {name}" + Stdlib.println $" [PASS] {name}" { newSummary with passedTests = newSummary.passedTests + 1 } | Skipped reason -> - Stdlib.printLine $" [SKIP] {name}: {reason}" + Stdlib.println $" [SKIP] {name}: {reason}" { newSummary with skippedTests = newSummary.skippedTests + 1 } | Fail message -> - Stdlib.printLine $" [FAIL] {name}: {message}" + Stdlib.println $" [FAIL] {name}: {message}" { newSummary with failedTests = newSummary.failedTests + 1 failedTestNames = Stdlib.List.push newSummary.failedTestNames $"{name}: {message}" }) @@ -1137,15 +1137,15 @@ let runAllTests () : String = skippedTests = 0 failedTestNames = [] } - let _ = Stdlib.printLine "Anthropic API Integration Tests" - let _ = Stdlib.printLine "================================" + let _ = Stdlib.println "Anthropic API Integration Tests" + let _ = Stdlib.println "================================" let finalSummary = groups |> Stdlib.List.fold emptySummary (fun acc groupPair -> let groupName = Stdlib.Tuple2.first groupPair let tests = Stdlib.Tuple2.second groupPair - let _ = Stdlib.printLine $"\n[{groupName}]" + let _ = Stdlib.println $"\n[{groupName}]" let groupSummary = runTestGroup groupName tests combineSummaries acc groupSummary) diff --git a/scripts/perf/_bench.py b/scripts/perf/_bench.py index 4ec1301abf..d7937e6cc7 100644 --- a/scripts/perf/_bench.py +++ b/scripts/perf/_bench.py @@ -105,7 +105,7 @@ def fixture_path(name): let t0 = Builtin.timeNowMs () let result = repeat 200 50 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " stats=" ++ (Builtin.interpreterStatsGet ())) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " stats=" ++ (Builtin.interpreterStatsGet ())) """, # Arithmetic: builtin-call heavy, deep recursion. A deliberately different shape from `steady`, since # the two disagree substantially on per-Apply cost. @@ -126,7 +126,7 @@ def fixture_path(name): let t0 = Builtin.timeNowMs () let result = hot 4000 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " stats=" ++ (Builtin.interpreterStatsGet ())) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " stats=" ++ (Builtin.interpreterStatsGet ())) """, } @@ -150,7 +150,7 @@ def fixture_path(name): let t0 = Builtin.timeNowMs () let r = l3 16 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " leafCalls=" ++ (Stdlib.Int.toString r)) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " leafCalls=" ++ (Stdlib.Int.toString r)) """ WORKLOADS["depth-deep"] = """ @@ -165,7 +165,7 @@ def fixture_path(name): let t0 = Builtin.timeNowMs () let r = chain 4096 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " leafCalls=" ++ (Stdlib.Int.toString r)) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " leafCalls=" ++ (Stdlib.Int.toString r)) """ SCENARIOS["depth-shallow"] = ["run", "rundir/perf-workloads/depth-shallow.dark"] diff --git a/scripts/perf/workloads/http-server.dark b/scripts/perf/workloads/http-server.dark index e40beb36ef..46cf8dbb17 100644 --- a/scripts/perf/workloads/http-server.dark +++ b/scripts/perf/workloads/http-server.dark @@ -55,11 +55,11 @@ let main () : Int64 = // Off: a stdout line per request would be most of what we're measuring. logRequests = false }) router - (fun () -> Stdlib.printLine "LISTENING") + (fun () -> Stdlib.println "LISTENING") with | Ok _ -> 0L | Error e -> - Stdlib.printLine ("serve failed: " ++ e) + Stdlib.println ("serve failed: " ++ e) 1L main () diff --git a/scripts/perf/workloads/steady.dark b/scripts/perf/workloads/steady.dark index 4a7b7ca08b..3bc762d3fa 100644 --- a/scripts/perf/workloads/steady.dark +++ b/scripts/perf/workloads/steady.dark @@ -9,4 +9,4 @@ let _reset = Builtin.interpreterStatsReset () let t0 = Builtin.timeNowMs () let result = repeat 200 50 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " stats=" ++ (Builtin.interpreterStatsGet ())) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " stats=" ++ (Builtin.interpreterStatsGet ())) diff --git a/scripts/perf/workloads/suite/dicts.dark b/scripts/perf/workloads/suite/dicts.dark index 1a71f00cfa..74043d45d1 100644 --- a/scripts/perf/workloads/suite/dicts.dark +++ b/scripts/perf/workloads/suite/dicts.dark @@ -28,4 +28,4 @@ let _reset = Builtin.interpreterStatsReset () let t0 = Builtin.timeNowMs () let result = repeat (iters ()) 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) diff --git a/scripts/perf/workloads/suite/json.dark b/scripts/perf/workloads/suite/json.dark index 5fe3f0aec0..7ed1df5257 100644 --- a/scripts/perf/workloads/suite/json.dark +++ b/scripts/perf/workloads/suite/json.dark @@ -25,4 +25,4 @@ let _reset = Builtin.interpreterStatsReset () let t0 = Builtin.timeNowMs () let result = repeat (iters ()) 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) diff --git a/scripts/perf/workloads/suite/lists.dark b/scripts/perf/workloads/suite/lists.dark index 7ecc405bf5..7fbf36c21c 100644 --- a/scripts/perf/workloads/suite/lists.dark +++ b/scripts/perf/workloads/suite/lists.dark @@ -19,4 +19,4 @@ let _reset = Builtin.interpreterStatsReset () let t0 = Builtin.timeNowMs () let result = repeat (iters ()) 50 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) diff --git a/scripts/perf/workloads/suite/records.dark b/scripts/perf/workloads/suite/records.dark index 6d3b1789c6..2b12120d20 100644 --- a/scripts/perf/workloads/suite/records.dark +++ b/scripts/perf/workloads/suite/records.dark @@ -37,4 +37,4 @@ let _reset = Builtin.interpreterStatsReset () let t0 = Builtin.timeNowMs () let result = repeat (iters ()) 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) diff --git a/scripts/perf/workloads/suite/recursion.dark b/scripts/perf/workloads/suite/recursion.dark index c25a678cb5..56473ad9cd 100644 --- a/scripts/perf/workloads/suite/recursion.dark +++ b/scripts/perf/workloads/suite/recursion.dark @@ -22,4 +22,4 @@ let _reset = Builtin.interpreterStatsReset () let t0 = Builtin.timeNowMs () let result = repeat (iters ()) 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) diff --git a/scripts/perf/workloads/suite/strings.dark b/scripts/perf/workloads/suite/strings.dark index 91c0473dfc..3483ea4d63 100644 --- a/scripts/perf/workloads/suite/strings.dark +++ b/scripts/perf/workloads/suite/strings.dark @@ -23,4 +23,4 @@ let _reset = Builtin.interpreterStatsReset () let t0 = Builtin.timeNowMs () let result = repeat (iters ()) 0 let t1 = Builtin.timeNowMs () -Stdlib.printLine ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result)) +Stdlib.println ("elapsed_ms=" ++ (Stdlib.Int.toString (t1 - t0)) ++ " result=" ++ (Stdlib.Int.toString result))