Add YAML to Rust test case generator
This commit is contained in:
parent
e1c43f7b2e
commit
483e0c3d52
|
|
@ -0,0 +1,13 @@
|
|||
pub mod types;
|
||||
pub mod errors;
|
||||
pub mod file;
|
||||
pub mod lexer;
|
||||
pub mod interpreter;
|
||||
pub mod repl;
|
||||
pub mod meta;
|
||||
pub mod builtin;
|
||||
|
||||
// Re-export commonly used items for tests and external users
|
||||
pub use crate::lexer::{Lexer, Token, TokenType};
|
||||
pub use crate::types::Value;
|
||||
pub use crate::interpreter::Interpreter;
|
||||
|
|
@ -1,13 +1,6 @@
|
|||
mod types;
|
||||
mod errors;
|
||||
mod file;
|
||||
mod lexer;
|
||||
mod interpreter;
|
||||
mod repl;
|
||||
mod meta;
|
||||
mod builtin;
|
||||
|
||||
use crate::interpreter::Interpreter;
|
||||
use sls::interpreter::Interpreter;
|
||||
use sls::meta;
|
||||
use sls::repl;
|
||||
|
||||
fn main() {
|
||||
println!("Starting sls (Rust) - {} v{}", meta::NAME, meta::VERSION);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,120 @@
|
|||
import yaml
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
"""
|
||||
Convert YAML test cases to Rust integration tests for the `sls` crate.
|
||||
|
||||
Usage:
|
||||
python3 SLS_Tests/yaml_to_rust_tests.py SLS_Tests/cases.yaml SLS_Rust/sls/tests/lexer_tests_generated.rs
|
||||
|
||||
This generator produces simple `#[test]` functions that run the lexer and
|
||||
verify token kinds and values (basic checks). It's intentionally conservative
|
||||
— it compares token types and lexemes/numeric values where applicable.
|
||||
"""
|
||||
|
||||
def sanitize_name(name: str) -> str:
|
||||
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
|
||||
name = re.sub(r"_+", "_", name)
|
||||
name = name.strip("_")
|
||||
if not name:
|
||||
name = "unnamed"
|
||||
return f"test_{name}"
|
||||
|
||||
|
||||
def rust_string_literal(s: str) -> str:
|
||||
return s.replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
|
||||
def token_match_expectation(token_var: str, expected: dict) -> str:
|
||||
ttype = expected.get('type')
|
||||
val = expected.get('value')
|
||||
|
||||
if ttype in ('i64','i32','i16','i8','u64','u32','u16','u8'):
|
||||
# parse integer from token.lexeme and compare
|
||||
return f"assert_eq!({token_var}.ttype, sls::lexer::TokenType::Int);\n let parsed: i128 = {token_var}.lexeme.parse().expect(\"expected integer\");\n assert_eq!(parsed, {val});"
|
||||
elif ttype in ('f64','f32'):
|
||||
return f"assert_eq!({token_var}.ttype, sls::lexer::TokenType::Float);\n let parsed: f64 = {token_var}.lexeme.parse().expect(\"expected float\");\n assert!((parsed - {val}).abs() < 1e-12);"
|
||||
elif ttype == 'string':
|
||||
return f"assert_eq!({token_var}.ttype, sls::lexer::TokenType::Str);\n assert_eq!({token_var}.lexeme, \"{rust_string_literal(str(val))}\");"
|
||||
elif ttype in ('identifier', 'identifier_literal'):
|
||||
return f"assert_eq!({token_var}.ttype, sls::lexer::TokenType::Ident);\n assert_eq!({token_var}.lexeme, \"{rust_string_literal(str(val))}\");"
|
||||
elif ttype == 'char':
|
||||
return f"assert_eq!({token_var}.ttype, sls::lexer::TokenType::Int);\n let parsed: i128 = {token_var}.lexeme.parse().expect(\"expected char code\");\n assert_eq!(parsed, {(ord(val))});" # type: ignore
|
||||
elif ttype == 'bool':
|
||||
return f"assert_eq!({token_var}.ttype, sls::lexer::TokenType::Ident);\n assert_eq!({token_var}.lexeme, \"{'true' if val else 'false'}\");"
|
||||
elif ttype == 'error':
|
||||
# For now, assert that we got an Illegal token
|
||||
return f"assert_eq!({token_var}.ttype, sls::lexer::TokenType::Illegal);"
|
||||
elif ttype == 'token_string':
|
||||
# Complex nested token strings are not handled by this simple generator
|
||||
return f"// token_string check not implemented; received token: {{:#?}}\n // TODO: implement nested expectations\n // for now just assert we got an Ident or similar\n assert!(!{token_var}.lexeme.is_empty());"
|
||||
else:
|
||||
return f"// Unhandled expected token type: {ttype}\n assert!(!{token_var}.lexeme.is_empty());"
|
||||
|
||||
|
||||
def generate_rust_test(test: dict) -> str:
|
||||
name = sanitize_name(test.get('name','unnamed'))
|
||||
code = test.get('code','')
|
||||
tokens = test.get('tokens', [])
|
||||
|
||||
fn_lines = [f"#[test]", f"fn {name}() " "{"]
|
||||
fn_lines.append(f" let src = \"{rust_string_literal(str(code))}\";")
|
||||
fn_lines.append(" let mut lexer = sls::lexer::Lexer::new(src);")
|
||||
fn_lines.append(" let mut got = vec![];")
|
||||
fn_lines.append(" loop {")
|
||||
fn_lines.append(" let t = lexer.next_token();")
|
||||
fn_lines.append(" if t.ttype == sls::lexer::TokenType::Eof { break; }")
|
||||
fn_lines.append(" got.push(t);")
|
||||
fn_lines.append(" }")
|
||||
fn_lines.append("")
|
||||
# Basic assertion count vs expected (allow zero expected -> empty)
|
||||
if tokens:
|
||||
fn_lines.append(f" assert_eq!(got.len(), {len(tokens)}usize, \"token count mismatch\");")
|
||||
else:
|
||||
fn_lines.append(" assert!(got.is_empty());")
|
||||
|
||||
for i, token in enumerate(tokens):
|
||||
expectation = token_match_expectation(f"got[{i}]", token)
|
||||
# indent lines of expectation properly
|
||||
for line in expectation.split('\n'):
|
||||
fn_lines.append(f" {line}")
|
||||
|
||||
fn_lines.append("}")
|
||||
fn_lines.append("")
|
||||
return "\n".join(fn_lines)
|
||||
|
||||
|
||||
def yaml_to_rust_tests(yaml_path: str, output_path: str):
|
||||
with open(yaml_path, 'r', encoding='utf-8') as f:
|
||||
tests = yaml.safe_load(f)
|
||||
|
||||
if not isinstance(tests, list):
|
||||
raise ValueError('Expected YAML to be a list of tests')
|
||||
|
||||
rust_tests = []
|
||||
for t in tests:
|
||||
rust_tests.append(generate_rust_test(t))
|
||||
|
||||
header = """// Generated tests - do not edit by hand
|
||||
// Use: run `python3 SLS_Tests/yaml_to_rust_tests.py SLS_Tests/cases.yaml tests/lexer_tests_generated.rs`
|
||||
|
||||
use sls; // crate under test
|
||||
|
||||
const INT64_MIN: i128 = i64::MIN as i128;
|
||||
const UINT64_MAX: i128 = u64::MAX as i128;
|
||||
|
||||
"""
|
||||
|
||||
out_text = header + "\n".join(rust_tests)
|
||||
Path(output_path).write_text(out_text, encoding='utf-8')
|
||||
print(f"Generated {len(rust_tests)} Rust tests -> {output_path}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('input')
|
||||
parser.add_argument('output')
|
||||
args = parser.parse_args()
|
||||
yaml_to_rust_tests(args.input, args.output)
|
||||
Loading…
Reference in New Issue