# Mica: a safer language for new web applications Date: 2026-10-22 Status: implemented prototype. Native execution was selected by the user. The implementation, tests, examples, and development server have passed their checks; final artifact validation is tracked in the implementation plan. ## Intent and success criteria The user wants to create a language that replaces PHP for new web applications, prioritizing safer code and cleaner syntax. The approved approach is a small, statically checked language implemented in Go, with an interpreter, a command-line interface, and a minimal development HTTP server. Mica is a working project name; `.mica` is the source extension and `mica` is the command name. PHP source compatibility is outside this milestone. This is an initial runnable language, not a complete replacement for PHP's ecosystem. Success means that a developer can build one executable, check and run a Mica program, receive useful diagnostics for invalid programs, and serve an HTTP handler that processes query input and returns text or serialized JSON. Programs with static errors must produce no program output and must not open a listener. ## Chosen approach and alternatives Use a handwritten lexer and recursive-descent parser, an explicit type checker, and a tree-walking interpreter. Implement them in Go using its standard library. This keeps the first executable easy to build and the language semantics easy to inspect. Performance optimization follows evidence from working programs. Compiling to PHP would reuse PHP hosting but couple behavior to another runtime. A native compiler would require substantially more backend work. Both are deferred in favor of a complete, testable interpreter. ## First-version syntax ```text // greeting.mica fn greet(name: String) -> String { return "Hello, " + name; } let name = "Ada"; print(greet(name)); ``` Supported statements are immutable `let` bindings, expression statements, `return`, lexical blocks, and `if` with optional `else`. Functions are declared only at the top level. Semicolons end simple statements; block statements and function declarations do not need them. `else if` is supported. Expressions include decimal integers, double-quoted strings, `true`, `false`, `null`, variable references, calls to named functions, parentheses, unary `-` and `!`, arithmetic, comparisons, boolean operations, and null coalescing. Strings support `\n`, `\r`, `\t`, `\"`, and `\\`; invalid escapes and unterminated strings are syntax errors. Comments begin with `//` and continue to end of line. Identifiers use ASCII letters, digits, and underscores and cannot start with a digit. Unescaped line breaks inside strings are invalid. Spaces, tabs, carriage returns, and newlines separate tokens but do not insert semicolons. Source and strings must be valid UTF-8. Precedence, from weakest to strongest, is `??`, `||`, `&&`, equality (`==`, `!=`), ordering (`<`, `<=`, `>`, `>=`), additive (`+`, `-`), multiplicative (`*`, `/`, `%`), unary, and calls. `??` associates right; other binary operators associate left. `&&`, `||`, and `??` evaluate their right operand only when required. Arguments and other operands evaluate left to right. Assignment expressions do not exist. Arrays, maps, records, classes, loops, imports, closures, interpolation, user generics, mutable variables, and first-class functions are deferred. Recursion and conditionals are available for computation in this milestone. ## Types and safety rules Public types are `Int`, `Bool`, `String`, `Void`, `Request`, `Response`, and nullable forms such as `String?`. `Int` is a signed 64-bit integer. `Request` and `Response` are opaque runtime values; user code cannot construct them directly or access host-language objects. `Void?` is invalid. `null` has an internal null type. `Void` is legal only as a function return type, never as a binding or parameter type. A type annotation may have at most one nullable suffix; `Int??` is invalid. Function parameters and return types require annotations. `let` bindings infer their type unless annotated, for example `let name: String? = null;`. A bare `let name = null;` is rejected because it has no underlying type to infer. There are no implicit numeric/string conversions or truthiness rules. Conditions and boolean operators require `Bool`. `+` accepts two `Int` values or two `String` values; the other arithmetic and ordering operators require two `Int` values. Equality accepts matching primitive base types regardless of nullability, so `Int == Int?` is valid. It also accepts comparisons between null and any nullable value, in either operand order, and `null == null`. Nullable opaque values such as `Request?` may be compared only with null, never with another opaque value. Non-null opaque comparisons, non-null values compared with null, unrelated base types, and `Void` comparisons are type errors. Nullable equality compares underlying values, with null equal only to null. A non-null `T` may initialize or be passed to `T?`; a `T?` cannot be passed to `T` without explicit handling. `value ?? fallback` requires a nullable left operand and a non-null fallback of its underlying type and returns that type. In `if value != null`, the immutable variable is treated as non-null inside the then block; `if value == null` provides that refinement inside the else block. This refinement applies only to a direct variable/null comparison, not compound conditions, and does not persist after the conditional. No other flow-sensitive type inference is promised in this version. Bindings cannot be reassigned or redeclared within the same scope. An inner block may shadow an outer local binding. Built-in names and function names cannot be used for bindings or parameters. Duplicate function or parameter names are rejected. Functions can call functions declared later and may recurse. They access their own parameters, locals, functions, and built-ins; they do not capture top-level script bindings. Function values cannot be stored or returned. Every path through a non-`Void` function must return its declared type. A `Void` function may use `return;` or fall through; it cannot return a value. `return` outside a function is invalid. Expressions producing `Void` cannot be stored or used as arguments. All branches are checked, including unreachable branches. Return analysis is structural: a `return` terminates its block; a nested block terminates when a statement in it guarantees a return; an `if` guarantees a return only when both its then and else branches do. Statements after a guaranteed return are still checked. A function call alone is not a guaranteed return, even if it recurses or calls a function that never completes. Integer overflow, division by zero, negation overflow, and excessive recursion are controlled runtime errors. Integer division truncates toward zero. Integer literal values must fit `Int`, including the minimum value when written with a leading minus. At most 256 user-function calls may be active at once. Runtime checks supplement static typing where failures depend on actual input values. ## Built-ins | Call | Result and behavior | | --- | --- | | `print(value)` | `Void`; writes one primitive or nullable primitive plus a newline to standard output. Values print as decimal integers, lowercase booleans, string contents, or `null`. | | `parse_int(value: String)` | `Int?`; accepts an optional leading `+` or `-` and decimal digits only. Empty input, whitespace, invalid digits, and overflow return null. | | `to_string(value)` | `String`; explicitly converts `Int`, `Bool`, or `String`; it does not accept null or nullable types. | | `query(request: Request, name: String)` | `String?`; returns the first decoded query value for the key, or null when absent. An explicitly empty value is an empty string. | | `method(request: Request)` | `String`; returns the HTTP method. | | `path(request: Request)` | `String`; returns the parsed URL path without the query. | | `text(value: String)` | `Response`; HTTP 200, `text/plain; charset=utf-8`, exact string body. | | `json(value)` | `Response`; HTTP 200, `application/json; charset=utf-8`, standard JSON encoding of a primitive, null, or nullable primitive. | The checker implements the limited overloaded built-ins above directly; this does not introduce union types or generics into user syntax. `json("hello")` produces a JSON string, including its quotes; it does not parse raw JSON text. No raw HTML response API is included in this milestone. Text and JSON responses also set `X-Content-Type-Options: nosniff`. ## Command-line contract ```text mica check program.mica mica run program.mica mica serve app.mica --addr 127.0.0.1:8080 ``` `check` reads, parses, and checks the complete source without executing it. It is silent on success. `run` performs the same checks before executing top-level statements in source order; function declarations themselves do not execute. There is no implicit call to a function named `main`. `serve` performs static checks, requires exactly the handler signature `fn handle(request: Request) -> Response`, and rejects top-level executable statements. Its only top-level declarations are functions. It binds the requested address only after validation. `--addr` defaults to `127.0.0.1:8080`; allowing `:0` permits an operating-system-assigned port for integration tests. The startup message reports the actual listening address to standard error. The flag is accepted after the source path as shown above. Exit statuses are 0 for success, 1 for syntax/type/runtime/input-file/listener errors, and 2 for invalid command usage. Ctrl-C stops the development listener cleanly and exits 0. Diagnostics go to standard error; program `print` output goes to standard output. A runtime failure can follow earlier successful prints; only static failures guarantee no program side effects. ## HTTP execution contract ```text // app.mica fn handle(request: Request) -> Response { let name = query(request, "name") ?? "world"; return text("Hello, " + name); } ``` A request to `/?name=Ada` returns `Hello, Ada`; a missing name returns `Hello, world`. Dispatching by path or method is ordinary conditional logic in the handler. All parsed methods and paths reach the handler; there is no implicit router or filesystem serving. Request bodies, cookies, sessions, custom response headers, and custom status codes are deferred. Parse and type-check once at startup. Each request uses fresh interpreter state; only the immutable checked program is shared. Request inputs and call stacks must never leak between requests. Invalid query encoding, invalid UTF-8 in any decoded query key or value, or an invalid UTF-8 decoded path returns HTTP 400 before the handler runs. A handler runtime error produces HTTP 500 with a fixed `Internal Server Error` text body and a source-located server-side diagnostic. Responses are buffered until the handler finishes. HEAD requests use the same handler path but send no response body, following Go's HTTP behavior. The HTTP adapter uses Go's `net/http` with finite header and idle timeouts and graceful shutdown. This is a development server. Production deployment, application sandboxing, and security guarantees beyond the specified semantics are outside this milestone. ## Components and boundaries The initial Go module has no third-party runtime dependencies. - `cmd/mica`: command dispatch, flags, files, signals, and exit statuses. - `internal/syntax`: source spans, tokens, AST, lexer, and parser. It has no runtime or HTTP dependencies. - `internal/check`: type definitions, scopes, built-in signatures, function signatures, return analysis, and nullable refinements. It consumes syntax and produces a checked program or diagnostics; it does not execute code. - `internal/eval`: tagged runtime values, environments, evaluation, arithmetic, recursion limits, and built-in implementations. It accepts a checked program and an injected output writer and returns values or language errors. - `internal/web`: conversion between HTTP requests/responses and opaque language values, per-request execution, listener lifecycle, and server diagnostics. - `examples`: runnable CLI and handler programs, plus a deliberately invalid example illustrating a static diagnostic. Both the CLI and server use the same parser, checker, and evaluator. Keeping source spans throughout the pipeline allows syntax, type, and runtime errors to share the format `file.mica:line:column: category: message`, with one-based line and column numbers. Columns count Unicode code points rather than UTF-8 bytes. Tests assert the primary error's location and meaning, not incidental wording or Go stack traces. ## Verification and acceptance The implementation must pass `go test ./...`, `go vet ./...`, and `go test -race ./...`, and build with `go build ./cmd/mica`. Source is formatted with `gofmt`. Use a supported Go release, at least Go 1.22, and standard-library tests and HTTP test helpers. Toolchain availability is an implementation preflight: the current workspace's `go` command does not respond to `go version`, so a usable compiler must be located or installed before implementation checks. Acceptance tests cover: 1. The greeting script prints exactly `Hello, Ada` followed by a newline. 2. Function calls before declarations, recursion, lexical scope, and early returns behave consistently. 3. `greet(42)`, `1 + "2"`, non-boolean conditions, unknown names, invalid returns, reassignment, and nullable misuse fail before any program output occurs. 4. `parse_int` accepts valid signed decimal values and rejects invalid or overflowing input; `??` and the specified branch refinements allow safe use. 5. Arithmetic boundary values, division by zero, and call-depth exhaustion report controlled source-located runtime errors. 6. Lazy operators do not evaluate an unused failing operand. Branches are still checked statically. 7. Unterminated strings, malformed UTF-8, invalid syntax, and wrong argument counts produce useful diagnostics and nonzero exit statuses. 8. HTTP tests exercise query decoding, missing versus empty input, text and JSON encoding, content types, HEAD behavior, malformed queries, invalid UTF-8 decoded inputs, and HTTP 500 errors. 9. Parallel HTTP requests keep input and evaluator state separate; the race detector reports no shared-state races. 10. CLI integration tests verify check/run/serve behavior, exit statuses, command argument order, startup validation, and termination of a local server. The README will document installation/build requirements, runnable examples, type rules, CLI usage, and the milestone's deliberate limits. It must describe the product as a statically checked prototype, without claiming PHP compatibility or production readiness. ## Delivery The implementation plan records completed tasks and release verification. The README documents build requirements, runnable examples, commands, and the prototype's limits. Deliver the local executable and source archive without publishing or deploying a service.