# attrs_node.ludic — @Node(model) / @Clip(model) / @Material(model): a string naming a node, a clip or a # material inside the glTF # another field of the same record names. That field is @Asset("gltf") itself, or an @Ref(R) whose record has exactly one # @Asset("gltf") field (the model is found through the row). Anything else is an error naming it. # the field of record d called name, or null function at_field_of(d: Node, name: pointer) -> Node { var k = 0 while k < len(d.kids) { if d.kids[k].s != null and vis_plain(d.kids[k].s) == name { return d.kids[k] } k += 1 } return null } # field f's argument to attribute a as written, or null when f does not carry it function at_arg_of(f: Node, a: pointer) -> pointer { if f.at < 0 { return null } var j = g_at_start[f.at] while j < g_at_start[f.at] + g_at_count[f.at] { if g_at_name[j] == a { return g_at_first[j] } j += 1 } return null } function at_is_gltf(f: Node) -> bool { let a = at_arg_of(f, "Asset") return a != null and a == "gltf" } # the record called name (a property or record), or null function at_record(name: pointer) -> Node { let plain = vis_plain(name) var i = 0 while i < len(prog) { if prog[i].kind == N_COMP and prog[i].s != null and vis_plain(prog[i].s) == plain { return prog[i] } i += 1 } return null } # how many @Asset("gltf") fields record r carries function at_gltf_count(r: Node) -> int { var n = 0 var k = 0 while k < len(r.kids) { if at_is_gltf(r.kids[k]) { n += 1 } k += 1 } return n } function at_check_node(d: Node, f: Node, a: pointer, target: pointer) -> void { let where = `field {d.s}.{f.s}: @{a}({target})` let t = at_field_of(d, target) if t == null { perr_at(f.file, f.line, f.col, `{where} names no field - {d.s} has no field {target}`) return } if at_is_gltf(t) { return } let reg = at_arg_of(t, "Ref") if reg == null { perr_at(f.file, f.line, f.col, `{where}: {target} is neither @Asset("gltf") nor an @Ref to a registry of models`) return } let r = reg_find(reg) if r < 0 { return } # @Ref's own check says so let rec = at_record(g_rg_type[r]) var n = 0 if rec != null { n = at_gltf_count(rec) } if n != 1 { perr_at(f.file, f.line, f.col, `{where}: {target} is an @Ref({reg}), and its record {g_rg_type[r]} has {n} @Asset("gltf") fields, not one`) } } # @OneOf: on a string field, words, and every row's value one of them; otherwise one argument ending # in _ is a prefix, or every argument a constant that must exist function at_check_oneof(d: Node, f: Node, j: int) -> void { let where = `field {d.s}.{f.s}: @OneOf` let words = g_at_strs[j] let is_text = f.ty != null and f.ty == "string" if words and not is_text { perr_at(f.file, f.line, f.col, `{where} lists words, and {f.s} is not a string - name constants instead`) return } if is_text and not words { perr_at(f.file, f.line, f.col, `{where} names constants, and {f.s} is a string - list the words it may hold: @OneOf("a", "b")`) return } let names = at_arg_names(g_at_args[j]) if words { at_check_rows(d, f, names) return } let first = g_at_first[j] if g_at_nargs[j] == 1 and first[len(first) - 1] == '_' { return } var i = 0 while i < len(names) { if not at_const_exists(names[i]) { at_const_target(d, f, j, names[i]) } i += 1 } } # every registry row of record d that writes field f as a string: one of the words function at_check_rows(d: Node, f: Node, words: []pointer) -> void { let plain = vis_plain(d.s) var i = 0 while i < len(g_df_rec) { let r = reg_find(g_df_reg[i]) if r >= 0 and vis_plain(g_rg_type[r]) == plain { at_check_row(d, f, g_df_rec[i], g_df_key[i], words) } i += 1 } } function at_check_row(d: Node, f: Node, rec: Node, key: pointer, words: []pointer) -> void { var k = 0 while k < len(rec.kids) { let fi = rec.kids[k] if fi.kind == E_FINIT and fi.s == f.s and fi.a != null and fi.a.kind == E_STR and not at_word_in(fi.a.s, words) { perr_at(fi.file, fi.line, fi.col, `{key}.{f.s} is "{fi.a.s}", and field {d.s}.{f.s} is @OneOf({at_words_text(words)})`) } k += 1 } } function at_word_in(w: pointer, words: []pointer) -> bool { var i = 0 while i < len(words) { if words[i] == w { return true } i += 1 } return false } function at_words_text(words: []pointer) -> pointer { var out: pointer = "" var i = 0 while i < len(words) { if i > 0 { out = out + ", " } out = out + "\"" + words[i] + "\"" i += 1 } return out } # the names in an attribute's JSON arguments (["A", "B"]): each a quoted identifier function at_arg_names(args: pointer) -> []pointer { let out = new []pointer var i = 0 var at = -1 while i < len(args) { if args[i] == '"' { if at < 0 { at = i + 1 } else { push(out, args[at .. i]) at = -1 } } i += 1 } return out } function at_const_exists(name: pointer) -> bool { var i = 0 while i < len(prog) { if prog[i].kind == N_CONST and prog[i].s != null and vis_plain(prog[i].s) == name { return true } i += 1 } return false } # a declaration of any kind (a record, a function, a const, a state ...) called name function at_decl_named(name: pointer) -> bool { var i = 0 while i < len(prog) { if prog[i].s != null and vis_plain(prog[i].s) == name { return true } i += 1 } return false } # a field's @Asset: 0 none or a game path, 1 a path under each map's directory, 2 the same and optional function at_asset_map(f: Node) -> int { if f.at < 0 { return 0 } var j = g_at_start[f.at] while j < g_at_start[f.at] + g_at_count[f.at] { if g_at_name[j] == "Asset" { let w = at_arg_names(g_at_args[j]) if len(w) >= 3 { return 2 } if len(w) == 2 { return 1 } return 0 } j += 1 } return 0 }