Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81a1a624f1 | |||
| a3732a1e9a | |||
| 8ef3eb6bec | |||
| 8ab8e3fd31 | |||
| b0d0975f05 | |||
| 908ce303f3 |
@@ -252,4 +252,51 @@ jobs:
|
||||
--source=el-compiler/runtime/el_runtime.js
|
||||
|
||||
echo "Published El SDK version=${VERSION} to foundation-dev"
|
||||
# Keep key alive for the ci-base rebuild step below
|
||||
# (deleted in that step after docker push)
|
||||
|
||||
- name: Rebuild ci-base with fresh El SDK (dev)
|
||||
# Patches ci-base:dev in-place: pulls the existing image (which has all
|
||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CI_BASE="us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base"
|
||||
SHA="${GITHUB_SHA:0:8}"
|
||||
|
||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||
gcloud config set project neuron-785695
|
||||
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
|
||||
|
||||
# Pull existing ci-base:dev (system deps stay cached in the base layer)
|
||||
docker pull "${CI_BASE}:dev" || docker pull "${CI_BASE}:latest"
|
||||
|
||||
# Inline Dockerfile — only replaces the El SDK layer
|
||||
cat > /tmp/Dockerfile.ci-base-patch << 'EOF'
|
||||
ARG BASE
|
||||
FROM ${BASE}
|
||||
COPY dist/platform/elc /opt/el/dist/platform/elc
|
||||
COPY dist/bin/elb /opt/el/dist/bin/elb
|
||||
COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c
|
||||
COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h
|
||||
COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js
|
||||
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||
EOF
|
||||
|
||||
docker build \
|
||||
--build-arg BASE="${CI_BASE}:dev" \
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||
-f /tmp/Dockerfile.ci-base-patch \
|
||||
-t "${CI_BASE}:dev" \
|
||||
-t "${CI_BASE}:dev-${SHA}" \
|
||||
.
|
||||
|
||||
docker push "${CI_BASE}:dev"
|
||||
docker push "${CI_BASE}:dev-${SHA}"
|
||||
|
||||
echo "ci-base rebuilt: ${CI_BASE}:dev (${SHA})"
|
||||
rm -f /tmp/gcp-key.json
|
||||
|
||||
@@ -246,4 +246,51 @@ jobs:
|
||||
--source=el-compiler/runtime/el_runtime.h
|
||||
|
||||
echo "Published El SDK version=${VERSION} to foundation-stage"
|
||||
# Keep key alive for the ci-base rebuild step below
|
||||
# (deleted in that step after docker push)
|
||||
|
||||
- name: Rebuild ci-base with fresh El SDK (stage)
|
||||
# Patches ci-base:stage in-place: pulls the existing image (which has all
|
||||
# system deps — Node, Go, gcloud, Docker CLI, etc.) and overlays the freshly
|
||||
# built El SDK on top. Keeps the full ci-base rebuild fast and incremental.
|
||||
if: github.event_name == 'push'
|
||||
env:
|
||||
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CI_BASE="us-central1-docker.pkg.dev/neuron-785695/neuron-ci/ci-base"
|
||||
SHA="${GITHUB_SHA:0:8}"
|
||||
|
||||
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
|
||||
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
|
||||
gcloud config set project neuron-785695
|
||||
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
|
||||
|
||||
# Pull existing ci-base:stage (system deps stay cached in the base layer)
|
||||
docker pull "${CI_BASE}:stage" || docker pull "${CI_BASE}:latest"
|
||||
|
||||
# Inline Dockerfile — only replaces the El SDK layer
|
||||
cat > /tmp/Dockerfile.ci-base-patch << 'EOF'
|
||||
ARG BASE
|
||||
FROM ${BASE}
|
||||
COPY dist/platform/elc /opt/el/dist/platform/elc
|
||||
COPY dist/bin/elb /opt/el/dist/bin/elb
|
||||
COPY el-compiler/runtime/el_runtime.c /opt/el/el-compiler/runtime/el_runtime.c
|
||||
COPY el-compiler/runtime/el_runtime.h /opt/el/el-compiler/runtime/el_runtime.h
|
||||
COPY el-compiler/runtime/el_runtime.js /opt/el/el-compiler/runtime/el_runtime.js
|
||||
RUN chmod +x /opt/el/dist/platform/elc /opt/el/dist/bin/elb
|
||||
EOF
|
||||
|
||||
docker build \
|
||||
--build-arg BASE="${CI_BASE}:stage" \
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||
-f /tmp/Dockerfile.ci-base-patch \
|
||||
-t "${CI_BASE}:stage" \
|
||||
-t "${CI_BASE}:stage-${SHA}" \
|
||||
.
|
||||
|
||||
docker push "${CI_BASE}:stage"
|
||||
docker push "${CI_BASE}:stage-${SHA}"
|
||||
|
||||
echo "ci-base rebuilt: ${CI_BASE}:stage (${SHA})"
|
||||
rm -f /tmp/gcp-key.json
|
||||
|
||||
@@ -324,6 +324,10 @@ fn cg_html_parts(children: [Map<String, Any>], acc_var: String) -> String {
|
||||
let each_c: String = cg_html_each(child, acc_var)
|
||||
let parts = native_list_append(parts, each_c)
|
||||
}
|
||||
if str_eq(html_kind, "HtmlIf") {
|
||||
let if_c: String = cg_html_if(child, acc_var)
|
||||
let parts = native_list_append(parts, if_c)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
str_join(parts, "")
|
||||
@@ -413,6 +417,17 @@ fn cg_html_each(node: Map<String, Any>, acc_var: String) -> String {
|
||||
"{ el_val_t " + list_var + " = (" + list_c + "); el_val_t " + len_var + " = el_list_len(" + list_var + "); for (el_val_t " + idx_var + " = 0; " + idx_var + " < " + len_var + "; " + idx_var + "++) { el_val_t " + item_name + " = el_list_get(" + list_var + ", " + idx_var + "); " + inner_c + "} } "
|
||||
}
|
||||
|
||||
// Generate code for {#if cond} ... {/if} (with optional {#else}).
|
||||
fn cg_html_if(node: Map<String, Any>, acc_var: String) -> String {
|
||||
let cond_expr = node["cond"]
|
||||
let then_children: [Map<String, Any>] = node["then"]
|
||||
let else_children: [Map<String, Any>] = node["else"]
|
||||
let cond_c: String = cg_expr(cond_expr)
|
||||
let then_c: String = cg_html_parts(then_children, acc_var)
|
||||
let else_c: String = cg_html_parts(else_children, acc_var)
|
||||
"if (" + cond_c + ") { " + then_c + " } else { " + else_c + " } "
|
||||
}
|
||||
|
||||
// Top-level HTML template codegen — returns a C statement-expression string.
|
||||
fn cg_html_template(expr: Map<String, Any>) -> String {
|
||||
let root = expr["root"]
|
||||
|
||||
+283
-93
@@ -293,6 +293,48 @@ fn is_void_element(name: String) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
// Collect all tokens as raw text until </tag_name> is encountered.
|
||||
// Used for <style> and <script> elements to avoid parsing CSS/JS as El.
|
||||
// Returns { "text": "...", "pos": p_after_closing_tag }
|
||||
fn parse_raw_text_content(tokens: [Any], pos: Int, tag_name: String) -> Map<String, Any> {
|
||||
let parts: [String] = native_list_empty()
|
||||
let p = pos
|
||||
let running = true
|
||||
while running {
|
||||
let k = tok_kind(tokens, p)
|
||||
if str_eq(k, "Eof") {
|
||||
let running = false
|
||||
} else {
|
||||
if str_eq(k, "Lt") {
|
||||
let k2 = tok_kind(tokens, p + 1)
|
||||
if str_eq(k2, "Slash") {
|
||||
// Check if this is </tag_name>
|
||||
let close_name = tok_value(tokens, p + 2)
|
||||
if str_eq(close_name, tag_name) {
|
||||
// consume </tag_name>
|
||||
let p = p + 3
|
||||
let p = expect(tokens, p, "Gt")
|
||||
let running = false
|
||||
} else {
|
||||
let v = tok_value(tokens, p)
|
||||
let parts = native_list_append(parts, v)
|
||||
let p = p + 1
|
||||
}
|
||||
} else {
|
||||
let v = tok_value(tokens, p)
|
||||
let parts = native_list_append(parts, v)
|
||||
let p = p + 1
|
||||
}
|
||||
} else {
|
||||
let v = tok_value(tokens, p)
|
||||
let parts = native_list_append(parts, v)
|
||||
let p = p + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
{ "text": str_join(parts, " "), "pos": p }
|
||||
}
|
||||
|
||||
// Collect tokens as text content until we hit Lt, LBrace, Eof, or a
|
||||
// closing-tag marker (Lt Slash). Returns { "text": "...", "pos": p }
|
||||
fn parse_html_text_tokens(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||
@@ -435,77 +477,125 @@ fn parse_html_children(tokens: [Any], pos: Int, parent_tag: String) -> Map<Strin
|
||||
}
|
||||
} else {
|
||||
if str_eq(k, "LBrace") {
|
||||
// Interpolation: {expr} or {#each ...} or {/each}
|
||||
// Interpolation: {expr}, {#each ...}, {#if ...}, {#else}, {/each}, {/if}
|
||||
// Note: '#' (ASCII 35) is skipped by the lexer, so {#each} lexes as
|
||||
// LBrace Ident:"each" ... and {#if} lexes as LBrace If ... and
|
||||
// {#else} lexes as LBrace Else RBrace.
|
||||
let k2 = tok_kind(tokens, p + 1)
|
||||
if str_eq(k2, "Hash") {
|
||||
// {#each list as item}
|
||||
let k3_v = tok_value(tokens, p + 2)
|
||||
if str_eq(k3_v, "each") {
|
||||
let p = p + 3
|
||||
// parse list expr up to "as" keyword
|
||||
if str_eq(k2, "Slash") {
|
||||
// {/each} or {/if} — end of block, stop
|
||||
// skip { /
|
||||
let p = p + 2
|
||||
// skip the close-tag name token (each, if, etc.)
|
||||
let p = p + 1
|
||||
// skip }
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
let running = false
|
||||
} else {
|
||||
if str_eq(k2, "If") {
|
||||
// {#if condition} ... {/if}
|
||||
// Skip { if (2 tokens; '#' was silently skipped by lexer)
|
||||
let p = p + 2
|
||||
// Parse condition expression (no block expr)
|
||||
let prev_no_block: String = state_get("__no_block_expr")
|
||||
state_set("__no_block_expr", "1")
|
||||
let r_list = parse_expr(tokens, p)
|
||||
let r_cond = parse_expr(tokens, p)
|
||||
state_set("__no_block_expr", prev_no_block)
|
||||
let list_expr = r_list["node"]
|
||||
let p = r_list["pos"]
|
||||
// r_list result map fully consumed — release to free peak heap.
|
||||
el_release(r_list)
|
||||
// expect "as"
|
||||
let p = expect(tokens, p, "As")
|
||||
// item variable name
|
||||
let item_name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
let cond_expr = r_cond["node"]
|
||||
let p = r_cond["pos"]
|
||||
el_release(r_cond)
|
||||
// consume closing }
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// parse body until {/each}
|
||||
let r_body = parse_html_each_body(tokens, p)
|
||||
let body_children = r_body["children"]
|
||||
let p = r_body["pos"]
|
||||
// r_body result map fully consumed — release to free peak heap.
|
||||
el_release(r_body)
|
||||
let each_node: Map<String, Any> = { "html": "Each", "list": list_expr, "item": item_name, "body": body_children }
|
||||
let children = native_list_append(children, each_node)
|
||||
} else {
|
||||
let p = p + 1
|
||||
}
|
||||
} else {
|
||||
if str_eq(k2, "Slash") {
|
||||
// {/each} — end of each block, stop
|
||||
// skip {/each}
|
||||
let p = p + 2
|
||||
// skip "each"
|
||||
let p = p + 1
|
||||
// skip }
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
let running = false
|
||||
} else {
|
||||
// regular {expr}
|
||||
let r = parse_expr(tokens, p + 1)
|
||||
let interp_val = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// Check if the expr is a call to raw()
|
||||
let is_raw_call = false
|
||||
let interp_kind: String = interp_val["expr"]
|
||||
if str_eq(interp_kind, "Call") {
|
||||
let fn_node = interp_val["func"]
|
||||
let fn_kind: String = fn_node["expr"]
|
||||
if str_eq(fn_kind, "Ident") {
|
||||
let fn_name_v: String = fn_node["name"]
|
||||
if str_eq(fn_name_v, "raw") {
|
||||
let is_raw_call = true
|
||||
}
|
||||
// parse then-children until {#else} or {/if}
|
||||
let r_then = parse_html_children(tokens, p, "__if_then__")
|
||||
let then_children = r_then["children"]
|
||||
let p = r_then["pos"]
|
||||
el_release(r_then)
|
||||
// check for {#else} — lexed as LBrace Else RBrace
|
||||
let else_children: [Map<String, Any>] = native_list_empty()
|
||||
let ck = tok_kind(tokens, p)
|
||||
if str_eq(ck, "LBrace") {
|
||||
let ck2 = tok_kind(tokens, p + 1)
|
||||
if str_eq(ck2, "Else") {
|
||||
// consume { else }
|
||||
let p = p + 2
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// parse else-children until {/if}
|
||||
let r_else = parse_html_children(tokens, p, "__if_else__")
|
||||
let else_children = r_else["children"]
|
||||
let p = r_else["pos"]
|
||||
el_release(r_else)
|
||||
}
|
||||
}
|
||||
if is_raw_call {
|
||||
let raw_args = interp_val["args"]
|
||||
let raw_inner = native_list_get(raw_args, 0)
|
||||
let children = native_list_append(children, { "html": "Raw", "value": raw_inner })
|
||||
let if_node: Map<String, Any> = { "html": "HtmlIf", "cond": cond_expr, "then": then_children, "else": else_children }
|
||||
let children = native_list_append(children, if_node)
|
||||
} else {
|
||||
if str_eq(k2, "Else") {
|
||||
// {#else} sentinel — lexed as LBrace Else RBrace
|
||||
// Do NOT consume — leave position for caller ({#if} handler checks for it)
|
||||
let running = false
|
||||
} else {
|
||||
let children = native_list_append(children, { "html": "Interp", "value": interp_val })
|
||||
// Check for {#each list as item} — lexed as LBrace Ident:"each" ...
|
||||
let k2_v = tok_value(tokens, p + 1)
|
||||
if str_eq(k2_v, "each") {
|
||||
let p = p + 2
|
||||
// parse list expr up to "as" keyword
|
||||
let prev_no_block: String = state_get("__no_block_expr")
|
||||
state_set("__no_block_expr", "1")
|
||||
let r_list = parse_expr(tokens, p)
|
||||
state_set("__no_block_expr", prev_no_block)
|
||||
let list_expr = r_list["node"]
|
||||
let p = r_list["pos"]
|
||||
// r_list result map fully consumed — release to free peak heap.
|
||||
el_release(r_list)
|
||||
// expect "as"
|
||||
let p = expect(tokens, p, "As")
|
||||
// item variable name
|
||||
let item_name = tok_value(tokens, p)
|
||||
let p = p + 1
|
||||
// consume closing }
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// parse body until {/each}
|
||||
let r_body = parse_html_each_body(tokens, p)
|
||||
let body_children = r_body["children"]
|
||||
let p = r_body["pos"]
|
||||
// r_body result map fully consumed — release to free peak heap.
|
||||
el_release(r_body)
|
||||
let each_node: Map<String, Any> = { "html": "Each", "list": list_expr, "item": item_name, "body": body_children }
|
||||
let children = native_list_append(children, each_node)
|
||||
} else {
|
||||
// regular {expr} — disable map-literal parsing so {fn(a,b)}
|
||||
// does not trigger the LBrace→map path inside parse_primary
|
||||
let prev_no_block: String = state_get("__no_block_expr")
|
||||
state_set("__no_block_expr", "1")
|
||||
let r = parse_expr(tokens, p + 1)
|
||||
state_set("__no_block_expr", prev_no_block)
|
||||
let interp_val = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
// Check if the expr is a call to raw()
|
||||
let is_raw_call = false
|
||||
let interp_kind: String = interp_val["expr"]
|
||||
if str_eq(interp_kind, "Call") {
|
||||
let fn_node = interp_val["func"]
|
||||
let fn_kind: String = fn_node["expr"]
|
||||
if str_eq(fn_kind, "Ident") {
|
||||
let fn_name_v: String = fn_node["name"]
|
||||
if str_eq(fn_name_v, "raw") {
|
||||
let is_raw_call = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_raw_call {
|
||||
let raw_args = interp_val["args"]
|
||||
let raw_inner = native_list_get(raw_args, 0)
|
||||
let children = native_list_append(children, { "html": "Raw", "value": raw_inner })
|
||||
} else {
|
||||
let children = native_list_append(children, { "html": "Interp", "value": interp_val })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,6 +655,27 @@ fn parse_html_element(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||
if is_void_element(tag_name) {
|
||||
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": native_list_empty(), "self_closing": true }, p)
|
||||
}
|
||||
// raw-text mode for style/script — collect content as plain text without parsing CSS/JS as El
|
||||
if str_eq(tag_name, "style") {
|
||||
let r_raw = parse_raw_text_content(tokens, p, "style")
|
||||
let raw_text: String = r_raw["text"]
|
||||
let p = r_raw["pos"]
|
||||
el_release(r_raw)
|
||||
let raw_child: Map<String, Any> = { "html": "Text", "text": raw_text }
|
||||
let raw_children: [Map<String, Any>] = native_list_empty()
|
||||
let raw_children = native_list_append(raw_children, raw_child)
|
||||
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": raw_children, "self_closing": false }, p)
|
||||
}
|
||||
if str_eq(tag_name, "script") {
|
||||
let r_raw = parse_raw_text_content(tokens, p, "script")
|
||||
let raw_text: String = r_raw["text"]
|
||||
let p = r_raw["pos"]
|
||||
el_release(r_raw)
|
||||
let raw_child: Map<String, Any> = { "html": "Text", "text": raw_text }
|
||||
let raw_children: [Map<String, Any>] = native_list_empty()
|
||||
let raw_children = native_list_append(raw_children, raw_child)
|
||||
return make_result({ "html": "Element", "tag": tag_name, "attrs": attrs, "children": raw_children, "self_closing": false }, p)
|
||||
}
|
||||
// parse children
|
||||
let r_children = parse_html_children(tokens, p, tag_name)
|
||||
let children = r_children["children"]
|
||||
@@ -718,44 +829,123 @@ fn parse_primary(tokens: [Any], pos: Int) -> Map<String, Any> {
|
||||
// as the start of the block they're expecting.
|
||||
return make_result({ "expr": "Nil" }, pos)
|
||||
}
|
||||
let p = pos + 1
|
||||
let pairs: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
if k2 == "Eof" {
|
||||
// Distinguish map literal from interpolation chain.
|
||||
// A map literal requires { key: value } — the second token inside { must be Colon.
|
||||
// An empty {} is a map literal. Everything else is an interpolation chain.
|
||||
let first_k: String = tok_kind(tokens, pos + 1)
|
||||
let second_k: String = tok_kind(tokens, pos + 2)
|
||||
if str_eq(first_k, "RBrace") {
|
||||
// Empty map literal {}
|
||||
return make_result({ "expr": "Map", "pairs": native_list_empty() }, pos + 2)
|
||||
}
|
||||
if str_eq(second_k, "Colon") {
|
||||
// MAP LITERAL: { key: value, ... }
|
||||
let p = pos + 1
|
||||
let pairs: [Map<String, Any>] = native_list_empty()
|
||||
let running = true
|
||||
while running {
|
||||
let k2 = tok_kind(tokens, p)
|
||||
if k2 == "RBrace" {
|
||||
let running = false
|
||||
} else {
|
||||
// key: Str token
|
||||
let key = tok_value(tokens, p)
|
||||
let new_p: Int = p + 1
|
||||
let new_p = expect(tokens, new_p, "Colon")
|
||||
let r = parse_expr(tokens, new_p)
|
||||
let val_node = r["node"]
|
||||
let new_p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let pair = { "key": key, "value": val_node }
|
||||
let pairs = native_list_append(pairs, pair)
|
||||
let k3 = tok_kind(tokens, new_p)
|
||||
if k3 == "Comma" {
|
||||
let new_p = new_p + 1
|
||||
}
|
||||
// Non-progress guard: malformed map content can leave
|
||||
// parse_expr returning the same pos. Force advance.
|
||||
if new_p <= p {
|
||||
let p = p + 1
|
||||
if k2 == "Eof" {
|
||||
let running = false
|
||||
} else {
|
||||
let p = new_p
|
||||
// key: Str or Ident token
|
||||
let key = tok_value(tokens, p)
|
||||
let new_p: Int = p + 1
|
||||
let new_p = expect(tokens, new_p, "Colon")
|
||||
let r = parse_expr(tokens, new_p)
|
||||
let val_node = r["node"]
|
||||
let new_p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let pair = { "key": key, "value": val_node }
|
||||
let pairs = native_list_append(pairs, pair)
|
||||
let k3 = tok_kind(tokens, new_p)
|
||||
if k3 == "Comma" {
|
||||
let new_p = new_p + 1
|
||||
}
|
||||
// Non-progress guard: malformed map content can leave
|
||||
// parse_expr returning the same pos. Force advance.
|
||||
if new_p <= p {
|
||||
let p = p + 1
|
||||
} else {
|
||||
let p = new_p
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
||||
}
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
return make_result({ "expr": "Map", "pairs": pairs }, p)
|
||||
// INTERPOLATION CHAIN: {expr}, {expr}{expr}, {expr}<html>, etc.
|
||||
// Build a BinOp(Plus, ...) concatenation chain.
|
||||
let p = pos
|
||||
let chain_node: Map<String, Any> = { "expr": "Nil" }
|
||||
let chain_started = false
|
||||
let chain_running = true
|
||||
while chain_running {
|
||||
let ck: String = tok_kind(tokens, p)
|
||||
if str_eq(ck, "LBrace") {
|
||||
let prev_no_block: String = state_get("__no_block_expr")
|
||||
state_set("__no_block_expr", "1")
|
||||
let r = parse_expr(tokens, p + 1)
|
||||
state_set("__no_block_expr", prev_no_block)
|
||||
let part = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
let p = expect(tokens, p, "RBrace")
|
||||
if !chain_started {
|
||||
let chain_node = part
|
||||
let chain_started = true
|
||||
} else {
|
||||
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||
}
|
||||
} else {
|
||||
if str_eq(ck, "Lt") {
|
||||
let ck2: String = tok_kind(tokens, p + 1)
|
||||
if str_eq(ck2, "Not") {
|
||||
let r = parse_html_template(tokens, p)
|
||||
let part = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
if !chain_started {
|
||||
let chain_node = part
|
||||
let chain_started = true
|
||||
} else {
|
||||
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||
}
|
||||
} else {
|
||||
if str_eq(ck2, "Ident") {
|
||||
let tag_candidate: String = tok_value(tokens, p + 1)
|
||||
if is_html_tag_name(tag_candidate) {
|
||||
let r = parse_html_template(tokens, p)
|
||||
let part = r["node"]
|
||||
let p = r["pos"]
|
||||
// r result map fully consumed — release to free peak heap.
|
||||
el_release(r)
|
||||
if !chain_started {
|
||||
let chain_node = part
|
||||
let chain_started = true
|
||||
} else {
|
||||
let chain_node: Map<String, Any> = { "expr": "BinOp", "op": "Plus", "left": chain_node, "right": part }
|
||||
}
|
||||
} else {
|
||||
let chain_running = false
|
||||
}
|
||||
} else {
|
||||
let chain_running = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let chain_running = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return make_result(chain_node, p)
|
||||
}
|
||||
|
||||
// if expression
|
||||
|
||||
Reference in New Issue
Block a user