Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions runtime/include/hemlock_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ HmlValue hml_convert_to_type(HmlValue val, HmlValueType target_type);
// Type conversion that allows string parsing (for type constructors like i32("42"))
HmlValue hml_parse_string_to_type(HmlValue val, HmlValueType target_type);

// Schema extraction - returns JSON Schema-compatible object from type registry
HmlValue hml_schema(HmlValue type_name);

// Assertions
void hml_assert(HmlValue condition, HmlValue message);
__attribute__((noreturn)) void hml_panic(HmlValue message);
Expand Down
130 changes: 130 additions & 0 deletions runtime/src/builtins_types.c
Original file line number Diff line number Diff line change
Expand Up @@ -225,5 +225,135 @@ HmlValue hml_validate_enum_value(HmlValue val, const char *enum_name) {
exit(1);
}

// ========== SCHEMA EXTRACTION ==========

// Map HML_VAL_* type kind to JSON Schema type string
static const char* hml_type_kind_to_json_schema_type(int type_kind) {
switch (type_kind) {
case HML_VAL_I8:
case HML_VAL_I16:
case HML_VAL_I32:
case HML_VAL_I64:
case HML_VAL_U8:
case HML_VAL_U16:
case HML_VAL_U32:
case HML_VAL_U64:
return "integer";
case HML_VAL_F32:
case HML_VAL_F64:
return "number";
case HML_VAL_BOOL:
return "boolean";
case HML_VAL_STRING:
return "string";
case HML_VAL_ARRAY:
return "array";
case HML_VAL_OBJECT:
return "object";
case HML_VAL_NULL:
return "null";
default:
return "any";
}
}

// Map HML_VAL_* type kind to Hemlock type name string
static const char* hml_type_kind_to_hemlock_type(int type_kind) {
switch (type_kind) {
case HML_VAL_I8: return "i8";
case HML_VAL_I16: return "i16";
case HML_VAL_I32: return "i32";
case HML_VAL_I64: return "i64";
case HML_VAL_U8: return "u8";
case HML_VAL_U16: return "u16";
case HML_VAL_U32: return "u32";
case HML_VAL_U64: return "u64";
case HML_VAL_F32: return "f32";
case HML_VAL_F64: return "f64";
case HML_VAL_BOOL: return "bool";
case HML_VAL_STRING: return "string";
case HML_VAL_RUNE: return "rune";
case HML_VAL_PTR: return "ptr";
case HML_VAL_BUFFER: return "buffer";
case HML_VAL_ARRAY: return "array";
case HML_VAL_OBJECT: return "object";
case HML_VAL_NULL: return "null";
default: return "any";
}
}

// hml_schema(type_name) -> object
// Extracts a JSON Schema-compatible object from the runtime type registry.
HmlValue hml_schema(HmlValue type_name_val) {
if (type_name_val.type != HML_VAL_STRING) {
hml_runtime_error("schema() argument must be a string");
}

const char *type_name = type_name_val.as.as_string->data;
HmlTypeDef *type = hml_lookup_type(type_name);

if (!type) {
fprintf(stderr, "Runtime error: schema() unknown type '%s'\n", type_name);
exit(1);
}

// Build schema object: { type: "object", name: "TypeName", properties: {...}, required: [...] }
HmlValue schema = hml_val_object();

// schema.type = "object"
hml_object_set_field(schema, "type", hml_val_string("object"));

// schema.name = type_name
hml_object_set_field(schema, "name", hml_val_string(type_name));

// Build properties object
HmlValue properties = hml_val_object();

// Build required array
HmlValue required = hml_val_array();

for (int i = 0; i < type->num_fields; i++) {
HmlTypeField *field = &type->fields[i];

// Build property descriptor
HmlValue prop = hml_val_object();

// JSON Schema type
const char *json_type = (field->type_kind >= 0)
? hml_type_kind_to_json_schema_type(field->type_kind)
: "any";
hml_object_set_field(prop, "type", hml_val_string(json_type));

// Hemlock type
const char *hemlock_type = (field->type_kind >= 0)
? hml_type_kind_to_hemlock_type(field->type_kind)
: "any";
hml_object_set_field(prop, "hemlock_type", hml_val_string(hemlock_type));

// Required flag
hml_object_set_field(prop, "required", hml_val_bool(!field->is_optional));

// Add property to properties
hml_object_set_field(properties, field->name, prop);
hml_release(&prop);

// Add to required array if not optional
if (!field->is_optional) {
HmlValue req_name = hml_val_string(field->name);
hml_array_push(required, req_name);
hml_release(&req_name);
}
}

// Set properties and required on schema
hml_object_set_field(schema, "properties", properties);
hml_release(&properties);

hml_object_set_field(schema, "required", required);
hml_release(&required);

return schema;
}

// FFI (Foreign Function Interface) operations moved to builtins_ffi.c

9 changes: 9 additions & 0 deletions src/backends/compiler/codegen_call.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ char* codegen_expr_call(CodegenContext *ctx, Expr *expr, char *result) {
return result;
}

// Handle schema builtin - extract JSON Schema from type registry
if (strcmp(fn_name, "schema") == 0 && expr->as.call.num_args == 1) {
char *arg = codegen_expr(ctx, expr->as.call.args[0]);
codegen_writeln(ctx, "HmlValue %s = hml_schema(%s);", result, arg);
codegen_writeln(ctx, "hml_release(&%s);", arg);
free(arg);
return result;
}

// Handle get_stack_limit builtin
if (strcmp(fn_name, "get_stack_limit") == 0 && expr->as.call.num_args == 0) {
codegen_writeln(ctx, "HmlValue %s = hml_get_stack_limit();", result);
Expand Down
1 change: 1 addition & 0 deletions src/backends/compiler/type_check.c
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,7 @@ CheckedType* type_check_infer_expr(TypeCheckContext *ctx, Expr *expr) {
if (strcmp(name, "open") == 0) return checked_type_primitive(CHECKED_FILE);
if (strcmp(name, "channel") == 0) return checked_type_primitive(CHECKED_CHANNEL);
if (strcmp(name, "spawn") == 0) return checked_type_primitive(CHECKED_TASK);
if (strcmp(name, "schema") == 0) return checked_type_primitive(CHECKED_OBJECT);
if (strcmp(name, "read_line") == 0) {
CheckedType *t = checked_type_primitive(CHECKED_STRING);
t->nullable = 1;
Expand Down
3 changes: 3 additions & 0 deletions src/backends/interpreter/builtins/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ Value builtin_atomic_exchange_i64(Value *args, int num_args, ExecutionContext *c
// Memory fence
Value builtin_atomic_fence(Value *args, int num_args, ExecutionContext *ctx);

// Schema builtin (schema.c)
Value builtin_schema(Value *args, int num_args, ExecutionContext *ctx);

// Regex builtins (regex.c)
Value builtin_regex_compile(Value *args, int num_args, ExecutionContext *ctx);
Value builtin_regex_test(Value *args, int num_args, ExecutionContext *ctx);
Expand Down
2 changes: 2 additions & 0 deletions src/backends/interpreter/builtins/registration.c
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,8 @@ static BuiltinInfo builtins[] = {
{"atomic_exchange_i64", builtin_atomic_exchange_i64},
// Memory fence
{"atomic_fence", builtin_atomic_fence},
// Schema extraction for JSON validation
{"schema", builtin_schema},
{NULL, NULL} // Sentinel
};

Expand Down
197 changes: 197 additions & 0 deletions src/backends/interpreter/builtins/schema.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Hemlock Built-in: schema()
*
* Extracts a JSON Schema-compatible object from a registered define type.
* This enables validation of JSON data against Hemlock's structural types.
*
* Usage:
* define User { name: string, age: i32, email?: string }
* let s = schema("User");
* // Returns: {
* // type: "object",
* // name: "User",
* // properties: {
* // name: { type: "string", required: true },
* // age: { type: "integer", required: true },
* // email: { type: "string", required: false }
* // },
* // required: ["name", "age"]
* // }
*/

#include "internal.h"

// Map a Hemlock TypeKind to a JSON Schema type string
static const char* type_kind_to_json_schema_type(TypeKind kind) {
switch (kind) {
case TYPE_I8:
case TYPE_I16:
case TYPE_I32:
case TYPE_I64:
case TYPE_U8:
case TYPE_U16:
case TYPE_U32:
case TYPE_U64:
return "integer";
case TYPE_F32:
case TYPE_F64:
return "number";
case TYPE_BOOL:
return "boolean";
case TYPE_STRING:
return "string";
case TYPE_ARRAY:
return "array";
case TYPE_GENERIC_OBJECT:
case TYPE_CUSTOM_OBJECT:
return "object";
case TYPE_NULL:
return "null";
default:
return "any";
}
}

// Map a Hemlock TypeKind to its Hemlock type name string
static const char* type_kind_to_hemlock_type(TypeKind kind) {
switch (kind) {
case TYPE_I8: return "i8";
case TYPE_I16: return "i16";
case TYPE_I32: return "i32";
case TYPE_I64: return "i64";
case TYPE_U8: return "u8";
case TYPE_U16: return "u16";
case TYPE_U32: return "u32";
case TYPE_U64: return "u64";
case TYPE_F32: return "f32";
case TYPE_F64: return "f64";
case TYPE_BOOL: return "bool";
case TYPE_STRING: return "string";
case TYPE_RUNE: return "rune";
case TYPE_PTR: return "ptr";
case TYPE_BUFFER: return "buffer";
case TYPE_ARRAY: return "array";
case TYPE_NULL: return "null";
case TYPE_GENERIC_OBJECT: return "object";
default: return "any";
}
}

// schema(type_name: string) -> object
// Extracts a JSON Schema-compatible object from the type registry.
Value builtin_schema(Value *args, int num_args, ExecutionContext *ctx) {
if (num_args != 1) {
fprintf(stderr, "Runtime error: schema() expects 1 argument (type name)\n");
exit(1);
}

if (args[0].type != VAL_STRING) {
fprintf(stderr, "Runtime error: schema() argument must be a string\n");
exit(1);
}

const char *type_name = args[0].as.as_string->data;
ObjectType *obj_type = lookup_object_type(type_name);

if (!obj_type) {
fprintf(stderr, "Runtime error: schema() unknown type '%s'\n", type_name);
exit(1);
}

// Build the schema object
// Top-level: { type: "object", name: "TypeName", properties: {...}, required: [...] }
int num_top_fields = 4; // type, name, properties, required
Object *schema = object_new(NULL, num_top_fields);

// schema.type = "object"
Value type_val = val_string("object");
schema->fields[schema->num_fields].name = strdup("type");
schema->fields[schema->num_fields].value = type_val;
schema->num_fields++;

// schema.name = type_name
Value name_val = val_string(type_name);
schema->fields[schema->num_fields].name = strdup("name");
schema->fields[schema->num_fields].value = name_val;
schema->num_fields++;

// Build properties object
int num_fields = obj_type->num_fields;
Object *properties = object_new(NULL, num_fields > 0 ? num_fields : 1);

// Build required array
Array *required = array_new();

for (int i = 0; i < num_fields; i++) {
const char *field_name = obj_type->field_names[i];
Type *field_type = obj_type->field_types[i];
int is_optional = obj_type->field_optional[i];

// Build property descriptor: { type: "string", hemlock_type: "string", required: true/false }
int prop_field_count = 3;
if (field_type && field_type->kind == TYPE_CUSTOM_OBJECT && field_type->type_name) {
prop_field_count = 4; // Add ref field for custom object types
}
Object *prop = object_new(NULL, prop_field_count);

// JSON Schema type
const char *json_type = "any";
const char *hemlock_type = "any";
if (field_type) {
json_type = type_kind_to_json_schema_type(field_type->kind);
if (field_type->kind == TYPE_CUSTOM_OBJECT && field_type->type_name) {
hemlock_type = field_type->type_name;
} else {
hemlock_type = type_kind_to_hemlock_type(field_type->kind);
}
}

Value prop_type_val = val_string(json_type);
prop->fields[prop->num_fields].name = strdup("type");
prop->fields[prop->num_fields].value = prop_type_val;
prop->num_fields++;

Value prop_hml_type_val = val_string(hemlock_type);
prop->fields[prop->num_fields].name = strdup("hemlock_type");
prop->fields[prop->num_fields].value = prop_hml_type_val;
prop->num_fields++;

Value prop_required_val = val_bool(!is_optional);
prop->fields[prop->num_fields].name = strdup("required");
prop->fields[prop->num_fields].value = prop_required_val;
prop->num_fields++;

// For custom object types, add a $ref field
if (field_type && field_type->kind == TYPE_CUSTOM_OBJECT && field_type->type_name) {
Value ref_val = val_string(field_type->type_name);
prop->fields[prop->num_fields].name = strdup("$ref");
prop->fields[prop->num_fields].value = ref_val;
prop->num_fields++;
}

// Add property to properties object
properties->fields[properties->num_fields].name = strdup(field_name);
properties->fields[properties->num_fields].value = val_object(prop);
properties->num_fields++;

// Add to required array if not optional
if (!is_optional) {
Value req_name = val_string(field_name);
array_push(required, req_name);
value_release(req_name); // array_push retains
}
}

// schema.properties = properties
schema->fields[schema->num_fields].name = strdup("properties");
schema->fields[schema->num_fields].value = val_object(properties);
schema->num_fields++;

// schema.required = required
// Note: the field takes ownership of the ref_count=1 reference from array_new()
schema->fields[schema->num_fields].name = strdup("required");
schema->fields[schema->num_fields].value = val_array(required);
schema->num_fields++;

return val_object(schema);
}
Loading
Loading