Skip to main content

sochdb_core/
soch.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// SochDB - LLM-Optimized Embedded Database
3// Copyright (C) 2026 Sushanth Reddy Vanagala (https://github.com/sushanthpy)
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18//! TOON (Tabular Object-Oriented Notation) - Native Data Format for SochDB
19//!
20//! TOON is a compact, schema-aware data format optimized for LLMs and databases.
21//! It's the native format for SochDB, like JSON is for MongoDB.
22//!
23//! Format: `name[count]{fields}:\nrow1\nrow2\n...`
24//!
25//! Example:
26//! ```text
27//! users[3]{id,name,email}:
28//! 1,Alice,alice@example.com
29//! 2,Bob,bob@example.com
30//! 3,Charlie,charlie@example.com
31//! ```
32
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::fmt;
36
37/// TOON Value types
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub enum SochValue {
40    Null,
41    Bool(bool),
42    Int(i64),
43    UInt(u64),
44    Float(f64),
45    Text(String),
46    Binary(Vec<u8>),
47    Array(Vec<SochValue>),
48    Object(HashMap<String, SochValue>),
49    /// Reference to another table row: ref(table_name, id)
50    Ref {
51        table: String,
52        id: u64,
53    },
54}
55
56impl SochValue {
57    pub fn is_null(&self) -> bool {
58        matches!(self, SochValue::Null)
59    }
60
61    pub fn as_int(&self) -> Option<i64> {
62        match self {
63            SochValue::Int(v) => Some(*v),
64            SochValue::UInt(v) => Some(*v as i64),
65            _ => None,
66        }
67    }
68
69    pub fn as_uint(&self) -> Option<u64> {
70        match self {
71            SochValue::UInt(v) => Some(*v),
72            SochValue::Int(v) if *v >= 0 => Some(*v as u64),
73            _ => None,
74        }
75    }
76
77    pub fn as_float(&self) -> Option<f64> {
78        match self {
79            SochValue::Float(v) => Some(*v),
80            SochValue::Int(v) => Some(*v as f64),
81            SochValue::UInt(v) => Some(*v as f64),
82            _ => None,
83        }
84    }
85
86    pub fn as_text(&self) -> Option<&str> {
87        match self {
88            SochValue::Text(s) => Some(s),
89            _ => None,
90        }
91    }
92
93    pub fn as_bool(&self) -> Option<bool> {
94        match self {
95            SochValue::Bool(b) => Some(*b),
96            _ => None,
97        }
98    }
99}
100
101fn needs_quoting(s: &str) -> bool {
102    if s.is_empty() { return true; }
103    if s.starts_with(' ') || s.ends_with(' ') { return true; }
104    if matches!(s, "true" | "false" | "null") { return true; }
105    
106    // Check for number-like patterns
107    if s.parse::<f64>().is_ok() { return true; }
108    if s == "-" || s.starts_with('-') { return true; }
109    // Leading zeros check (e.g. 05 usually treated as number in some contexts or invalid)
110    if s.len() > 1 && s.starts_with('0') && s.chars().nth(1).map_or(false, |c| c.is_ascii_digit()) && !s.contains('.') {
111        return true;
112    }
113
114    // Check for special chars or delimiter (comma)
115    // Spec ยง7.3: :, ", \, [, ], {, }, newline, return, tab, delimiter
116    s.contains(|c| matches!(c, ':' | '"' | '\\' | '[' | ']' | '{' | '}' | '\n' | '\r' | '\t' | ','))
117}
118
119impl fmt::Display for SochValue {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        match self {
122            SochValue::Null => write!(f, "null"),
123            SochValue::Bool(b) => write!(f, "{}", b),
124            SochValue::Int(i) => write!(f, "{}", i),
125            SochValue::UInt(u) => write!(f, "{}", u),
126            SochValue::Float(fl) => write!(f, "{}", fl),
127            SochValue::Text(s) => {
128                if needs_quoting(s) {
129                    write!(f, "\"")?;
130                    for c in s.chars() {
131                        match c {
132                            '"' => write!(f, "\\\"")?,
133                            '\\' => write!(f, "\\\\")?,
134                            '\n' => write!(f, "\\n")?,
135                            '\r' => write!(f, "\\r")?,
136                            '\t' => write!(f, "\\t")?,
137                            c => write!(f, "{}", c)?,
138                        }
139                    }
140                    write!(f, "\"")
141                } else {
142                    write!(f, "{}", s)
143                }
144            }
145            SochValue::Binary(b) => write!(f, "0x{}", hex::encode(b)),
146            SochValue::Array(arr) => {
147                write!(f, "[")?;
148                for (i, v) in arr.iter().enumerate() {
149                    if i > 0 {
150                        write!(f, ";")?;
151                    }
152                    write!(f, "{}", v)?;
153                }
154                write!(f, "]")
155            }
156            SochValue::Object(obj) => {
157                write!(f, "{{")?;
158                for (i, (k, v)) in obj.iter().enumerate() {
159                    if i > 0 {
160                        write!(f, ";")?;
161                    }
162                    write!(f, "{}:{}", k, v)?;
163                }
164                write!(f, "}}")
165            }
166            SochValue::Ref { table, id } => write!(f, "@{}:{}", table, id),
167        }
168    }
169}
170
171/// Field type in a TOON schema
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub enum SochType {
174    Null,
175    Bool,
176    Int,
177    UInt,
178    Float,
179    Text,
180    Binary,
181    Array(Box<SochType>),
182    Object(Vec<(String, SochType)>),
183    Ref(String), // Reference to table name
184    /// Union of types (for nullable fields)
185    Optional(Box<SochType>),
186}
187
188impl SochType {
189    /// Check if a value matches this type
190    pub fn matches(&self, value: &SochValue) -> bool {
191        match (self, value) {
192            (SochType::Null, SochValue::Null) => true,
193            (SochType::Bool, SochValue::Bool(_)) => true,
194            (SochType::Int, SochValue::Int(_)) => true,
195            (SochType::UInt, SochValue::UInt(_)) => true,
196            (SochType::Float, SochValue::Float(_)) => true,
197            (SochType::Text, SochValue::Text(_)) => true,
198            (SochType::Binary, SochValue::Binary(_)) => true,
199            (SochType::Array(inner), SochValue::Array(arr)) => arr.iter().all(|v| inner.matches(v)),
200            (SochType::Ref(table), SochValue::Ref { table: t, .. }) => table == t,
201            (SochType::Optional(inner), value) => value.is_null() || inner.matches(value),
202            _ => false,
203        }
204    }
205
206    /// Parse type from string notation
207    pub fn parse(s: &str) -> Option<Self> {
208        let s = s.trim();
209        match s {
210            "null" => Some(SochType::Null),
211            "bool" => Some(SochType::Bool),
212            "int" | "i64" => Some(SochType::Int),
213            "uint" | "u64" => Some(SochType::UInt),
214            "float" | "f64" => Some(SochType::Float),
215            "text" | "string" => Some(SochType::Text),
216            "binary" | "bytes" => Some(SochType::Binary),
217            _ if s.starts_with("ref(") && s.ends_with(')') => {
218                let table = &s[4..s.len() - 1];
219                Some(SochType::Ref(table.to_string()))
220            }
221            _ if s.starts_with("array(") && s.ends_with(')') => {
222                let inner = &s[6..s.len() - 1];
223                SochType::parse(inner).map(|t| SochType::Array(Box::new(t)))
224            }
225            _ if s.ends_with('?') => {
226                let inner = &s[..s.len() - 1];
227                SochType::parse(inner).map(|t| SochType::Optional(Box::new(t)))
228            }
229            _ => None,
230        }
231    }
232}
233
234impl fmt::Display for SochType {
235    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236        match self {
237            SochType::Null => write!(f, "null"),
238            SochType::Bool => write!(f, "bool"),
239            SochType::Int => write!(f, "int"),
240            SochType::UInt => write!(f, "uint"),
241            SochType::Float => write!(f, "float"),
242            SochType::Text => write!(f, "text"),
243            SochType::Binary => write!(f, "binary"),
244            SochType::Array(inner) => write!(f, "array({})", inner),
245            SochType::Object(fields) => {
246                write!(f, "{{")?;
247                for (i, (name, ty)) in fields.iter().enumerate() {
248                    if i > 0 {
249                        write!(f, ",")?;
250                    }
251                    write!(f, "{}:{}", name, ty)?;
252                }
253                write!(f, "}}")
254            }
255            SochType::Ref(table) => write!(f, "ref({})", table),
256            SochType::Optional(inner) => write!(f, "{}?", inner),
257        }
258    }
259}
260
261/// A TOON schema definition
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263pub struct SochSchema {
264    /// Schema name (table name)
265    pub name: String,
266    /// Field definitions
267    pub fields: Vec<SochField>,
268    /// Primary key field name
269    pub primary_key: Option<String>,
270    /// Indexes on this schema
271    pub indexes: Vec<SochIndex>,
272}
273
274/// A field in a TOON schema
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
276pub struct SochField {
277    pub name: String,
278    pub field_type: SochType,
279    pub nullable: bool,
280    pub default: Option<String>, // Default value as TOON string
281}
282
283/// An index definition
284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
285pub struct SochIndex {
286    pub name: String,
287    pub fields: Vec<String>,
288    pub unique: bool,
289}
290
291impl SochSchema {
292    pub fn new(name: impl Into<String>) -> Self {
293        Self {
294            name: name.into(),
295            fields: Vec::new(),
296            primary_key: None,
297            indexes: Vec::new(),
298        }
299    }
300
301    pub fn field(mut self, name: impl Into<String>, field_type: SochType) -> Self {
302        self.fields.push(SochField {
303            name: name.into(),
304            field_type,
305            nullable: false,
306            default: None,
307        });
308        self
309    }
310
311    pub fn nullable_field(mut self, name: impl Into<String>, field_type: SochType) -> Self {
312        self.fields.push(SochField {
313            name: name.into(),
314            field_type,
315            nullable: true,
316            default: None,
317        });
318        self
319    }
320
321    pub fn primary_key(mut self, field: impl Into<String>) -> Self {
322        self.primary_key = Some(field.into());
323        self
324    }
325
326    pub fn index(mut self, name: impl Into<String>, fields: Vec<String>, unique: bool) -> Self {
327        self.indexes.push(SochIndex {
328            name: name.into(),
329            fields,
330            unique,
331        });
332        self
333    }
334
335    /// Get field names for header
336    pub fn field_names(&self) -> Vec<&str> {
337        self.fields.iter().map(|f| f.name.as_str()).collect()
338    }
339
340    /// Format schema header: name[0]{field1,field2,...}:
341    pub fn format_header(&self) -> String {
342        let fields: Vec<&str> = self.fields.iter().map(|f| f.name.as_str()).collect();
343        format!("{}[0]{{{}}}:", self.name, fields.join(","))
344    }
345}
346
347/// A TOON row - values for a single record
348#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
349pub struct SochRow {
350    pub values: Vec<SochValue>,
351}
352
353impl SochRow {
354    pub fn new(values: Vec<SochValue>) -> Self {
355        Self { values }
356    }
357
358    /// Get value by index
359    pub fn get(&self, index: usize) -> Option<&SochValue> {
360        self.values.get(index)
361    }
362
363    /// Format row as TOON line
364    pub fn format(&self) -> String {
365        self.values
366            .iter()
367            .map(|v| v.to_string())
368            .collect::<Vec<_>>()
369            .join(",")
370    }
371
372    /// Parse row from TOON line
373    pub fn parse(line: &str, schema: &SochSchema) -> Result<Self, String> {
374        let mut values = Vec::with_capacity(schema.fields.len());
375        let mut chars = line.chars().peekable();
376        let mut current = String::new();
377        let mut in_quotes = false;
378        let mut field_idx = 0;
379
380        while let Some(ch) = chars.next() {
381            match ch {
382                '"' if !in_quotes => {
383                    in_quotes = true;
384                }
385                '"' if in_quotes => {
386                    if chars.peek() == Some(&'"') {
387                        chars.next();
388                        current.push('"');
389                    } else {
390                        in_quotes = false;
391                    }
392                }
393                ',' if !in_quotes => {
394                    let value = Self::parse_value(&current, field_idx, schema)?;
395                    values.push(value);
396                    current.clear();
397                    field_idx += 1;
398                }
399                _ => {
400                    current.push(ch);
401                }
402            }
403        }
404
405        // Last field
406        if !current.is_empty() || field_idx < schema.fields.len() {
407            let value = Self::parse_value(&current, field_idx, schema)?;
408            values.push(value);
409        }
410
411        Ok(Self { values })
412    }
413
414    fn parse_value(s: &str, field_idx: usize, schema: &SochSchema) -> Result<SochValue, String> {
415        let s = s.trim();
416
417        if s.is_empty() || s == "null" {
418            return Ok(SochValue::Null);
419        }
420
421        let field = schema
422            .fields
423            .get(field_idx)
424            .ok_or_else(|| format!("Field index {} out of bounds", field_idx))?;
425
426        match &field.field_type {
427            SochType::Bool => match s.to_lowercase().as_str() {
428                "true" | "1" | "yes" => Ok(SochValue::Bool(true)),
429                "false" | "0" | "no" => Ok(SochValue::Bool(false)),
430                _ => Err(format!("Invalid bool: {}", s)),
431            },
432            SochType::Int => s
433                .parse::<i64>()
434                .map(SochValue::Int)
435                .map_err(|e| format!("Invalid int: {}", e)),
436            SochType::UInt => s
437                .parse::<u64>()
438                .map(SochValue::UInt)
439                .map_err(|e| format!("Invalid uint: {}", e)),
440            SochType::Float => s
441                .parse::<f64>()
442                .map(SochValue::Float)
443                .map_err(|e| format!("Invalid float: {}", e)),
444            SochType::Text => Ok(SochValue::Text(s.to_string())),
445            SochType::Binary => {
446                if let Some(hex_str) = s.strip_prefix("0x") {
447                    hex::decode(hex_str)
448                        .map(SochValue::Binary)
449                        .map_err(|e| format!("Invalid hex: {}", e))
450                } else {
451                    Err("Binary must start with 0x".to_string())
452                }
453            }
454            SochType::Ref(table) => {
455                // Format: @table:id or just id
456                if let Some(ref_str) = s.strip_prefix('@') {
457                    let parts: Vec<&str> = ref_str.split(':').collect();
458                    if parts.len() == 2 {
459                        let id = parts[1]
460                            .parse::<u64>()
461                            .map_err(|e| format!("Invalid ref id: {}", e))?;
462                        Ok(SochValue::Ref {
463                            table: parts[0].to_string(),
464                            id,
465                        })
466                    } else {
467                        Err(format!("Invalid ref format: {}", s))
468                    }
469                } else {
470                    let id = s
471                        .parse::<u64>()
472                        .map_err(|e| format!("Invalid ref id: {}", e))?;
473                    Ok(SochValue::Ref {
474                        table: table.clone(),
475                        id,
476                    })
477                }
478            }
479            SochType::Optional(inner) => {
480                // Try to parse as inner type
481                let temp_field = SochField {
482                    name: field.name.clone(),
483                    field_type: (**inner).clone(),
484                    nullable: true,
485                    default: None,
486                };
487                let temp_schema = SochSchema {
488                    name: schema.name.clone(),
489                    fields: vec![temp_field],
490                    primary_key: None,
491                    indexes: vec![],
492                };
493                Self::parse_value(s, 0, &temp_schema)
494            }
495            _ => Ok(SochValue::Text(s.to_string())),
496        }
497    }
498}
499
500/// A complete TOON table (header + rows)
501#[derive(Debug, Clone, Serialize, Deserialize)]
502pub struct SochTable {
503    pub schema: SochSchema,
504    pub rows: Vec<SochRow>,
505}
506
507impl SochTable {
508    pub fn new(schema: SochSchema) -> Self {
509        Self {
510            schema,
511            rows: Vec::new(),
512        }
513    }
514
515    pub fn with_rows(schema: SochSchema, rows: Vec<SochRow>) -> Self {
516        Self { schema, rows }
517    }
518
519    pub fn push(&mut self, row: SochRow) {
520        self.rows.push(row);
521    }
522
523    pub fn len(&self) -> usize {
524        self.rows.len()
525    }
526
527    pub fn is_empty(&self) -> bool {
528        self.rows.is_empty()
529    }
530
531    /// Format as TOON string
532    pub fn format(&self) -> String {
533        let fields: Vec<&str> = self.schema.fields.iter().map(|f| f.name.as_str()).collect();
534        let header = format!(
535            "{}[{}]{{{}}}:",
536            self.schema.name,
537            self.rows.len(),
538            fields.join(",")
539        );
540
541        let mut output = header;
542        for row in &self.rows {
543            output.push('\n');
544            output.push_str(&row.format());
545        }
546        output
547    }
548
549    /// Parse TOON string to table
550    pub fn parse(input: &str) -> Result<Self, String> {
551        let mut lines = input.lines();
552
553        // Parse header: name[count]{field1,field2,...}:
554        let header = lines.next().ok_or("Empty input")?;
555        let (schema, _count) = Self::parse_header(header)?;
556
557        // Parse rows
558        let mut rows = Vec::new();
559        for line in lines {
560            if line.trim().is_empty() {
561                continue;
562            }
563            let row = SochRow::parse(line, &schema)?;
564            rows.push(row);
565        }
566
567        Ok(Self { schema, rows })
568    }
569
570    fn parse_header(header: &str) -> Result<(SochSchema, usize), String> {
571        // name[count]{field1,field2,...}:
572        let header = header.trim_end_matches(':');
573
574        let bracket_start = header.find('[').ok_or("Missing [")?;
575        let bracket_end = header.find(']').ok_or("Missing ]")?;
576        let brace_start = header.find('{').ok_or("Missing {")?;
577        let brace_end = header.find('}').ok_or("Missing }")?;
578
579        let name = &header[..bracket_start];
580        let count_str = &header[bracket_start + 1..bracket_end];
581        let fields_str = &header[brace_start + 1..brace_end];
582
583        let count = count_str
584            .parse::<usize>()
585            .map_err(|e| format!("Invalid count: {}", e))?;
586
587        let field_names: Vec<&str> = fields_str.split(',').map(|s| s.trim()).collect();
588
589        let mut schema = SochSchema::new(name);
590        for field_name in field_names {
591            // Check if type is specified: field_name:type
592            if let Some(colon_pos) = field_name.find(':') {
593                let fname = &field_name[..colon_pos];
594                let ftype_str = &field_name[colon_pos + 1..];
595                let ftype = SochType::parse(ftype_str).unwrap_or(SochType::Text);
596                schema.fields.push(SochField {
597                    name: fname.to_string(),
598                    field_type: ftype,
599                    nullable: false,
600                    default: None,
601                });
602            } else {
603                // Default to text type
604                schema.fields.push(SochField {
605                    name: field_name.to_string(),
606                    field_type: SochType::Text,
607                    nullable: false,
608                    default: None,
609                });
610            }
611        }
612
613        Ok((schema, count))
614    }
615}
616
617impl fmt::Display for SochTable {
618    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619        write!(f, "{}", self.format())
620    }
621}
622
623/// Trait for accessing columnar data without allocation
624pub trait ColumnAccess {
625    fn row_count(&self) -> usize;
626    fn col_count(&self) -> usize;
627    fn field_names(&self) -> Vec<&str>;
628    fn write_value(
629        &self,
630        col_idx: usize,
631        row_idx: usize,
632        f: &mut dyn std::fmt::Write,
633    ) -> std::fmt::Result;
634}
635
636/// Cursor for iterating over columnar data and emitting TOON format
637pub struct SochCursor<'a, C: ColumnAccess> {
638    access: &'a C,
639    current_row: usize,
640    header_emitted: bool,
641    schema_name: String,
642}
643
644impl<'a, C: ColumnAccess> SochCursor<'a, C> {
645    pub fn new(access: &'a C, schema_name: String) -> Self {
646        Self {
647            access,
648            current_row: 0,
649            header_emitted: false,
650            schema_name,
651        }
652    }
653}
654
655impl<'a, C: ColumnAccess> Iterator for SochCursor<'a, C> {
656    type Item = String;
657
658    fn next(&mut self) -> Option<Self::Item> {
659        if !self.header_emitted {
660            self.header_emitted = true;
661            let fields = self.access.field_names().join(",");
662            return Some(format!(
663                "{}[{}]{{{}}}:",
664                self.schema_name,
665                self.access.row_count(),
666                fields
667            ));
668        }
669
670        if self.current_row >= self.access.row_count() {
671            return None;
672        }
673
674        let mut row_str = String::new();
675        for col_idx in 0..self.access.col_count() {
676            if col_idx > 0 {
677                row_str.push(',');
678            }
679            // We ignore write errors here as String write shouldn't fail
680            let _ = self
681                .access
682                .write_value(col_idx, self.current_row, &mut row_str);
683        }
684
685        self.current_row += 1;
686        Some(row_str)
687    }
688}
689
690#[cfg(test)]
691mod tests {
692    use super::*;
693
694    #[test]
695    fn test_soch_value_display() {
696        assert_eq!(SochValue::Int(42).to_string(), "42");
697        assert_eq!(SochValue::Text("hello".into()).to_string(), "hello");
698        assert_eq!(
699            SochValue::Text("hello, world".into()).to_string(),
700            "\"hello, world\""
701        );
702        assert_eq!(SochValue::Bool(true).to_string(), "true");
703        assert_eq!(SochValue::Null.to_string(), "null");
704    }
705
706    #[test]
707    fn test_soch_schema() {
708        let schema = SochSchema::new("users")
709            .field("id", SochType::UInt)
710            .field("name", SochType::Text)
711            .field("email", SochType::Text)
712            .primary_key("id");
713
714        assert_eq!(schema.name, "users");
715        assert_eq!(schema.fields.len(), 3);
716        assert_eq!(schema.primary_key, Some("id".to_string()));
717    }
718
719    #[test]
720    fn test_soch_table_format() {
721        let schema = SochSchema::new("users")
722            .field("id", SochType::UInt)
723            .field("name", SochType::Text)
724            .field("email", SochType::Text);
725
726        let mut table = SochTable::new(schema);
727        table.push(SochRow::new(vec![
728            SochValue::UInt(1),
729            SochValue::Text("Alice".into()),
730            SochValue::Text("alice@example.com".into()),
731        ]));
732        table.push(SochRow::new(vec![
733            SochValue::UInt(2),
734            SochValue::Text("Bob".into()),
735            SochValue::Text("bob@example.com".into()),
736        ]));
737
738        let formatted = table.format();
739        assert!(formatted.contains("users[2]{id,name,email}:"));
740        assert!(formatted.contains("1,Alice,alice@example.com"));
741        assert!(formatted.contains("2,Bob,bob@example.com"));
742    }
743
744    #[test]
745    fn test_soch_table_parse() {
746        let input = r#"users[2]{id,name,email}:
7471,Alice,alice@example.com
7482,Bob,bob@example.com"#;
749
750        let table = SochTable::parse(input).unwrap();
751        assert_eq!(table.schema.name, "users");
752        assert_eq!(table.rows.len(), 2);
753    }
754
755    #[test]
756    fn test_soch_type_parse() {
757        assert_eq!(SochType::parse("int"), Some(SochType::Int));
758        assert_eq!(SochType::parse("text"), Some(SochType::Text));
759        assert_eq!(
760            SochType::parse("ref(users)"),
761            Some(SochType::Ref("users".into()))
762        );
763        assert_eq!(
764            SochType::parse("int?"),
765            Some(SochType::Optional(Box::new(SochType::Int)))
766        );
767    }
768}