Repository navigation
Devlog #185
Replies: 11 comments 2 replies
|
I am currently working on removing &mut. this is a major change to the language. probably the biggest refactor I've ever done. the spec for &mut removal: https://github.com/withlang-dev/with/blob/main/docs/completed/mut.md |
|
Devlog: Three Block Body Forms I kept seeing this pattern and it bothered me: A single expression taking three lines. It didn't feel like With. We already had two block forms — indented colon and braces — but neither solved the trivial-body problem cleanly. So I added a third: inline colon. Now this is valid: I wrote up the full spec for the three interchangeable forms (inline colon, indented colon, braced) and made the rule universal across every construct that takes a body. this is now completed https://github.com/withlang-dev/with/blob/main/docs/completed/three-block-types.md |
|
Devlog: Mutability and Calling Convention Increasing complexity in the name of simplicity. What changedSix independent axes now:
The big shift: share-place by defaultFor non-Copy types, For Copy types, default is still copy (as expected). When you actually want isolation or ownership transfer, you say so: Why this feels right
The old model forced too many decisions into signatures. This one pushes the decisions to the call site where they belong, while still giving the compiler everything it needs for safety. https://github.com/withlang-dev/with/blob/main/docs/completed/mutability.md This is now implemented. |
|
Devlog: Implicit Main I added implicit main. This means simple With programs no longer need ceremony: The compiler now treats top-level executable statements in the build/run entry file as the body of a synthesized This is only for entry files. Imported modules, The annoying part was top-level At first I tried the obvious thing: in implicit-main mode, parse top-level The parser sees the file incrementally, so it can encounter So the final rule is: the parser does a cheap top-level pre-scan for If there is an explicit main anywhere in the entry file, top-level The implementation is entirely in the frontend/parser path. The parser builds a normal I added coverage for:
The regression that mattered was explicit-main files with top-level globals. That is fixed by the pre-scan. This feature enables https://github.com/withlang-dev/with/blob/main/docs/completed/implicit-main.md This is now implemented. |
|
Devlog: Do-While Another concession to and it does no true harm besides unnecessarily expanding the lexicon of With. it's a pragmatic tradeoff and I am ok with it. |
|
PCRE2 is in. Regular expressions integrated. Implicit main, one-liners implemented. |
|
Right now I'm working on updating the build system. Inspired by Jai's. |
Devlog: Three MonthsIt's mid-May. With is three months old. I keep having to remind myself of that, because the project feels older than it is. Not in a bad way. In the way that a thing feels older when you've lived inside it. I've been thinking in With syntax for weeks now. When I read C code I mentally translate it. When I see a Python script I think about what it would look like without the runtime. That's either a good sign or a warning sign, and I'm choosing not to examine which. The 48-hour sprint in late February was the most productive weekend of my life, and I don't think I fully understood at the time what I was doing. I wasn't building a compiler. I was building a commitment. Once you have 400 tests passing and a language that can express structs, enums, traits, generics, closures, pattern matching, and async, even if most of those are skeletal, you've crossed a line. You can't look at that and say "this was just an experiment." You have to either abandon it or finish it. I chose to finish it. Self-hosting was the first real milestone. Not because it was technically the hardest thing (it wasn't. The MIR pipeline was harder), but because it changed the nature of the project. Before self-hosting, With was a program I was writing. After self-hosting, With was the tool I was using to write itself. Every bug in the compiler was now a bug in my development environment. Every missing feature was something I personally needed, right now, to keep working. That pressure is clarifying. You stop designing features in the abstract and start building what you actually need next. The whole March sprint. MIR, generics, C interop, f-strings, the compiler refactors. All of that was driven by "I need this to keep going." I didn't sit down and plan a MIR pipeline because it was architecturally elegant. I built it because the AST codegen was falling over on real code and I needed something that worked. The day I deleted the AST codegen path entirely, March 16 , felt like burning a bridge. In a good way. There was no fallback. MIR was the compiler now. If it didn't work, nothing worked. The C interop story surprised me. I expected it to be a necessary chore, something you grind through so you can call
The BearSSL port for the package manager was a fun detour. People will probably read the timeline and think "they wrote a TLS stack in a day," which sounds insane. What actually happened is that BearSSL is a beautifully written, compact C library, and the migration tool handled most of the translation. The hard part was getting the networking right, not the crypto. Thomas Pornin did the hard part years ago. I just moved it to a different language. April was the PCRE2 month. 73,000 lines of C. Not clean C, either. PCRE2 is a serious regex engine with computed gotos, macro-heavy codegen, and bit-manipulation code that makes you question whether the author was writing a program or composing a spell. Migrating it was the hardest test the migration tool has faced, and the migration tool failed that test repeatedly before it started passing. The bugs were educational, though. Every PCRE2 test failure pointed at a real compiler or migrator defect. Sign extension in integer codegen. Comma expressions losing side effects. C integer promotion rules not being applied to small types. sizeof on pointer types resolving to the pointee instead of the pointer. Each one was a four-line fix once you found it, and each one took hours to find. The moment the upstream PCRE2 test suite started passing was quietly one of the best moments of the project. No fanfare. Just a test run that ended with "All tests passed" instead of a crash. I stared at it for a while. The mutability model redesign in late April was the biggest philosophical decision I've made in With. The earlier model used So I threw it out. People will tell me this is too restrictive. They're not wrong. There are patterns you can express in Rust that you can't express in safe With. But there are also entire categories of bugs and complexity that simply don't exist in With's model. No lifetime annotations. No borrow checker error messages that take a PhD to parse. No I think that's worth it. Ask me again in a year. May has been about refinement. Implicit main, open source, regex literals, CLI one-liners, the build system, derive macros. Each of these is small compared to the March and April sprints, but they're the features that make the language feel finished. Or at least, feel like it's heading somewhere that people would want to use it. The derive macros are a good example. Same with CLI one-liners. Looking back at three months, what surprises me most is how much of the design held up. The spec I wrote in February, the original one, before any code existed, is still recognizably the language I'm building. The syntax barely changed. The ownership model evolved (the That's either because I got lucky with the initial design, or because I've been unconsciously steering the implementation to match the spec even when it would have been easier to diverge. Probably both. What I didn't anticipate is how much of the work would be C interop. I thought C interop would be a feature. It turned out to be half the project. The migration tool alone is thousands of lines of compiler code. The PCRE2 migration consumed most of April. The BearSSL port, the But it's also the reason the language is useful today, not in some hypothetical future. You can write a real program in With right now, pulling in real C libraries, because the interop actually works. That's worth the investment. What's next? The obvious answer is "a lot." The REPL and notebook are release gates. The query language for JSON/YAML processing needs a real design. The ML framework Crux is still just a name and a motivation. The regex engine works but the language integration needs more polish. The build system is functional but young. But the foundation is solid. The compiler bootstraps. The type system is coherent. The ownership model is simple and enforced. The C interop is real. The fixpoint holds. https://github.com/withlang-dev/with/blob/main/docs/blog_posts/timeline.md |
|
Devlog: With native build and emitting C Prerequite to cross-platform, is emitting C. this is a big feature, I wanna discuss what it is and why I'm doing it. The opposite of Why would we do such a thing?
How do we prove that it works?
In addition: We are adopting Jai-style build. (the build logic is written in With). this eliminates the need for Makefile and build scripts. All of this paves the path to true cross-platform. This work is currently spec'd and in-progress. |
|
Of desire for purity, and to enable cross platform build with minimal dependencies, I am adding a native build system to With inspired by Jai, and deprecating my Makefile and the dozens of shell scripts that previoualy functioned as my build system. This effort required me to expand the concept of comptime beyond Zig's (which I previously ported to With) Capability-Bearing ComptimeWhat it isCapability-bearing comptime is a generalization of pure compile-time evaluation. A comptime function declares the capabilities it requires through a with clause: Each capability binds to a name in scope (fs for ToolFs, proc for ProcessRunner, ctx for BuildCtx and ActionCtx, with as name to rebind). Method calls on a capability dispatch to compiler-native handlers that perform the corresponding effect: filesystem access, process execution, source emission, diagnostics. A function with no with clause is pure comptime in the Zig sense. It computes values and types, specializes generics, folds constants. It cannot do I/O. It cannot forge a capability. It can only call other functions whose capability requirements are a subset of its own. The capability set determines what the function is allowed to do. The mechanism is the same evaluator either way. MotivationZig's comptime is powerful but strictly pure. Anything requiring effects, build orchestration in particular, lives outside the language as a separate program. Jai's metaprogramming runs compile-time code with full ambient authority. The first is too restrictive for build systems; the second gives up the safety property that makes compile-time evaluation tractable. Capability-bearing comptime resolves this without splitting the language. Build scripts, action functions, code generators, and compile-time queries are all ordinary comptime functions. They differ only in what capabilities they declare. There is no separate build language, no generated runner binary, no graph text as transport between phases. The same property gives capability-bearing comptime its safety: a function cannot acquire a capability it did not declare. The driver mints capabilities at well-defined entry points (build, action invocation, code-gen hooks) and passes them in. Everywhere else, the capability set is whatever the caller passes through, never more. ImplementationThe compiler maintains a registry of capability types (BuildCtx, ProjectInfo, Diagnostics, SourceEmitter, ToolFs, ProcessRunner, ActionCtx). Semantic analysis uses this registry to enforce three rules: capability values cannot be constructed by user code, cannot escape to runtime contexts, and cannot be called without a matching with clause at the call site. A comptime evaluator interprets MIR with capability-aware dispatch. When a method is called on a capability value, the evaluator validates the handle (correct kind, live generation, owned by the current evaluator) and routes the call to a compiler-native handler. The handler performs the effect (read a file, run a process, emit a diagnostic) and returns a value the evaluator can use. The driver mints capabilities at entry points and invokes user-declared comptime functions through the evaluator. build(ctx) returns a typed Build value; a materializer converts that value directly into the driver's internal build graph. Action targets store the action function as a typed reference; when the target runs, the driver mints an ActionCtx scoped to the target's declared outputs and evaluates the action through the same evaluator. Compile-time evaluation is unified. The same machinery handles const X = comptime fib(10), comptime with BuildCtx: pub fn build, and every capability-bearing function in between. Only the capability set changes. Why expand comptime instead of building a tool modeAn earlier draft of this design proposed a separate execution context called "tool mode." Build scripts and action functions would run in this mode, distinct from both runtime and ordinary comptime. The compiler would have three execution tiers: pure comptime for constants and types, tool mode for build orchestration with driver-minted authority, and runtime for the compiled program. The reframing to capability-bearing comptime came from noticing that tool mode would have been comptime with a specific, fixed set of capabilities. The "tool" in tool mode was just shorthand for "comptime with BuildCtx, or one of the things BuildCtx hands out." Once that was visible, the separate mode stopped earning its complexity. |
|
With 0.14.0 Released. massive progress. Added native build, one liners, and install script, regex, Release Install (on mac - windows and linux are coming soon) Demo |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
with migrateis complete. I used it to port pcre2 to with, which is 100% working, all tests pass.I had to accept
gotointo With. For implementation, I followed golang's example. https://github.com/withlang-dev/with/blob/main/docs/completed/goto.mdthe reason is that pcre2's gotos are irreducable. Therefore, I was not able to convert them to With using the Beyond Relooper algorithm. https://github.com/withlang-dev/with/blob/main/lib/std/cfg/stackify.w
All reactions