@@ -25,7 +25,7 @@ pub const Spec = struct {
2525 // Level 5 (Data Structures) fields:
2626 spec_type : ? []const u8 = null ,
2727 types : []const TypeDef = &.{},
28- // constants: stored as slice of ConstDef (deferred for MVP)
28+ constants : [] const ConstDef = &.{},
2929
3030 pub fn deinit (self : * Spec , allocator : std.mem.Allocator ) void {
3131 allocator .free (self .fields );
@@ -46,6 +46,11 @@ pub const TypeDef = struct {
4646 enum_values : []const []const u8 = &.{},
4747};
4848
49+ pub const ConstDef = struct {
50+ name : []const u8 ,
51+ value : []const u8 , // Stored as string for flexibility (int, float, etc.)
52+ };
53+
4954pub const Storage = struct {
5055 bits : u8 ,
5156 align_bytes : u8 ,
@@ -428,6 +433,13 @@ const Parser = struct {
428433 _ = try self .parseConversion ();
429434 } else if (std .mem .eql (u8 , maybe_key , "ops" )) {
430435 spec .ops = try self .parseOpList ();
436+ } else if (std .mem .eql (u8 , maybe_key , "constants" )) {
437+ // constants: is a section header
438+ self .skipWhitespaceAndComments ();
439+ if (self .peek ()) | ch | {
440+ if (ch == ':' ) _ = self .advance ();
441+ }
442+ spec .constants = try self .parseConstants ();
431443 } else if (std .mem .eql (u8 , maybe_key , "types" )) {
432444 // types: is a section header, not a key-value pair
433445 // Skip to next line for type definitions
@@ -998,6 +1010,61 @@ const Parser = struct {
9981010 return list .toOwnedSlice (self .allocator );
9991011 }
10001012
1013+ fn parseConstants (self : * Parser ) ! []const ConstDef {
1014+ // Parse constants: section
1015+ // Format: indent 2 (4 spaces): NAME: value
1016+ // Example:
1017+ // MAX_LEVEL: 16
1018+ // PROBABILITY: 0.5
1019+
1020+ var constants = try std .ArrayList (ConstDef ).initCapacity (self .allocator , 0 );
1021+ errdefer constants .deinit (self .allocator );
1022+
1023+ // Get current position (after "constants:" key)
1024+ const start_pos = self .pos ;
1025+
1026+ // Split remaining content into lines
1027+ var lines_it = std .mem .splitScalar (u8 , self .content [start_pos .. ], '\n ' );
1028+
1029+ while (lines_it .next ()) | raw_line | {
1030+ // Skip empty lines and comments
1031+ const trimmed = std .mem .trimLeft (u8 , raw_line , " \t \r " );
1032+ if (trimmed .len == 0 or trimmed [0 ] == '#' ) continue ;
1033+
1034+ // Count indentation (2-space units)
1035+ const indent = countIndent (raw_line );
1036+
1037+ // Check for top-level section ending (ops:, composite:, types:, etc.)
1038+ if (indent == 0 ) {
1039+ if (std .mem .indexOfScalar (u8 , trimmed , ':' )) | colon_idx | {
1040+ const key = trimmed [0.. colon_idx ];
1041+ if (std .mem .eql (u8 , key , "ops" ) or std .mem .eql (u8 , key , "composite" ) or
1042+ std .mem .eql (u8 , key , "test_vectors" ) or std .mem .eql (u8 , key , "description" ) or
1043+ std .mem .eql (u8 , key , "storage" ) or std .mem .eql (u8 , key , "level" ) or
1044+ std .mem .eql (u8 , key , "format" ) or std .mem .eql (u8 , key , "version" ) or
1045+ std .mem .eql (u8 , key , "type" ) or std .mem .eql (u8 , key , "types" ))
1046+ {
1047+ break ;
1048+ }
1049+ }
1050+ }
1051+
1052+ // Constant definition at indent 1 (2 spaces): "MAX_LEVEL: 16"
1053+ if (indent == 1 ) {
1054+ if (std .mem .indexOfScalar (u8 , trimmed , ':' )) | colon_idx | {
1055+ const const_name = std .mem .trimRight (u8 , trimmed [0.. colon_idx ], " \t " );
1056+ const const_value = std .mem .trim (u8 , trimmed [colon_idx + 1 .. ], " \t \r " );
1057+ try constants .append (self .allocator , .{
1058+ .name = try self .allocator .dupe (u8 , const_name ),
1059+ .value = try self .allocator .dupe (u8 , const_value ),
1060+ });
1061+ }
1062+ }
1063+ }
1064+
1065+ return constants .toOwnedSlice (self .allocator );
1066+ }
1067+
10011068 fn parseTypes (self : * Parser ) ! []const TypeDef {
10021069 // Line-based parser for indentation-based type definitions
10031070 // This handles YAML-like structure where type names are at indent 2
0 commit comments