use unilang_parser :: *;
use unilang_parser ::error :: { ErrorKind, SourceLocation };
fn options_error_on_duplicate_named() -> UnilangParserOptions
{
UnilangParserOptions
{
error_on_duplicate_named_arguments: true,
..Default ::default()
}
}
#[ test ]
fn ct1_1_single_str_single_path_unquoted_pos_arg()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "cmd val";
let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "CT1.1 Parse error: {:?}", result.err() );
let instruction = result.unwrap();
assert_eq!( instruction.command_path_slices, vec![ "cmd".to_string() ], "CT1.1 Path" ); assert_eq!( instruction.positional_arguments.len(), 1, "CT1.1 Positional args count" );
assert_eq!(
instruction.positional_arguments[ 0 ].value,
"val".to_string(),
"CT1.1 Positional arg value"
);
assert!( instruction.named_arguments.is_empty(), "CT1.1 Named args" );
}
#[ test ]
fn ct1_2_single_str_multi_path_unquoted_named_arg()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "path1 path2 name1 ::val1";
let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "CT1.2 Parse error: {:?}", result.err() );
let instruction = result.unwrap();
assert_eq!( instruction.command_path_slices, vec![ "path1".to_string() ], "CT1.2 Path" ); assert_eq!( instruction.positional_arguments.len(), 1, "CT1.2 Positional args count" ); assert_eq!(
instruction.positional_arguments[ 0 ].value,
"path2".to_string(),
"CT1.2 Positional arg value"
); assert_eq!( instruction.named_arguments.len(), 1, "CT1.2 Named args count" );
let arg1 = instruction.named_arguments.get( "name1" ).expect( "CT1.2 Missing name1" );
assert_eq!( arg1[0].value, "val1", "CT1.2 name1 value" ); }
#[ test ]
fn ct1_3_single_str_single_path_help_no_args()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "cmd ?";
let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "CT1.3 Parse error: {:?}", result.err() );
let instruction = result.unwrap();
assert_eq!( instruction.command_path_slices, vec![ "cmd".to_string() ], "CT1.3 Path" );
assert!( instruction.positional_arguments.is_empty(), "CT1.3 Positional args" );
assert!( instruction.named_arguments.is_empty(), "CT1.3 Named args" );
assert!( instruction.help_requested, "CT1.3 Help requested should be true" ); }
#[ test ]
fn ct1_4_single_str_single_path_quoted_pos_arg()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "cmd \"quoted val\"";
let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "CT1.4 Parse error: {:?}", result.err() );
let instruction = result.unwrap();
assert_eq!( instruction.command_path_slices, vec![ "cmd".to_string() ], "CT1.4 Path" );
assert_eq!( instruction.positional_arguments.len(), 1, "CT1.4 Positional args count" );
assert_eq!(
instruction.positional_arguments[ 0 ].value,
"quoted val".to_string(),
"CT1.4 Positional arg value"
);
assert!( instruction.named_arguments.is_empty(), "CT1.4 Named args" );
}
#[ test ]
fn ct1_5_single_str_single_path_named_arg_escaped_val()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "cmd name1 :: \"esc\\nval\"";
let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "CT1.5 Parse error: {:?}", result.err() );
let instruction = result.unwrap();
assert_eq!( instruction.command_path_slices, vec![ "cmd".to_string() ], "CT1.5 Path" );
assert!( instruction.positional_arguments.is_empty(), "CT1.5 Positional args" );
assert_eq!( instruction.named_arguments.len(), 1, "CT1.5 Named args count" );
let arg1 = instruction.named_arguments.get( "name1" ).expect( "CT1.5 Missing name1" );
assert_eq!( arg1[0].value, "esc\nval", "CT1.5 name1 value with newline" ); }
#[ test ]
fn ct1_6_single_str_single_path_named_arg_invalid_escape()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "cmd name1 :: \"bad\\xval\"";
let result = parser.parse_repl_input( input );
assert!(
result.is_ok(),
"CT1.6 Expected Ok for invalid escape, got Err: {:?}",
result.err()
);
let instruction = result.unwrap();
assert_eq!(
instruction.named_arguments.get( "name1" ).unwrap()[0].value,
"bad\\xval".to_string(),
"CT1.6 Invalid escape should be literal"
);
}
#[ test ]
fn ct3_1_single_str_separator_basic()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "cmd1 arg1 ;; cmd2 name ::val";
let result = parser.parse_multiple_instructions( input ); assert!( result.is_ok(), "CT3.1 Parse error: {:?}", result.err() );
let instructions = result.unwrap();
assert_eq!( instructions.len(), 2, "CT3.1 Instruction count" );
let instr1 = &instructions[ 0 ];
assert_eq!( instr1.command_path_slices, vec![ "cmd1".to_string() ], "CT3.1 Instr1 Path" ); assert_eq!( instr1.positional_arguments.len(), 1, "CT3.1 Instr1 Positional" ); assert_eq!(
instr1.positional_arguments[ 0 ].value,
"arg1".to_string(),
"CT3.1 Positional arg value"
); assert!( instr1.named_arguments.is_empty(), "CT3.1 Instr1 Named" );
let instr2 = &instructions[ 1 ];
assert_eq!( instr2.command_path_slices, vec![ "cmd2".to_string() ], "CT3.1 Instr2 Path" );
assert!( instr2.positional_arguments.is_empty(), "CT3.1 Instr2 Positional" );
assert_eq!( instr2.named_arguments.len(), 1, "CT3.1 Instr2 Named count" );
assert_eq!(
instr2.named_arguments.get( "name" ).unwrap()[0].value,
"val",
"CT3.1 Instr2 name value"
); }
#[ test ]
fn ct4_1_single_str_duplicate_named_error()
{
let parser = Parser ::new( options_error_on_duplicate_named() );
let input = "cmd name ::val1 name ::val2";
let result = parser.parse_repl_input( input );
assert!(
result.is_err(),
"CT4.1 Expected error for duplicate named, got Ok: {:?}",
result.ok()
);
if let Err( e ) = result
{
assert!(
matches!( e.kind, ErrorKind ::Syntax( _ ) ),
"CT4.1 ErrorKind mismatch: {:?}",
e.kind
);
assert!(
e.to_string().contains( "Duplicate named argument 'name'" ),
"CT4.1 Error message mismatch: {e}"
);
}
}
#[ test ]
fn ct4_2_single_str_duplicate_named_last_wins()
{
let parser = Parser ::new( UnilangParserOptions
{
error_on_duplicate_named_arguments: false,
..Default ::default()
}); let input = "cmd name ::val1 name ::val2";
let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "CT4.2 Parse error: {:?}", result.err() );
let instruction = result.unwrap();
assert_eq!( instruction.command_path_slices, vec![ "cmd".to_string() ] );
assert_eq!( instruction.named_arguments.len(), 1, "CT4.2 Named args count" );
assert_eq!(
instruction.named_arguments.get( "name" ).unwrap().last().unwrap().value,
"val2",
"CT4.2 Last value should win"
); }
#[ test ]
fn ct5_1_single_str_no_path_named_arg_only()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "name ::val";
let result = parser.parse_repl_input( input );
assert!(
result.is_ok(),
"CT5.1 Named-only args should parse successfully (spec.md:173), got error: {:?}",
result.err()
);
let inst = result.unwrap();
assert!( inst.command_path_slices.is_empty(), "CT5.1 Command path should be empty" );
assert!( inst.named_arguments.contains_key( "name" ), "CT5.1 Should have 'name' named arg" );
assert_eq!( inst.named_arguments.get( "name" ).unwrap()[0].value, "val", "CT5.1 Value should be 'val'" );
}
#[ test ]
fn ct6_1_command_path_with_dots_and_slashes()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "cmd.sub.path arg1 name ::val"; let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "CT6.1 Parse error: {:?}", result.err() );
let instruction = result.unwrap();
assert_eq!(
instruction.command_path_slices,
vec![ "cmd".to_string(), "sub".to_string(), "path".to_string() ],
"CT6.1 Path"
); assert_eq!( instruction.positional_arguments.len(), 1, "CT6.1 Positional args count" ); assert_eq!(
instruction.positional_arguments[ 0 ].value,
"arg1".to_string(),
"CT6.1 Positional arg value"
); assert_eq!( instruction.named_arguments.len(), 1, "CT6.1 Named args count" );
assert_eq!(
instruction.named_arguments.get( "name" ).unwrap()[0].value,
"val",
"CT6.1 name value"
); }
#[ test ]
fn sa1_1_root_namespace_list()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = ".";
let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "SA1.1 Parse error for '.' : {:?}", result.err() );
let instruction = result.unwrap();
assert!(
instruction.command_path_slices.is_empty(),
"SA1.1 Path for '.' should be empty"
);
assert!(
instruction.positional_arguments.is_empty(),
"SA1.1 Positional args for '.' should be empty"
);
assert!(
instruction.named_arguments.is_empty(),
"SA1.1 Named args for '.' should be empty"
);
assert_eq!( instruction.overall_location, SourceLocation ::StrSpan { start: 0, end: 1 } );
}
#[ test ]
fn sa1_2_root_namespace_help()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = ". ?";
let result = parser.parse_repl_input( input );
assert!( result.is_ok(), "SA1.2 Parse error for '. ?' : {:?}", result.err() );
let instruction = result.unwrap();
assert!(
instruction.command_path_slices.is_empty(),
"SA1.2 Path for '. ?' should be empty"
);
assert!(
instruction.positional_arguments.is_empty(),
"SA1.2 Positional args for '. ?' should be empty"
);
assert!( instruction.help_requested, "SA1.2 Help requested for '. ?' should be true" );
}
#[ test ]
fn sa2_1_whole_line_comment()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "# this is a whole line comment";
let result = parser.parse_repl_input( input );
assert!(
result.is_err(),
"SA2.1 Expected error for whole line comment, got Ok: {:?}",
result.ok()
);
if let Err( e ) = result
{
assert!(
matches!( e.kind, ErrorKind ::Syntax( _ ) ),
"SA2.1 ErrorKind mismatch: {:?}",
e.kind
);
assert!(
e.to_string().contains( "Unexpected token '#' in arguments" ),
"SA2.1 Error message mismatch: {e}"
);
}
}
#[ test ]
fn sa2_2_comment_only_line()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "#";
let result = parser.parse_repl_input( input );
assert!(
result.is_err(),
"SA2.2 Expected error for '#' only line, got Ok: {:?}",
result.ok()
);
if let Err( e ) = result
{
assert!(
matches!( e.kind, ErrorKind ::Syntax( _ ) ),
"SA2.2 ErrorKind mismatch: {:?}",
e.kind
);
assert!(
e.to_string().contains( "Unexpected token '#' in arguments" ),
"SA2.2 Error message mismatch: {e}"
);
}
}
#[ test ]
fn sa2_3_inline_comment_attempt()
{
let parser = Parser ::new( UnilangParserOptions ::default() );
let input = "cmd arg1 # inline comment";
let result = parser.parse_repl_input( input );
assert!(
result.is_err(),
"SA2.3 Expected error for inline '#', got Ok: {:?}",
result.ok()
);
if let Err( e ) = result
{
assert!(
matches!( e.kind, ErrorKind ::Syntax( _ ) ),
"SA2.3 ErrorKind mismatch: {:?}",
e.kind
);
assert!(
e.to_string().contains( "Unexpected token '#' in arguments" ),
"SA2.3 Error message mismatch: {e}"
); }
}
#[ test ]
fn parse_repl_input_exists_and_delegates_to_string_parser()
{
let parser = Parser::new( UnilangParserOptions::default() );
let result_old = parser.parse_repl_input( ".cmd key::val" );
let result_new = parser.parse_repl_input( ".cmd key::val" );
assert_eq!(
result_old.is_ok(),
result_new.is_ok(),
"parse_repl_input must delegate identically to parse_single_instruction"
);
}