restructure: move el compiler content into lang/
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// activate.el — The `activate` construct: spreading activation over the type graph.
|
||||
//
|
||||
// The `activate` keyword is the most novel feature of the Engram language.
|
||||
// Instead of querying a database with SQL or a key lookup, you query the
|
||||
// *type graph* using a natural language description. The result is
|
||||
// statically typed — the compiler knows you get [User] back, not Any.
|
||||
//
|
||||
// How it works:
|
||||
//
|
||||
// 1. At compile time, the type checker verifies that `User` is a registered
|
||||
// type with a corresponding Engram node mapping.
|
||||
//
|
||||
// 2. At runtime, the Engram runtime performs spreading activation over
|
||||
// the knowledge graph, starting from the User node, propagating to
|
||||
// semantically related nodes with co-activation energy proportional
|
||||
// to edge weights and node salience.
|
||||
//
|
||||
// 3. The query string "customer who purchased recently" is embedded
|
||||
// and used as a semantic filter: only nodes whose embeddings are
|
||||
// within cosine-similarity threshold of the query embedding are returned.
|
||||
//
|
||||
// 4. The result is type-safe: [User]. You don't get back Any or a JSON blob.
|
||||
// The type checker enforces this at compile time.
|
||||
|
||||
type User {
|
||||
id: Uuid
|
||||
name: String
|
||||
email: String
|
||||
}
|
||||
|
||||
type Order {
|
||||
id: Uuid
|
||||
user_id: Uuid
|
||||
amount: Float
|
||||
status: String
|
||||
}
|
||||
|
||||
// Query: find all users who are semantically related to "customer who purchased recently"
|
||||
// The result type is [User] — an array of User nodes from the knowledge graph.
|
||||
let recent_buyers: [User] = activate User where "customer who purchased recently"
|
||||
|
||||
// You can also activate on other types:
|
||||
let pending_orders: [Order] = activate Order where "order awaiting fulfillment"
|
||||
|
||||
// Spreading activation respects the type graph edges.
|
||||
// If User and Customer share an Engram node type (e.g. "Entity"),
|
||||
// they are semantically compatible — the type checker allows
|
||||
// treating one as the other when they map to the same Engram node class.
|
||||
|
||||
fn process_buyers(users: [User]) -> String {
|
||||
return "processing users"
|
||||
}
|
||||
|
||||
let result: String = process_buyers(recent_buyers)
|
||||
@@ -0,0 +1,200 @@
|
||||
// browser-auth.el -- El-compiled auth flow using Supabase
|
||||
//
|
||||
// Compile: elc --target=js --bundle examples/browser-auth.el > auth.js
|
||||
// (requires el_runtime.js in the same directory as browser-auth.el)
|
||||
//
|
||||
// Demonstrates:
|
||||
// - extern fn for declaring Supabase client constructor
|
||||
// - anonymous function literals for callbacks
|
||||
// - method call syntax on Any-typed values (client.auth.signInWithOtp)
|
||||
// - try/catch for error handling
|
||||
// - @async functions with DOM interaction
|
||||
// - DOM bridge: dom_get_element, dom_get_value, dom_set_text, dom_add_class
|
||||
// dom_remove_class, dom_show, dom_hide, dom_is_null
|
||||
// - window_set to expose El functions to the browser global scope
|
||||
// - local_storage_set/get for session hints
|
||||
// - set_timeout for transient UI state
|
||||
// - state_set/get for component state
|
||||
//
|
||||
// Expected HTML elements:
|
||||
// #acct-email-input -- email text input
|
||||
// #send-link-btn -- submit button
|
||||
// #auth-message -- status message container
|
||||
// #auth-form -- the form to hide after success
|
||||
//
|
||||
// The Supabase JS SDK is loaded from CDN via a <script> tag before auth.js.
|
||||
// supabase_create_client is declared extern: the runtime provides it via
|
||||
// the global supabase.createClient function exposed by the CDN bundle.
|
||||
|
||||
// ── External declarations ─────────────────────────────────────────────────
|
||||
//
|
||||
// These functions are provided by the JS environment (CDN script tags).
|
||||
// No body is emitted -- the compiler just records the names.
|
||||
|
||||
extern fn supabase_create_client(url: String, key: String) -> Any
|
||||
|
||||
// ── UI helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn show_message(text: String, is_error: Bool) -> Void {
|
||||
let msg_el = dom_get_element("auth-message")
|
||||
if !dom_is_null(msg_el) {
|
||||
dom_set_text(msg_el, text)
|
||||
dom_remove_class(msg_el, "hidden")
|
||||
if is_error {
|
||||
dom_add_class(msg_el, "error")
|
||||
dom_remove_class(msg_el, "success")
|
||||
} else {
|
||||
dom_add_class(msg_el, "success")
|
||||
dom_remove_class(msg_el, "error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_button_loading(loading: Bool) -> Void {
|
||||
let btn = dom_get_element("send-link-btn")
|
||||
if !dom_is_null(btn) {
|
||||
if loading {
|
||||
dom_set_text(btn, "Sending...")
|
||||
dom_set_attr(btn, "disabled", "true")
|
||||
} else {
|
||||
dom_set_text(btn, "Send Magic Link")
|
||||
dom_remove_attr(btn, "disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_message() -> Void {
|
||||
let msg_el = dom_get_element("auth-message")
|
||||
if !dom_is_null(msg_el) {
|
||||
dom_add_class(msg_el, "hidden")
|
||||
dom_set_text(msg_el, "")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Email validation ───────────────────────────────────────────────────────
|
||||
|
||||
fn is_valid_email(email: String) -> Bool {
|
||||
let trimmed: String = str_trim(email)
|
||||
if str_len(trimmed) < 5 { return false }
|
||||
let at_pos: Int = str_index_of(trimmed, "@")
|
||||
if at_pos < 1 { return false }
|
||||
let dot_pos: Int = str_index_of(trimmed, ".")
|
||||
if dot_pos < at_pos + 2 { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
// ── Supabase client construction ──────────────────────────────────────────
|
||||
//
|
||||
// Build a Supabase client from config injected into the page as NEURON_CFG.
|
||||
// The extern fn supabase_create_client maps to supabase.createClient on
|
||||
// the global object exposed by the CDN bundle.
|
||||
|
||||
fn get_supabase_client() -> Any {
|
||||
let cfg = window_get("NEURON_CFG")
|
||||
if dom_is_null(cfg) {
|
||||
return null
|
||||
}
|
||||
let url: String = cfg["supabaseUrl"]
|
||||
let key: String = cfg["supabaseAnonKey"]
|
||||
supabase_create_client(url, key)
|
||||
}
|
||||
|
||||
// ── Auth flow ──────────────────────────────────────────────────────────────
|
||||
|
||||
@async
|
||||
fn send_magic_link() -> Void {
|
||||
let email_el = dom_get_element("acct-email-input")
|
||||
if dom_is_null(email_el) {
|
||||
show_message("Could not find email input", true)
|
||||
return null
|
||||
}
|
||||
|
||||
let email: String = str_trim(dom_get_value(email_el))
|
||||
|
||||
if !is_valid_email(email) {
|
||||
show_message("Please enter a valid email address", true)
|
||||
return null
|
||||
}
|
||||
|
||||
clear_message()
|
||||
set_button_loading(true)
|
||||
state_set("auth_email", email)
|
||||
|
||||
// Build the Supabase client and call auth.signInWithOtp directly.
|
||||
// Method call syntax on Any-typed values: client.auth.signInWithOtp(opts)
|
||||
// No native_js_call required.
|
||||
let client = get_supabase_client()
|
||||
if dom_is_null(client) {
|
||||
show_message("Auth service not configured", true)
|
||||
set_button_loading(false)
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
let opts: Map<String, Any> = { "email": email }
|
||||
// client is Any-typed; .auth returns the auth sub-client (also Any).
|
||||
// .signInWithOtp(opts) returns a Promise. @async + await handles it.
|
||||
let resp = client.auth.signInWithOtp(opts)
|
||||
let err = resp["error"]
|
||||
if !dom_is_null(err) {
|
||||
let msg: String = err["message"]
|
||||
show_message("Error: " + msg, true)
|
||||
} else {
|
||||
local_storage_set("auth_pending_email", email)
|
||||
show_message("Magic link sent! Check your inbox for " + email, false)
|
||||
let form = dom_get_element("auth-form")
|
||||
if !dom_is_null(form) {
|
||||
dom_hide(form)
|
||||
}
|
||||
}
|
||||
} catch (err: Any) {
|
||||
show_message("Unexpected error. Please try again.", true)
|
||||
}
|
||||
|
||||
set_button_loading(false)
|
||||
}
|
||||
|
||||
// ── Keyboard support ───────────────────────────────────────────────────────
|
||||
|
||||
fn handle_email_keydown(event: Any) -> Void {
|
||||
let key: String = dom_get_prop(event, "key")
|
||||
if str_eq(key, "Enter") {
|
||||
send_magic_link()
|
||||
}
|
||||
}
|
||||
|
||||
// ── Initialization ─────────────────────────────────────────────────────────
|
||||
|
||||
fn init_auth() -> Void {
|
||||
let email_el = dom_get_element("acct-email-input")
|
||||
if !dom_is_null(email_el) {
|
||||
// Pre-fill from local storage if a pending send was interrupted.
|
||||
let pending: String = local_storage_get("auth_pending_email")
|
||||
if !str_eq(pending, "") {
|
||||
dom_set_value(email_el, pending)
|
||||
}
|
||||
// Anonymous function literal for inline event handler.
|
||||
dom_listen(email_el, "keydown", fn(event: Any) -> Void {
|
||||
let key: String = dom_get_prop(event, "key")
|
||||
if str_eq(key, "Enter") {
|
||||
send_magic_link()
|
||||
}
|
||||
})
|
||||
}
|
||||
let btn = dom_get_element("send-link-btn")
|
||||
if !dom_is_null(btn) {
|
||||
dom_listen(btn, "click", fn(event: Any) -> Void {
|
||||
send_magic_link()
|
||||
})
|
||||
}
|
||||
state_set("auth_initialized", "true")
|
||||
}
|
||||
|
||||
fn main() -> Void {
|
||||
// Expose send_magic_link globally so inline event handlers can call it.
|
||||
window_set("sendMagicLink", send_magic_link)
|
||||
window_set("initAuth", init_auth)
|
||||
|
||||
// Run init when DOM is ready.
|
||||
window_on_load(init_auth)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// browser-counter.el — canonical browser DOM bridge example
|
||||
//
|
||||
// Compile with: elc --target=js examples/browser-counter.el > counter.js
|
||||
//
|
||||
// Then include in an HTML page that has a <span id="count-display"> element.
|
||||
// The page can call window.increment() from any onclick handler, e.g.:
|
||||
// <button onclick="increment()">+1</button>
|
||||
//
|
||||
// On load the display is initialised to "0". Each call to increment()
|
||||
// adds 1 and updates the display text.
|
||||
//
|
||||
// Demonstrates:
|
||||
// - dom_get_element to locate a DOM node by id
|
||||
// - dom_set_text to update visible text content
|
||||
// - dom_is_null to guard against missing elements
|
||||
// - window_set to expose an El function for inline event handlers
|
||||
// - state_set/get for in-memory counter state (survives calls, resets
|
||||
// on page reload — same semantics as the C state_* API)
|
||||
|
||||
fn init() -> Void {
|
||||
state_set("counter", 0)
|
||||
let display = dom_get_element("count-display")
|
||||
if !dom_is_null(display) {
|
||||
dom_set_text(display, "0")
|
||||
}
|
||||
}
|
||||
|
||||
fn increment() -> Void {
|
||||
let current = str_to_int(state_get("counter"))
|
||||
let next = current + 1
|
||||
state_set("counter", next)
|
||||
let display = dom_get_element("count-display")
|
||||
if !dom_is_null(display) {
|
||||
dom_set_text(display, int_to_str(next))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Void {
|
||||
init()
|
||||
window_set("increment", increment)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Engram language — example test file
|
||||
// Run with: el test-file examples/hello-project/src/tests.el
|
||||
|
||||
test "basic arithmetic" {
|
||||
let x: Int = 6
|
||||
let y: Int = 7
|
||||
let result: Int = x * y
|
||||
assert result == 42
|
||||
}
|
||||
|
||||
test "string operations" {
|
||||
let greeting: String = "Hello"
|
||||
let name: String = "Engram"
|
||||
let full: String = greeting + ", " + name
|
||||
assert full == "Hello, Engram"
|
||||
}
|
||||
|
||||
test "type graph has Point type" {
|
||||
seed Node {
|
||||
type: "Point"
|
||||
content: "A 2D coordinate"
|
||||
importance: 0.8
|
||||
tier: Semantic
|
||||
}
|
||||
|
||||
let results = activate Point where "coordinate"
|
||||
assert results.len() > 0
|
||||
}
|
||||
|
||||
test "empty graph" {
|
||||
let results = activate Customer where "anything"
|
||||
assert results.len() == 0
|
||||
}
|
||||
|
||||
test "empty graph returns insufficient" {
|
||||
let result = reason("Is there a customer named Will?")
|
||||
assert result.verdict == "Insufficient"
|
||||
}
|
||||
|
||||
test "seeded graph reasoning" {
|
||||
seed Node {
|
||||
type: "Customer"
|
||||
content: "Will Anderson, founding member"
|
||||
importance: 0.9
|
||||
tier: Semantic
|
||||
}
|
||||
|
||||
let result = reason("founding member")
|
||||
assert result.verdict == "Supported"
|
||||
}
|
||||
|
||||
test "production customer lookup" target: e2e {
|
||||
// No seeds — uses the real Engram database
|
||||
// (skipped when ENGRAM_URL is not set)
|
||||
let results = activate Customer where "founding member"
|
||||
assert results.len() > 0
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// hello.el — The simplest Engram program.
|
||||
// Run with: el run hello.el
|
||||
|
||||
fn greet(name: String) -> String {
|
||||
return "Hello, " + name
|
||||
}
|
||||
|
||||
let message: String = greet("World")
|
||||
@@ -0,0 +1,40 @@
|
||||
// html-page.el — Example of native HTML template syntax in El.
|
||||
//
|
||||
// El HTML templates let you write HTML directly in expression position.
|
||||
// Interpolated values are automatically HTML-escaped.
|
||||
// Use raw(expr) to bypass escaping when you know the content is safe.
|
||||
//
|
||||
// Compile and run:
|
||||
// ./dist/platform/elc examples/html-page.el > /tmp/html-page.c
|
||||
// cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
||||
// -o /tmp/html-page /tmp/html-page.c el-compiler/runtime/el_runtime.c
|
||||
// /tmp/html-page
|
||||
|
||||
fn render_item(item: String) -> String {
|
||||
return <li class="item">{item}</li>
|
||||
}
|
||||
|
||||
fn render_page(title: String, items: [String]) -> String {
|
||||
return <!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{title}</h1>
|
||||
<ul>
|
||||
{#each items as item}
|
||||
<li class="item">{item}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<p>Built with El HTML templates</p>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
fn main() -> Void {
|
||||
let items: [String] = ["Lexer", "Parser", "Codegen", "Runtime"]
|
||||
let page: String = render_page("El Compiler Stages", items)
|
||||
println(page)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// http-status-envelope.el — acceptance test for the __status__ HTTP envelope.
|
||||
//
|
||||
// Before fix: a handler returning {"__status__":401,"error":"unauthorized"}
|
||||
// went out as an HTTP 200 with the JSON body verbatim, so Cloud Run logs were
|
||||
// full of 200s for what should have been 4xx/5xx.
|
||||
//
|
||||
// After fix: when the response body's FIRST key is __status__, the runtime
|
||||
// reads the integer value as the HTTP status code and strips the marker from
|
||||
// the body before sending it to the client.
|
||||
//
|
||||
// Verify with curl:
|
||||
// curl -i http://localhost:8081/auth -> HTTP/1.1 401 Unauthorized
|
||||
// curl -i http://localhost:8081/health -> HTTP/1.1 200 OK
|
||||
// curl -i http://localhost:8081/oops -> HTTP/1.1 503 Service Unavailable
|
||||
|
||||
fn handle(method: String, path: String, body: String) -> String {
|
||||
if path == "/auth" {
|
||||
return "{\"__status__\":401,\"error\":\"unauthorized\"}"
|
||||
}
|
||||
if path == "/oops" {
|
||||
return "{\"__status__\":503,\"error\":\"degraded\"}"
|
||||
}
|
||||
if path == "/health" {
|
||||
return "{\"ok\":true}"
|
||||
}
|
||||
return "{\"__status__\":404,\"error\":\"not found\"}"
|
||||
}
|
||||
|
||||
fn main() -> Int {
|
||||
http_set_handler("handle")
|
||||
http_serve(8081, "handle")
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// json-array-traversal.el — acceptance test for json_get dot-path with array
|
||||
// indices.
|
||||
//
|
||||
// Before fix: json_get("...", "0.field") would substring-search for a literal
|
||||
// key named `"0.field"` and find nothing, returning "".
|
||||
//
|
||||
// After fix: dot-path segments that are all digits are treated as array
|
||||
// indices and the walker descends into the array.
|
||||
|
||||
fn test_array_traversal() -> String {
|
||||
let s: String = "[{\"name\":\"alice\"},{\"name\":\"bob\"}]"
|
||||
let a: String = json_get(s, "0.name")
|
||||
let b: String = json_get(s, "1.name")
|
||||
return a + "," + b
|
||||
}
|
||||
|
||||
fn main() -> Int {
|
||||
let r: String = test_array_traversal()
|
||||
print(r)
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// reassign-in-if.el — acceptance test for bare reassignment inside if-body.
|
||||
//
|
||||
// Before fix: parser dropped `x = "override"` on the floor and codegen emitted
|
||||
// three orphan expressions (`x; EL_NULL; EL_STR("override");`). Effective store
|
||||
// was lost, so the function returned "default".
|
||||
//
|
||||
// After fix: parse_stmt recognises `Ident "=" Expr` as an Assign statement and
|
||||
// codegen emits a real C assignment, so the function returns "override".
|
||||
|
||||
fn test_reassign() -> String {
|
||||
let x: String = "default"
|
||||
if true {
|
||||
x = "override"
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
fn main() -> Int {
|
||||
let r: String = test_reassign()
|
||||
print(r)
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// types.el — Type definitions and pattern matching.
|
||||
//
|
||||
// Demonstrates:
|
||||
// - type (struct) definitions
|
||||
// - enum definitions with payloads
|
||||
// - match expressions with enum patterns
|
||||
|
||||
// A user-defined struct type.
|
||||
// Every field is typed; the type name maps to an Engram graph node.
|
||||
type User {
|
||||
id: Uuid
|
||||
name: String
|
||||
email: String
|
||||
}
|
||||
|
||||
// An enum with a payload variant.
|
||||
enum Status {
|
||||
Active
|
||||
Inactive
|
||||
Pending(String)
|
||||
}
|
||||
|
||||
// A function that takes a Status and returns a human-readable string.
|
||||
fn describe_status(s: Status) -> String {
|
||||
return match s {
|
||||
Status::Active => "user is active"
|
||||
Status::Inactive => "user is inactive"
|
||||
Status::Pending(reason) => "pending: " + reason
|
||||
}
|
||||
}
|
||||
|
||||
// Sealed blocks protect sensitive values even in debug builds.
|
||||
// The runtime enforces that values in sealed blocks are never
|
||||
// observable through a debugger or memory dump.
|
||||
sealed {
|
||||
let api_key: String = "sk-prod-12345"
|
||||
}
|
||||
|
||||
let status: Status = Status::Pending("email not verified")
|
||||
let description: String = describe_status(status)
|
||||
Reference in New Issue
Block a user