How a Ruby Developer Spent 18 Months Building AlexScript, an Object-Oriented Language with Polish Keywords and an Async Runtime

What began as a weekend experiment to better understand the mechanics of programming language execution has culminated in the development of AlexScript, an interpreted, object-oriented scripting language complete with a standard library, asynchronous programming support, a debugger, and an internal web framework. Developed entirely in Ruby, the project serves not only as a functional programming environment but also as an exhaustive deep dive into runtime architecture, memory management, and compiler design.
The creator, a professional Ruby developer who chose to build the language over an 18-month period, structured the syntax entirely around the Polish language. While the linguistic focus provides a localized developer experience, the true value of the project emerged from the technical hurdles encountered while translating high-level concepts into efficient Ruby execution code.
Chronology and Evolution of the Project
The initiative started approximately two years prior to its completion as a basic exploratory exercise. Dissatisfied with a surface-level understanding of how simple variable assignments—such as converting a textual representation of code into machine-readable actions—are executed, the developer constructed a toy interpreter over a single weekend.
Initially capable of evaluating basic arithmetic operations, the toy project quickly expanded. Rather than concluding the experiment, the author continued appending features over a year and a half. This incremental development cycle transformed the basic tree-walking interpreter into a robust environment featuring a custom parser, lexer, standard library, and a native web framework written in AlexScript itself.
As the project scaled, architectural bottlenecks forced multiple refactorings. Issues ranging from string encoding inefficiencies to garbage collection overhead required deep investigation into MRI (Matz’s Ruby Interpreter) internals. The resulting software bridges the gap between educational toy interpreters and production-grade script execution models.
Linguistic Design and Dual-Form Syntax
A defining characteristic of AlexScript is its localization. Every core keyword is translated into Polish, the author’s native tongue. For instance, the keyword for "class" is implemented as klasa, "function" as funkcja, "let" as niech, and "return" as zwroc.
A notable design challenge arose regarding diacritics. The grammatically correct Polish spelling for return is zwróć, which includes specialized characters that require complex keystroke combinations, such as AltGr mappings on standard international keyboards. Recognizing that continuous use of accented characters would severely degrade developer ergonomics during routine coding tasks, the creator implemented a dual-form system within the lexer.
Developers can utilize ASCII-compliant aliases for rapid typing or opt for fully accented equivalents for formal orthography. The lexer seamlessly processes both forms for keywords such as jesli/jeśli (if), dopoki/dopóki (while), and falsz/fałsz (false). This flexibility highlights a pragmatic approach to language design, balancing linguistic authenticity with modern developer productivity.
Technical Insights: Overcoming Ruby Runtime Bottlenecks
Building a language interpreter on top of Ruby exposed several hidden performance characteristics of the underlying runtime environment. The developer documented seven major technical takeaways that challenge common assumptions about Ruby application development.
1. Flow Control Optimization via Throw and Catch
In a standard tree-walking interpreter, implementing function returns often relies on raising and catching exceptions. While straightforward to implement, this approach imposes a severe performance penalty. Every raised exception forces Ruby to construct an exception object and capture a deep backtrace, creating heavy overhead during recursive execution.
AlexScript resolved this by utilizing Ruby’s built-in throw and catch primitives for non-local exits. Unlike exceptions, throw and catch operate without allocating heavy backtrace machinery or unwinding the call stack unnecessarily. For targeted control-flow jumps where the destination is explicitly controlled, this optimization significantly accelerates execution speed.
2. Resolving UTF-8 String Indexing Inefficiencies
Early iterations of the AlexScript lexer scanned source code character by character using standard string indexing (source[position]). While performant for ASCII text, Ruby strings default to UTF-8 encoding. To locate the nth character in a UTF-8 string, the interpreter must traverse the entire sequence from the beginning to count codepoints, leading to accidental quadratic time complexity ($O(n^2)$) when processing files containing diacritics.
The performance issue was entirely eliminated by shifting to byte-level operations utilizing getbyte for $O(1)$ access and byteslice for token extraction, followed by an explicit encoding assertion.
3. Unified Method Tables for Native and User Code
To achieve first-class status for standard library components, AlexScript abandoned the naive approach of maintaining separate registries for native and user-defined methods. Instead, native methods are injected directly into the standard method table alongside user classes, distinguished only by an internal flag.
This architecture allows native classes to support inheritance, reflection, and super-calls identically to user-defined constructs, mirroring the internal design patterns observed in CRuby’s handling of cfunc and Ruby methods.
4. Asynchronous Concurrency Through Fibers
Rather than relying on external concurrency libraries, AlexScript implements an asynchronous runtime powered by Ruby Fibers. The language keyword czekaj (await) suspends the current fiber via Fiber.yield, handing execution control over to a custom event reactor comprising a ready queue, a sorted timer list, and an IO.select loop.
When an asynchronous promise resolves, the reactor schedules waiting fibers back onto the execution queue, effectively establishing a cooperative concurrency model without native OS-level thread overhead.
5. Memory Management and the Dangers of Weak References
Initial attempts to optimize closure environment cleanups utilized WeakRef to assist the garbage collector in purging unused scopes. However, this introduced sporadic, irreproducible runtime errors characterized by "Invalid Reference" exceptions.
The garbage collector aggressively reaped environments that remained logically reachable through complex closure chains, prompting a reversion to standard, strong references to guarantee memory stability.
6. Hosting Language Exceptions on Ruby’s Runtime
AlexScript exception handling—implemented via proba/zlap/wkoncu (try/rescue/ensure)—maps directly onto Ruby’s native exception infrastructure. By resolving user-defined exception classes to their nearest builtin counterparts, the interpreter inherits robust stack unwinding, backtrace generation, and cleanup semantics without requiring custom stack-management logic.
7. Leveraging Arbitrary-Precision Integers
Because Ruby natively supports arbitrary-precision integers, mathematical operations within AlexScript avoid overflow errors and precision degradation by default. This native capability allowed complex mathematical computations, such as generating Bernoulli numbers with multi-digit numerators, to execute accurately without explicit big-integer library imports.
Limitations and Future Outlook
Despite its robust feature set, AlexScript operates under inherent constraints. As a tree-walking interpreter implemented in a high-level language, its raw execution speed has a distinct performance ceiling. Furthermore, the language intentionally omits advanced metaprogramming features such as eval, method_missing, define_method, and macro systems.
Architectural challenges also surfaced during the development of the web framework. A persistent Ruby bug preventing the fiber scheduler from being cleanly interrupted by IO#close forced the server implementation to adopt a thread-per-connection model to handle client disconnections safely.
To encourage community engagement, the developer has deployed a browser-based REPL compiled to WebAssembly via ruby.wasm, allowing users to test the language interactively. Comprehensive documentation and source code repositories are maintained publicly for developers interested in compiler design and alternative runtime implementations.







